Codex /v1/responses input items have a `type` discriminator (message, * function_call, function_call_output, reasoning, custom_tool_call, * custom_tool_call_output, ...). We collapse them into chat_msgs the same way * the chat completion / Anthropic parsers do, so the rest of the engine sees a * single conversation history shape. * * Protocol contract for stateless replay: * - The client
| 3151 | cursor = marker + strlen(inputs[i]->marker); |
| 3152 | } |
| 3153 | if (ok) ds4_tokenize_rendered_chat(e, cursor, &r->prompt); |
| 3154 | |
| 3155 | done: |
| 3156 | for (size_t i = 0; i < count; i++) |
| 3157 | ds4_vision_embedding_free(&embeddings[i]); |
| 3158 | free(embeddings); |
| 3159 | free(inputs); |
| 3160 | if (!ok) { |
| 3161 | ds4_tokens_free(&r->prompt); |
| 3162 | for (size_t i = 0; i < r->image_count; i++) |
| 3163 | ds4_vision_embedding_free(&r->images[i].embedding); |
| 3164 | free(r->images); |
| 3165 | r->images = NULL; |
| 3166 | r->image_count = 0; |
| 3167 | } |
| 3168 | return ok; |
| 3169 | } |
| 3170 | |
| 3171 | /* Render only the semantic tail that must be appended to the live KV for a |
| 3172 | * tool-result continuation. |
| 3173 | * |
| 3174 | * In the common agent tool path, the previous assistant tool-call turn is |
| 3175 | * already in the model session, including hidden thinking and exact sampled |
| 3176 | * DSML. The next request provides only the tool results, either as OpenAI |
| 3177 | * Responses tool-output items or Anthropic user content blocks. Re-rendering |
| 3178 | * the assistant call here would duplicate it and destroy cache alignment, so |
| 3179 | * this function starts at the first new item and emits only: |
| 3180 | * |
| 3181 | * previous EOS, tool results, and the next assistant prefix. |
| 3182 | * |
| 3183 | * This is intentionally independent from req.prompt's already-tokenized suffix: |
| 3184 | * suffix tokenization happens later after the cache decision, using the live |
| 3185 | * token prefix as the boundary. That avoids BPE merges across the visible |
| 3186 | * replay/live-KV boundary. */ |
| 3187 | static char *render_deepseek_live_tool_tail(const chat_msgs *msgs, int start, |
| 3188 | ds4_think_mode think_mode) { |
| 3189 | const bool think = ds4_think_mode_enabled(think_mode); |
| 3190 | buf out = {0}; |
| 3191 | buf_puts(&out, "<|end▁of▁sentence|>"); |
| 3192 | |
| 3193 | bool pending_assistant = false; |
| 3194 | bool pending_tool_result = false; |
| 3195 | for (int i = start; msgs && i < msgs->len; i++) { |
| 3196 | const chat_msg *m = &msgs->v[i]; |
| 3197 | if (role_is_system(m->role)) { |
| 3198 | continue; |
| 3199 | } else if (!strcmp(m->role, "user")) { |
| 3200 | buf_puts(&out, "<|User|>"); |
| 3201 | buf_puts(&out, m->content ? m->content : ""); |
| 3202 | pending_assistant = true; |
| 3203 | pending_tool_result = false; |
| 3204 | } else if (!strcmp(m->role, "tool") || !strcmp(m->role, "function")) { |
| 3205 | if (!pending_tool_result) buf_puts(&out, "<|User|>"); |
| 3206 | buf_puts(&out, "<tool_result>"); |
| 3207 | append_tool_result_text(&out, m->content); |
| 3208 | buf_puts(&out, "</tool_result>"); |
| 3209 | pending_assistant = true; |
| 3210 | pending_tool_result = true; |