The API parsers are intentionally selective JSON parsers: they keep only * fields that affect model semantics, rendering, streaming, or cache keys, and * skip extension fields. The output is always a rendered DS4 chat/completion * prompt plus the small amount of protocol state needed to translate the reply. */
| 2633 | * In the common agent tool path, the previous assistant tool-call turn is |
| 2634 | * already in the model session, including hidden thinking and exact sampled |
| 2635 | * DSML. The next request provides only the tool results, either as OpenAI |
| 2636 | * Responses tool-output items or Anthropic user content blocks. Re-rendering |
| 2637 | * the assistant call here would duplicate it and destroy cache alignment, so |
| 2638 | * this function starts at the first new item and emits only: |
| 2639 | * |
| 2640 | * previous EOS, tool results, and the next assistant prefix. |
| 2641 | * |
| 2642 | * This is intentionally independent from req.prompt's already-tokenized suffix: |
| 2643 | * suffix tokenization happens later after the cache decision, using the live |
| 2644 | * token prefix as the boundary. That avoids BPE merges across the visible |
| 2645 | * replay/live-KV boundary. */ |
| 2646 | static char *render_deepseek_live_tool_tail(const chat_msgs *msgs, int start, |
| 2647 | ds4_think_mode think_mode) { |
| 2648 | const bool think = ds4_think_mode_enabled(think_mode); |
| 2649 | buf out = {0}; |
| 2650 | buf_puts(&out, "<|end▁of▁sentence|>"); |
| 2651 | |
| 2652 | bool pending_assistant = false; |
| 2653 | bool pending_tool_result = false; |
| 2654 | for (int i = start; msgs && i < msgs->len; i++) { |
| 2655 | const chat_msg *m = &msgs->v[i]; |
| 2656 | if (role_is_system(m->role)) { |
| 2657 | continue; |
| 2658 | } else if (!strcmp(m->role, "user")) { |
| 2659 | buf_puts(&out, "<|User|>"); |
| 2660 | buf_puts(&out, m->content ? m->content : ""); |
| 2661 | pending_assistant = true; |
| 2662 | pending_tool_result = false; |
| 2663 | } else if (!strcmp(m->role, "tool") || !strcmp(m->role, "function")) { |
| 2664 | if (!pending_tool_result) buf_puts(&out, "<|User|>"); |
| 2665 | buf_puts(&out, "<tool_result>"); |
| 2666 | append_tool_result_text(&out, m->content); |
| 2667 | buf_puts(&out, "</tool_result>"); |
| 2668 | pending_assistant = true; |
| 2669 | pending_tool_result = true; |
| 2670 | } else if (!strcmp(m->role, "assistant")) { |
| 2671 | if (pending_assistant) { |
| 2672 | buf_puts(&out, "<|Assistant|>"); |
| 2673 | if (think) { |
| 2674 | buf_puts(&out, "<think>"); |
| 2675 | buf_puts(&out, m->reasoning ? m->reasoning : ""); |
| 2676 | buf_puts(&out, "</think>"); |
| 2677 | } else { |
| 2678 | buf_puts(&out, "</think>"); |
| 2679 | } |
| 2680 | } |
| 2681 | buf_puts(&out, m->content ? m->content : ""); |
| 2682 | append_dsml_tool_calls_text(&out, &m->calls); |
| 2683 | buf_puts(&out, "<|end▁of▁sentence|>"); |
| 2684 | pending_assistant = false; |
| 2685 | pending_tool_result = false; |
| 2686 | } |
| 2687 | } |
| 2688 | |
| 2689 | if (pending_assistant) { |
| 2690 | buf_puts(&out, "<|Assistant|>"); |
| 2691 | buf_puts(&out, think ? "<think>" : "</think>"); |
| 2692 | } |