anthropic_to_responses_response.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. package apicompat
  2. import (
  3. "crypto/rand"
  4. "encoding/hex"
  5. "encoding/json"
  6. "fmt"
  7. "time"
  8. )
  9. // ---------------------------------------------------------------------------
  10. // Non-streaming: AnthropicResponse → ResponsesResponse
  11. // ---------------------------------------------------------------------------
  12. // AnthropicToResponsesResponse converts an Anthropic Messages response into a
  13. // Responses API response. This is the reverse of ResponsesToAnthropic and
  14. // enables Anthropic upstream responses to be returned in OpenAI Responses format.
  15. func AnthropicToResponsesResponse(resp *AnthropicResponse) *ResponsesResponse {
  16. id := resp.ID
  17. if id == "" {
  18. id = generateResponsesID()
  19. }
  20. // Anthropic responses carry no creation timestamp, so stamp now — the same
  21. // synthesize-what-the-client-requires rule the generated id above follows.
  22. out := &ResponsesResponse{
  23. ID: id,
  24. Object: "response",
  25. CreatedAt: time.Now().Unix(),
  26. Model: resp.Model,
  27. }
  28. var outputs []ResponsesOutput
  29. var msgParts []ResponsesContentPart
  30. for _, block := range resp.Content {
  31. switch block.Type {
  32. case "thinking":
  33. if block.Thinking != "" {
  34. outputs = append(outputs, ResponsesOutput{
  35. Type: "reasoning",
  36. ID: generateItemID(),
  37. Summary: []ResponsesSummary{{
  38. Type: "summary_text",
  39. Text: block.Thinking,
  40. }},
  41. })
  42. }
  43. case "text":
  44. if block.Text != "" {
  45. msgParts = append(msgParts, ResponsesContentPart{
  46. Type: "output_text",
  47. Text: block.Text,
  48. })
  49. }
  50. case "tool_use":
  51. args := "{}"
  52. if len(block.Input) > 0 {
  53. args = string(block.Input)
  54. }
  55. outputs = append(outputs, ResponsesOutput{
  56. Type: "function_call",
  57. ID: generateItemID(),
  58. CallID: toResponsesCallID(block.ID),
  59. Name: block.Name,
  60. Arguments: args,
  61. Status: "completed",
  62. })
  63. }
  64. }
  65. // Assemble message output item from text parts
  66. if len(msgParts) > 0 {
  67. outputs = append(outputs, ResponsesOutput{
  68. Type: "message",
  69. ID: generateItemID(),
  70. Role: "assistant",
  71. Content: msgParts,
  72. Status: "completed",
  73. })
  74. }
  75. if len(outputs) == 0 {
  76. outputs = append(outputs, ResponsesOutput{
  77. Type: "message",
  78. ID: generateItemID(),
  79. Role: "assistant",
  80. Content: []ResponsesContentPart{{Type: "output_text", Text: ""}},
  81. Status: "completed",
  82. })
  83. }
  84. out.Output = outputs
  85. // Map stop_reason → status
  86. out.Status = anthropicStopReasonToResponsesStatus(AnthropicStopReasonString(resp.StopReason), resp.Content)
  87. if out.Status == "incomplete" {
  88. out.IncompleteDetails = &ResponsesIncompleteDetails{Reason: "max_output_tokens"}
  89. }
  90. // Usage
  91. // Anthropic's input_tokens excludes cache_read/cache_creation, while OpenAI
  92. // Responses' input_tokens is the total including cached tokens. Add them back
  93. // when converting so downstream consumers see OpenAI semantics.
  94. totalInputTokens := resp.Usage.InputTokens +
  95. resp.Usage.CacheReadInputTokens +
  96. resp.Usage.CacheCreationInputTokens
  97. out.Usage = &ResponsesUsage{
  98. InputTokens: totalInputTokens,
  99. OutputTokens: resp.Usage.OutputTokens,
  100. TotalTokens: totalInputTokens + resp.Usage.OutputTokens,
  101. CacheCreationInputTokens: resp.Usage.CacheCreationInputTokens,
  102. }
  103. if resp.Usage.CacheReadInputTokens > 0 {
  104. out.Usage.InputTokensDetails = &ResponsesInputTokensDetails{
  105. CachedTokens: resp.Usage.CacheReadInputTokens,
  106. }
  107. }
  108. return out
  109. }
  110. // anthropicStopReasonToResponsesStatus maps Anthropic stop_reason to Responses status.
  111. func anthropicStopReasonToResponsesStatus(stopReason string, blocks []AnthropicContentBlock) string {
  112. switch stopReason {
  113. case "max_tokens":
  114. return "incomplete"
  115. case "end_turn", "tool_use", "stop_sequence":
  116. return "completed"
  117. default:
  118. return "completed"
  119. }
  120. }
  121. // ---------------------------------------------------------------------------
  122. // Streaming: AnthropicStreamEvent → []ResponsesStreamEvent (stateful converter)
  123. // ---------------------------------------------------------------------------
  124. // AnthropicEventToResponsesState tracks state for converting a sequence of
  125. // Anthropic SSE events into Responses SSE events.
  126. type AnthropicEventToResponsesState struct {
  127. ResponseID string
  128. Model string
  129. Created int64
  130. SequenceNumber int
  131. // CreatedSent tracks whether response.created has been emitted.
  132. CreatedSent bool
  133. // CompletedSent tracks whether the terminal event has been emitted.
  134. CompletedSent bool
  135. // Current output tracking
  136. OutputIndex int
  137. CurrentItemID string
  138. CurrentItemType string // "message" | "function_call" | "reasoning"
  139. // For message output: accumulate text parts
  140. ContentIndex int
  141. // TextAccum accumulates the current text part so that output_text.done and
  142. // content_part.done can carry the full text (deltas carry increments only).
  143. TextAccum string
  144. // For function_call: track per-output info
  145. CurrentCallID string
  146. CurrentName string
  147. // Content of the currently open item, folded into Outputs when it closes.
  148. CurrentContent []ResponsesContentPart // message
  149. CurrentArgs string // function_call
  150. CurrentSummary string // reasoning
  151. // Outputs accumulates every closed output item so that response.completed
  152. // can carry the full output list. The OpenAI SDK's get_final_response()
  153. // parses the terminal event's response directly; without this, clients see
  154. // an empty output_text.
  155. Outputs []ResponsesOutput
  156. // Usage from message_start / message_delta. InputTokens here follows
  157. // Anthropic semantics (excludes cached tokens); they are added back when
  158. // emitting the OpenAI Responses usage.
  159. InputTokens int
  160. OutputTokens int
  161. CacheReadInputTokens int
  162. CacheCreationInputTokens int
  163. StopReason string
  164. }
  165. // NewAnthropicEventToResponsesState returns an initialised stream state.
  166. func NewAnthropicEventToResponsesState() *AnthropicEventToResponsesState {
  167. return &AnthropicEventToResponsesState{
  168. Created: time.Now().Unix(),
  169. }
  170. }
  171. // AnthropicEventToResponsesEvents converts a single Anthropic SSE event into
  172. // zero or more Responses SSE events, updating state as it goes.
  173. func AnthropicEventToResponsesEvents(
  174. evt *AnthropicStreamEvent,
  175. state *AnthropicEventToResponsesState,
  176. ) []ResponsesStreamEvent {
  177. switch evt.Type {
  178. case "message_start":
  179. return anthToResHandleMessageStart(evt, state)
  180. case "content_block_start":
  181. return anthToResHandleContentBlockStart(evt, state)
  182. case "content_block_delta":
  183. return anthToResHandleContentBlockDelta(evt, state)
  184. case "content_block_stop":
  185. return anthToResHandleContentBlockStop(evt, state)
  186. case "message_delta":
  187. return anthToResHandleMessageDelta(evt, state)
  188. case "message_stop":
  189. return anthToResHandleMessageStop(state)
  190. default:
  191. return nil
  192. }
  193. }
  194. // FinalizeAnthropicResponsesStream emits synthetic termination events if the
  195. // stream ended without a proper message_stop.
  196. func FinalizeAnthropicResponsesStream(state *AnthropicEventToResponsesState) []ResponsesStreamEvent {
  197. if !state.CreatedSent || state.CompletedSent {
  198. return nil
  199. }
  200. var events []ResponsesStreamEvent
  201. // Close any open item
  202. events = append(events, closeCurrentResponsesItem(state)...)
  203. status, incompleteDetails := anthropicResponsesStreamTerminalState(state.StopReason)
  204. events = append(events, makeResponsesCompletedEvent(state, status, incompleteDetails))
  205. state.CompletedSent = true
  206. return events
  207. }
  208. // ResponsesEventToSSE formats a ResponsesStreamEvent as an SSE data line.
  209. func ResponsesEventToSSE(evt ResponsesStreamEvent) (string, error) {
  210. data, err := json.Marshal(evt)
  211. if err != nil {
  212. return "", err
  213. }
  214. return fmt.Sprintf("event: %s\ndata: %s\n\n", evt.Type, data), nil
  215. }
  216. // --- internal handlers ---
  217. func anthToResHandleMessageStart(evt *AnthropicStreamEvent, state *AnthropicEventToResponsesState) []ResponsesStreamEvent {
  218. if evt.Message != nil {
  219. state.ResponseID = evt.Message.ID
  220. if state.Model == "" {
  221. state.Model = evt.Message.Model
  222. }
  223. if evt.Message.Usage.InputTokens > 0 {
  224. state.InputTokens = evt.Message.Usage.InputTokens
  225. }
  226. if evt.Message.Usage.CacheReadInputTokens > 0 {
  227. state.CacheReadInputTokens = evt.Message.Usage.CacheReadInputTokens
  228. }
  229. if evt.Message.Usage.CacheCreationInputTokens > 0 {
  230. state.CacheCreationInputTokens = evt.Message.Usage.CacheCreationInputTokens
  231. }
  232. }
  233. if state.CreatedSent {
  234. return nil
  235. }
  236. state.CreatedSent = true
  237. // Emit response.created
  238. return []ResponsesStreamEvent{makeResponsesCreatedEvent(state)}
  239. }
  240. func anthToResHandleContentBlockStart(evt *AnthropicStreamEvent, state *AnthropicEventToResponsesState) []ResponsesStreamEvent {
  241. if evt.ContentBlock == nil {
  242. return nil
  243. }
  244. var events []ResponsesStreamEvent
  245. switch evt.ContentBlock.Type {
  246. case "thinking":
  247. // 开新 item 前必须先关掉在开的那个,与下面的 tool_use 分支一致。
  248. // 一个 message item 在它的 text 块 content_block_stop 时是刻意保持打开的
  249. // (同一 item 里可能还有后续 text 块),所以 thinking 块到来时它仍然开着:
  250. // 不关就直接被 CurrentItemType/CurrentItemID 覆盖,累积在 CurrentContent
  251. // 里的助手文本既不会进 state.Outputs,也拿不到 output_item.done,
  252. // response.completed 于是只带 reasoning——客户端看到的是「成功但无输出」。
  253. // 交错思考(interleaved-thinking,本仓库在 anthropic-beta 透传里明确支持)
  254. // 会稳定产生 text → thinking 这个顺序。
  255. events = append(events, closeCurrentResponsesItem(state)...)
  256. state.CurrentItemID = generateItemID()
  257. state.CurrentItemType = "reasoning"
  258. state.ContentIndex = 0
  259. events = append(events, makeResponsesEvent(state, "response.output_item.added", &ResponsesStreamEvent{
  260. OutputIndex: state.OutputIndex,
  261. Item: &ResponsesOutput{
  262. Type: "reasoning",
  263. ID: state.CurrentItemID,
  264. },
  265. }))
  266. case "text":
  267. // If we don't have an open message item, open one
  268. if state.CurrentItemType != "message" {
  269. // 走到这里时 CurrentItemType 只可能是 ""(前一个 reasoning/function_call
  270. // 已在自己的 content_block_stop 里关闭);保留这次关闭是为了让三个分支
  271. // 的「开新 item 前先关旧的」保持同一条不变式,而不是留一个仅 text 例外。
  272. events = append(events, closeCurrentResponsesItem(state)...)
  273. state.CurrentItemID = generateItemID()
  274. state.CurrentItemType = "message"
  275. state.ContentIndex = 0
  276. events = append(events, makeResponsesEvent(state, "response.output_item.added", &ResponsesStreamEvent{
  277. OutputIndex: state.OutputIndex,
  278. Item: &ResponsesOutput{
  279. Type: "message",
  280. ID: state.CurrentItemID,
  281. Role: "assistant",
  282. Status: "in_progress",
  283. },
  284. }))
  285. }
  286. // response.content_part.added must precede the output_text.delta events
  287. // for that part. The message item is added with content: [], and the
  288. // OpenAI SDK's accumulating stream helper (client.responses.stream) only
  289. // appends a content part when it sees content_part.added. Without it the
  290. // following output_text.delta indexes output.content[content_index] and
  291. // raises IndexError. Raw event iteration
  292. // (responses.create(stream=True)) does not accumulate, which is why this
  293. // went unnoticed.
  294. events = append(events, makeResponsesEvent(state, "response.content_part.added", &ResponsesStreamEvent{
  295. OutputIndex: state.OutputIndex,
  296. ContentIndex: state.ContentIndex,
  297. ItemID: state.CurrentItemID,
  298. Part: &ResponsesContentPart{Type: "output_text", Text: ""},
  299. }))
  300. state.TextAccum = ""
  301. case "tool_use":
  302. // Close previous item if any
  303. events = append(events, closeCurrentResponsesItem(state)...)
  304. state.CurrentItemID = generateItemID()
  305. state.CurrentItemType = "function_call"
  306. state.CurrentCallID = toResponsesCallID(evt.ContentBlock.ID)
  307. state.CurrentName = evt.ContentBlock.Name
  308. events = append(events, makeResponsesEvent(state, "response.output_item.added", &ResponsesStreamEvent{
  309. OutputIndex: state.OutputIndex,
  310. Item: &ResponsesOutput{
  311. Type: "function_call",
  312. ID: state.CurrentItemID,
  313. CallID: state.CurrentCallID,
  314. Name: state.CurrentName,
  315. Status: "in_progress",
  316. },
  317. }))
  318. }
  319. return events
  320. }
  321. func anthToResHandleContentBlockDelta(evt *AnthropicStreamEvent, state *AnthropicEventToResponsesState) []ResponsesStreamEvent {
  322. if evt.Delta == nil {
  323. return nil
  324. }
  325. switch evt.Delta.Type {
  326. case "text_delta":
  327. if evt.Delta.Text == "" {
  328. return nil
  329. }
  330. state.TextAccum += evt.Delta.Text
  331. return []ResponsesStreamEvent{makeResponsesEvent(state, "response.output_text.delta", &ResponsesStreamEvent{
  332. OutputIndex: state.OutputIndex,
  333. ContentIndex: state.ContentIndex,
  334. Delta: evt.Delta.Text,
  335. ItemID: state.CurrentItemID,
  336. })}
  337. case "thinking_delta":
  338. if evt.Delta.Thinking == "" {
  339. return nil
  340. }
  341. state.CurrentSummary += evt.Delta.Thinking
  342. return []ResponsesStreamEvent{makeResponsesEvent(state, "response.reasoning_summary_text.delta", &ResponsesStreamEvent{
  343. OutputIndex: state.OutputIndex,
  344. SummaryIndex: 0,
  345. Delta: evt.Delta.Thinking,
  346. ItemID: state.CurrentItemID,
  347. })}
  348. case "input_json_delta":
  349. if evt.Delta.PartialJSON == "" {
  350. return nil
  351. }
  352. state.CurrentArgs += evt.Delta.PartialJSON
  353. return []ResponsesStreamEvent{makeResponsesEvent(state, "response.function_call_arguments.delta", &ResponsesStreamEvent{
  354. OutputIndex: state.OutputIndex,
  355. Delta: evt.Delta.PartialJSON,
  356. ItemID: state.CurrentItemID,
  357. CallID: state.CurrentCallID,
  358. Name: state.CurrentName,
  359. })}
  360. case "signature_delta":
  361. // Anthropic signature deltas have no Responses equivalent; skip
  362. return nil
  363. }
  364. return nil
  365. }
  366. func anthToResHandleContentBlockStop(evt *AnthropicStreamEvent, state *AnthropicEventToResponsesState) []ResponsesStreamEvent {
  367. switch state.CurrentItemType {
  368. case "reasoning":
  369. // Emit reasoning summary done + output item done
  370. events := []ResponsesStreamEvent{
  371. makeResponsesEvent(state, "response.reasoning_summary_text.done", &ResponsesStreamEvent{
  372. OutputIndex: state.OutputIndex,
  373. SummaryIndex: 0,
  374. ItemID: state.CurrentItemID,
  375. }),
  376. }
  377. events = append(events, closeCurrentResponsesItem(state)...)
  378. return events
  379. case "function_call":
  380. // Emit function_call_arguments.done + output item done
  381. events := []ResponsesStreamEvent{
  382. makeResponsesEvent(state, "response.function_call_arguments.done", &ResponsesStreamEvent{
  383. OutputIndex: state.OutputIndex,
  384. ItemID: state.CurrentItemID,
  385. CallID: state.CurrentCallID,
  386. Name: state.CurrentName,
  387. }),
  388. }
  389. events = append(events, closeCurrentResponsesItem(state)...)
  390. return events
  391. case "message":
  392. // Text block is done: emit output_text.done then content_part.done (the
  393. // order OpenAI uses), both carrying the part's full text. The message
  394. // item itself stays open since more blocks may follow.
  395. text := state.TextAccum
  396. state.TextAccum = ""
  397. contentIndex := state.ContentIndex
  398. state.CurrentContent = append(state.CurrentContent, ResponsesContentPart{Type: "output_text", Text: text})
  399. // 关掉一个 part 就推进 content_index:上面那句注释说的「item 保持打开,
  400. // 因为后面可能还有块」正是这里的触发条件。不推进的话,同一 item 里第二个
  401. // text 块会再发一次 content_part.added(content_index=0),与第一个 part 撞在
  402. // 同一下标上——SDK 的累积式 stream helper 按 content[content_index] 写入,
  403. // 后一个 part 直接覆盖前一个,可见文本丢失。
  404. // 只在这里推进:新 item 的 content_index 由 closeCurrentResponsesItem 归 0。
  405. state.ContentIndex++
  406. return []ResponsesStreamEvent{
  407. makeResponsesEvent(state, "response.output_text.done", &ResponsesStreamEvent{
  408. OutputIndex: state.OutputIndex,
  409. ContentIndex: contentIndex,
  410. ItemID: state.CurrentItemID,
  411. Text: text,
  412. }),
  413. makeResponsesEvent(state, "response.content_part.done", &ResponsesStreamEvent{
  414. OutputIndex: state.OutputIndex,
  415. ContentIndex: contentIndex,
  416. ItemID: state.CurrentItemID,
  417. Part: &ResponsesContentPart{Type: "output_text", Text: text},
  418. }),
  419. }
  420. }
  421. return nil
  422. }
  423. func anthToResHandleMessageDelta(evt *AnthropicStreamEvent, state *AnthropicEventToResponsesState) []ResponsesStreamEvent {
  424. if evt.Usage != nil {
  425. state.OutputTokens = evt.Usage.OutputTokens
  426. if evt.Usage.InputTokens > 0 {
  427. state.InputTokens = evt.Usage.InputTokens
  428. }
  429. if evt.Usage.CacheReadInputTokens > 0 {
  430. state.CacheReadInputTokens = evt.Usage.CacheReadInputTokens
  431. }
  432. if evt.Usage.CacheCreationInputTokens > 0 {
  433. state.CacheCreationInputTokens = evt.Usage.CacheCreationInputTokens
  434. }
  435. }
  436. if evt.Delta != nil && evt.Delta.StopReason != "" {
  437. state.StopReason = evt.Delta.StopReason
  438. }
  439. return nil
  440. }
  441. func anthToResHandleMessageStop(state *AnthropicEventToResponsesState) []ResponsesStreamEvent {
  442. if state.CompletedSent {
  443. return nil
  444. }
  445. var events []ResponsesStreamEvent
  446. events = append(events, closeCurrentResponsesItem(state)...)
  447. status, incompleteDetails := anthropicResponsesStreamTerminalState(state.StopReason)
  448. events = append(events, makeResponsesCompletedEvent(state, status, incompleteDetails))
  449. state.CompletedSent = true
  450. return events
  451. }
  452. // --- helper functions ---
  453. func anthropicResponsesStreamTerminalState(stopReason string) (string, *ResponsesIncompleteDetails) {
  454. if stopReason == "max_tokens" {
  455. return "incomplete", &ResponsesIncompleteDetails{Reason: "max_output_tokens"}
  456. }
  457. return "completed", nil
  458. }
  459. func closeCurrentResponsesItem(state *AnthropicEventToResponsesState) []ResponsesStreamEvent {
  460. if state.CurrentItemType == "" {
  461. return nil
  462. }
  463. // Assemble the full item: both output_item.done and response.completed must
  464. // carry its content. Emitting only {type,id,status} makes SDK-side
  465. // accumulation produce an empty output.
  466. item := ResponsesOutput{
  467. Type: state.CurrentItemType,
  468. ID: state.CurrentItemID,
  469. Status: "completed",
  470. }
  471. switch state.CurrentItemType {
  472. case "message":
  473. item.Role = "assistant"
  474. item.Content = state.CurrentContent
  475. case "function_call":
  476. item.CallID = state.CurrentCallID
  477. item.Name = state.CurrentName
  478. args := state.CurrentArgs
  479. if args == "" {
  480. args = "{}"
  481. }
  482. item.Arguments = args
  483. case "reasoning":
  484. if state.CurrentSummary != "" {
  485. item.Summary = []ResponsesSummary{{Type: "summary_text", Text: state.CurrentSummary}}
  486. }
  487. }
  488. state.Outputs = append(state.Outputs, item)
  489. // Reset
  490. state.CurrentItemType = ""
  491. state.CurrentItemID = ""
  492. state.CurrentCallID = ""
  493. state.CurrentName = ""
  494. state.CurrentContent = nil
  495. state.CurrentArgs = ""
  496. state.CurrentSummary = ""
  497. state.TextAccum = ""
  498. state.OutputIndex++
  499. state.ContentIndex = 0
  500. return []ResponsesStreamEvent{makeResponsesEvent(state, "response.output_item.done", &ResponsesStreamEvent{
  501. OutputIndex: state.OutputIndex - 1, // Use the index before increment
  502. Item: &item,
  503. })}
  504. }
  505. func makeResponsesCreatedEvent(state *AnthropicEventToResponsesState) ResponsesStreamEvent {
  506. seq := state.SequenceNumber
  507. state.SequenceNumber++
  508. return ResponsesStreamEvent{
  509. Type: "response.created",
  510. SequenceNumber: seq,
  511. Response: &ResponsesResponse{
  512. ID: state.ResponseID,
  513. Object: "response",
  514. CreatedAt: state.Created,
  515. Model: state.Model,
  516. Status: "in_progress",
  517. Output: []ResponsesOutput{},
  518. },
  519. }
  520. }
  521. func makeResponsesCompletedEvent(
  522. state *AnthropicEventToResponsesState,
  523. status string,
  524. incompleteDetails *ResponsesIncompleteDetails,
  525. ) ResponsesStreamEvent {
  526. seq := state.SequenceNumber
  527. state.SequenceNumber++
  528. // Anthropic's input_tokens excludes cache_read/cache_creation; add them
  529. // back to match OpenAI Responses semantics where input_tokens is the total.
  530. totalInputTokens := state.InputTokens + state.CacheReadInputTokens + state.CacheCreationInputTokens
  531. usage := &ResponsesUsage{
  532. InputTokens: totalInputTokens,
  533. OutputTokens: state.OutputTokens,
  534. TotalTokens: totalInputTokens + state.OutputTokens,
  535. CacheCreationInputTokens: state.CacheCreationInputTokens,
  536. }
  537. if state.CacheReadInputTokens > 0 {
  538. usage.InputTokensDetails = &ResponsesInputTokensDetails{
  539. CachedTokens: state.CacheReadInputTokens,
  540. }
  541. }
  542. eventType := "response.completed"
  543. if status == "incomplete" {
  544. eventType = "response.incomplete"
  545. }
  546. // Carry the output items accumulated over the stream. The SDK's
  547. // get_final_response() reads them straight from the terminal event, so an
  548. // empty list leaves clients with an empty result.
  549. outputs := state.Outputs
  550. if outputs == nil {
  551. outputs = []ResponsesOutput{}
  552. }
  553. return ResponsesStreamEvent{
  554. Type: eventType,
  555. SequenceNumber: seq,
  556. Response: &ResponsesResponse{
  557. ID: state.ResponseID,
  558. Object: "response",
  559. CreatedAt: state.Created,
  560. Model: state.Model,
  561. Status: status,
  562. Output: outputs,
  563. Usage: usage,
  564. IncompleteDetails: incompleteDetails,
  565. },
  566. }
  567. }
  568. func makeResponsesEvent(state *AnthropicEventToResponsesState, eventType string, template *ResponsesStreamEvent) ResponsesStreamEvent {
  569. seq := state.SequenceNumber
  570. state.SequenceNumber++
  571. evt := *template
  572. evt.Type = eventType
  573. evt.SequenceNumber = seq
  574. return evt
  575. }
  576. func generateResponsesID() string {
  577. b := make([]byte, 12)
  578. _, _ = rand.Read(b)
  579. return "resp_" + hex.EncodeToString(b)
  580. }
  581. func generateItemID() string {
  582. b := make([]byte, 12)
  583. _, _ = rand.Read(b)
  584. return "item_" + hex.EncodeToString(b)
  585. }