Skip to content

Decouple protocol call_id from internal tool_call UUID #178

Description

@AnthonyRonning

Summary

We currently treat tool call_id as our internal tool_calls.uuid instead of the model/provider's opaque string ID. That came from the older classifier-style tool execution path, where we generated the tool call ourselves. The temporary Kimi replay normalization merged to master works, but this should be cleaned up at the storage and protocol layers.

Current behavior

  • src/web/responses/handlers.rs::append_streamed_tool_calls() and finalize_first_model_tool_call() do not retain provider tool_calls[*].id
  • src/web/responses/handlers.rs::execute_tool_call_and_wait() mints Uuid::new_v4() for tool_call_id
  • tool_calls.uuid is persisted and then reused as the external call_id
  • src/web/responses/conversions.rs still types ConversationItem::{FunctionToolCall, FunctionToolCallOutput}.call_id as Uuid
  • src/web/responses/context_builder.rs replays those UUID strings back into assistant/tool history, with a Kimi-only normalization pass layered on top

Proposed fix

  • Add a nullable text column on tool_calls, e.g. protocol_call_id (or similar)
  • Keep the existing UUID column for internal joins and tool_call_fk
  • Persist the real provider/model-emitted tool call ID when available
  • Resolve external/API call_id as protocol_call_id.unwrap_or(uuid.to_string()) so legacy rows still work
  • Change protocol-facing Rust types/serialization to use String for call_id instead of Uuid
  • Continue to normalize replayed IDs for Kimi models at the request boundary, but normalize from the resolved external/protocol call_id
  • Do not add a duplicate call-id column to tool_outputs; resolve through tool_call_fk

Acceptance criteria

  • Migration adds nullable string column to tool_calls
  • Responses/Conversations code persists real provider/model tool-call IDs when present
  • API output treats call_id as an opaque string everywhere
  • Replay/build-prompt path uses stored protocol call IDs with UUID fallback for old rows
  • Kimi models still receive normalized functions.{name}:{index} history IDs
  • Regression tests cover legacy UUID-only rows, stored protocol IDs, and Kimi replay normalization

Context

The Kimi-specific replay fix is already merged and working; this issue tracks the follow-up to remove the underlying UUID == protocol call_id tech debt.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions