File size: 11,626 Bytes
4ef118d
 
 
 
 
592cb1d
4ef118d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592cb1d
 
 
 
 
 
 
4ef118d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592cb1d
 
 
 
 
 
 
 
 
 
4ef118d
 
 
 
 
 
 
 
 
 
 
 
 
592cb1d
 
 
4ef118d
 
 
592cb1d
 
4ef118d
 
592cb1d
 
 
 
 
 
4ef118d
 
 
 
 
592cb1d
4ef118d
 
 
592cb1d
 
 
 
 
 
4ef118d
 
 
 
 
592cb1d
4ef118d
 
 
 
 
592cb1d
 
 
 
 
 
4ef118d
 
 
 
592cb1d
 
4ef118d
 
 
 
 
 
 
592cb1d
 
 
 
 
 
4ef118d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592cb1d
 
 
 
 
 
 
 
 
4ef118d
592cb1d
 
 
 
 
 
 
 
 
 
4ef118d
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
"""
Data models for stream chat API.
Defines request/response schemas compatible with the Node.js backend.
"""

from typing import Any, Literal, Union

from pydantic import BaseModel, Field

# ================================================================================
# Request Models
# ================================================================================

class ToolDefinition(BaseModel):
    """Tool definition for function calling."""
    type: str = Field(default="function", description="Type of tool, usually 'function'")
    function: "FunctionDefinition"


class FunctionDefinition(BaseModel):
    """Function definition for tool calling."""
    name: str
    description: str
    parameters: dict[str, Any] = Field(default_factory=dict)


class ToolCall(BaseModel):
    """Tool call from AI model."""
    id: str | None = None
    type: str = Field(default="function")
    function: "FunctionCall"
    text_index: int | None = Field(default=None, alias="textIndex")


class FunctionCall(BaseModel):
    """Function call details."""
    name: str
    arguments: str


class UserTool(BaseModel):
    """User-defined tool (HTTP or MCP)."""
    id: str
    name: str
    description: str
    type: Literal["http", "mcp"]
    input_schema: dict[str, Any] = Field(default_factory=dict, alias="inputSchema")
    parameters: dict[str, Any] = Field(default_factory=dict)
    config: dict[str, Any] = Field(default_factory=dict)
    category: str | None = None


class StreamChatRequest(BaseModel):
    """Request model for stream chat endpoint."""
    # Provider configuration
    provider: Literal[
        "gemini", "openai", "openai_compatibility", "siliconflow",
        "glm", "deepseek", "volcengine", "modelscope", "kimi", "nvidia", "minimax"
    ]
    api_key: str = Field(..., alias="apiKey")
    base_url: str | None = Field(default=None, alias="baseUrl")
    model: str | None = None

    # Message content
    messages: list[dict[str, Any]]

    # Tool configuration
    tools: list[ToolDefinition] | None = None
    tool_choice: Any = Field(default=None, alias="toolChoice")
    tool_ids: list[str] = Field(default_factory=list, alias="toolIds")
    skill_ids: list[str] = Field(default_factory=list, alias="skillIds")
    user_tools: list[UserTool] = Field(default_factory=list, alias="userTools")
    skip_default_tools: bool = Field(default=False, alias="skipDefaultTools")

    # Team configuration (Expert Mode)
    expert_mode: bool = Field(default=False, alias="expertMode")
    team_mode: Literal["coordinate", "route", "broadcast", "tasks"] | None = Field(default=None, alias="teamMode")
    leader_agent_id: str | None = Field(default=None, alias="leaderAgentId")
    team_agent_ids: list[str] = Field(default_factory=list, alias="teamAgentIds")

    # Response format
    # Response format
    response_format: dict[str, Any] | None = Field(default=None, alias="responseFormat")

    # Thinking mode - supports boolean (enabled/disabled) or dict (specific config)
    thinking: dict[str, Any] | bool | None = None
    thinking_mode: Literal["smart", "deep", "fast"] | None = Field(
        default=None,
        alias="thinkingMode",
    )

    # Generation parameters
    temperature: float | None = None
    top_k: int | None = None
    top_p: float | None = None
    frequency_penalty: float | None = None
    presence_penalty: float | None = None

    # Context limit
    # Turn-based limit (1 turn = user + assistant exchange).
    context_turn_limit: int | None = Field(default=None, alias="contextTurns")

    # Search configuration
    search_provider: Literal["tavily", "serpapi"] | None = Field(default=None, alias="searchProvider")
    tavily_api_key: str | None = Field(default=None, alias="tavilyApiKey")
    exa_api_key: str | None = Field(default=None, alias="exaApiKey")
    exa_search_category: str | None = Field(default=None, alias="exaSearchCategory")
    search_backend: str | None = Field(default=None, alias="searchBackend")
    serpapi_api_key: str | None = Field(default=None, alias="serpapiApiKey")
    concurrency_limit: int | None = Field(default=None, alias="concurrencyLimit")
    sequential_research: bool = Field(default=False, alias="sequentialResearch")

    # User context
    user_id: str | None = Field(default=None, alias="userId")
    user_timezone: str | None = Field(default=None, alias="userTimezone")
    user_locale: str | None = Field(default=None, alias="userLocale")
    enable_long_term_memory: bool = Field(default=False, alias="enableLongTermMemory")
    database_provider: str | None = Field(default=None, alias="databaseProvider")
    memory_provider: str | None = Field(default=None, alias="memoryProvider")
    memory_model: str | None = Field(default=None, alias="memoryModel")
    memory_base_url: str | None = Field(default=None, alias="memoryBaseUrl")
    memory_api_key: str | None = Field(default=None, alias="memoryApiKey")

    # Session Summary Configuration (Separate from Memory)
    summary_provider: str | None = Field(default=None, alias="summaryProvider")
    summary_model: str | None = Field(default=None, alias="summaryModel")
    summary_base_url: str | None = Field(default=None, alias="summaryBaseUrl")
    summary_api_key: str | None = Field(default=None, alias="summaryApiKey")
    enable_session_summary: bool = Field(default=True, alias="enableSessionSummary")
    is_editing: bool = Field(default=False, alias="isEditing", description="Forced summary rebuild flag (for edits/regenerates)")

    # Stream flag (default true for streaming)
    stream: bool = True

    # Internal use only: Structured Output schema (Agno v2)
    output_schema: Any | None = Field(default=None, exclude=True)

    # Internal use only: Feature flags set by backend routes
    enable_skills: bool = Field(default=False, exclude=True)

    # Internal use only: Personalized prompt from agent config
    personalized_prompt: str | None = Field(default=None, exclude=True)

    # Internal use only: Agent identification (set by resolve_agent_config)
    agent_id: str | None = Field(default=None, exclude=True)
    agent_name: str | None = Field(default=None, exclude=True)
    agent_emoji: str | None = Field(default=None, exclude=True)
    agent_description: str | None = Field(default=None, exclude=True)

    # Context and Session
    # Context and Session
    conversation_id: str | None = Field(default=None, alias="conversationId", description="Unique identifier for the conversation")
    model_config = {"populate_by_name": True}


    # ========================================================================
    # HITL (Human-in-the-Loop) Interactive Form Support
    # ========================================================================
    # When user submits a form, frontend sends run_id + field_values to resume
    run_id: str | None = Field(default=None, alias="runId", description="Agent run ID for HITL resumption")
    field_values: dict[str, Any] | None = Field(default=None, alias="fieldValues", description="User-submitted form field values")


# Status of an agent in a team run
AgentStatus = Literal["active", "waiting", "ready", "error", "idle"]


class TextEvent(BaseModel):
    """Text content event."""
    model_config = {"populate_by_name": True}

    type: Literal["text"] = "text"
    content: str
    # Agent identification for Team mode (identifies which agent generated this content)
    agent_id: str | None = Field(default=None, alias="agentId")
    agent_name: str | None = Field(default=None, alias="agentName")
    agent_role: str | None = Field(default=None, alias="agentRole")
    agent_emoji: str | None = Field(default=None, alias="agentEmoji")
    agent_status: AgentStatus | None = Field(default=None, alias="agentStatus")


class ThoughtEvent(BaseModel):
    """Thought/reasoning content event."""
    model_config = {"populate_by_name": True}

    type: Literal["thought"] = "thought"
    content: str
    text_index: int | None = Field(default=None, alias="textIndex")
    # Agent identification for Team mode (identifies which agent generated this thought)
    agent_id: str | None = Field(default=None, alias="agentId")
    agent_name: str | None = Field(default=None, alias="agentName")
    agent_role: str | None = Field(default=None, alias="agentRole")
    agent_emoji: str | None = Field(default=None, alias="agentEmoji")
    agent_status: AgentStatus | None = Field(default=None, alias="agentStatus")


class ToolCallEvent(BaseModel):
    """Tool call event."""
    model_config = {"populate_by_name": True}

    type: Literal["tool_call"] = Field(default="tool_call", alias="type")
    id: str | None = None
    name: str
    arguments: str
    text_index: int | None = Field(default=None, alias="textIndex")
    # Agent identification for Team mode
    agent_id: str | None = Field(default=None, alias="agentId")
    agent_name: str | None = Field(default=None, alias="agentName")
    agent_role: str | None = Field(default=None, alias="agentRole")
    agent_emoji: str | None = Field(default=None, alias="agentEmoji")
    agent_status: AgentStatus | None = Field(default=None, alias="agentStatus")


class ToolResultEvent(BaseModel):
    """Tool result event."""
    model_config = {"populate_by_name": True}

    type: Literal["tool_result"] = Field(default="tool_result", alias="type")
    id: str | None = None
    name: str
    status: Literal["calling", "done", "error"]
    output: Any = None
    error: str | None = None
    duration_ms: int | None = Field(default=None, alias="durationMs")
    # Agent identification for Team mode
    agent_id: str | None = Field(default=None, alias="agentId")
    agent_name: str | None = Field(default=None, alias="agentName")
    agent_role: str | None = Field(default=None, alias="agentRole")
    agent_emoji: str | None = Field(default=None, alias="agentEmoji")
    agent_status: AgentStatus | None = Field(default=None, alias="agentStatus")


class SourceEvent(BaseModel):
    """Source/citation event."""
    uri: str
    title: str
    snippet: str | None = None


class DoneEvent(BaseModel):
    """Stream completion event."""
    type: Literal["done"] = "done"
    content: str
    output: Any | None = None
    thought: str | None = None
    sources: list[SourceEvent] | None = None


class ErrorEvent(BaseModel):
    """Error event."""
    type: Literal["error"] = "error"
    error: str


class FormRequestEvent(BaseModel):
    """Form request event for HITL interactive forms."""
    type: Literal["form_request"] = "form_request"
    run_id: str = Field(..., description="Agent run ID for resumption")
    form_id: str | None = Field(default=None, description="Form identifier from tool call")
    title: str | None = Field(default=None, description="Form title")
    fields: list[dict[str, Any]] = Field(..., description="Form field definitions for frontend rendering")


class AgentStatusEvent(BaseModel):
    """Event for signaling agent status transitions in Team mode."""
    model_config = {"populate_by_name": True}

    type: Literal["agent_status"] = "agent_status"
    agent_id: str = Field(..., alias="agentId")
    status: AgentStatus


# Union type for all SSE events
StreamEvent = Union[
    TextEvent,
    ThoughtEvent,
    ToolCallEvent,
    ToolResultEvent,
    DoneEvent,
    ErrorEvent,
    FormRequestEvent,
    AgentStatusEvent,
]


# ================================================================================
# Update forward references
# ================================================================================

ToolDefinition.model_rebuild()
ToolCall.model_rebuild()