-
Notifications
You must be signed in to change notification settings - Fork 232
Expand file tree
/
Copy pathrequest.py
More file actions
178 lines (154 loc) · 6.44 KB
/
request.py
File metadata and controls
178 lines (154 loc) · 6.44 KB
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
"""Conversation request models.
These types define the payload for starting and interacting with
conversations. They live in the SDK so that ``ConversationSettings``
can reference them without a cross-package dependency on the
agent-server.
"""
from __future__ import annotations
from typing import Annotated, Literal
from uuid import UUID
from pydantic import BaseModel, Discriminator, Field, Tag
from openhands.sdk.agent.acp_agent import ACPAgent
from openhands.sdk.agent.agent import Agent
from openhands.sdk.conversation.types import ConversationTags
from openhands.sdk.hooks import HookConfig
from openhands.sdk.llm.message import ImageContent, Message, TextContent
from openhands.sdk.plugin import PluginSource
from openhands.sdk.secret import SecretSource
from openhands.sdk.security.analyzer import SecurityAnalyzerBase
from openhands.sdk.security.confirmation_policy import (
ConfirmationPolicyBase,
NeverConfirm,
)
from openhands.sdk.subagent.schema import AgentDefinition
from openhands.sdk.utils.models import kind_of
from openhands.sdk.workspace import LocalWorkspace
# ---------------------------------------------------------------------------
# Helper type alias
# ---------------------------------------------------------------------------
ACPEnabledAgent = Annotated[
Annotated[Agent, Tag("Agent")] | Annotated[ACPAgent, Tag("ACPAgent")],
Discriminator(kind_of),
]
"""Discriminated union: either a regular Agent or an ACP-capable Agent."""
# ---------------------------------------------------------------------------
# Request models
# ---------------------------------------------------------------------------
class SendMessageRequest(BaseModel):
"""Payload to send a message to the agent."""
role: Literal["user", "system", "assistant", "tool"] = "user"
content: list[TextContent | ImageContent] = Field(default_factory=list)
run: bool = Field(
default=False,
description="Whether the agent loop should automatically run if not running",
)
def create_message(self) -> Message:
return Message(role=self.role, content=self.content)
class _StartConversationRequestBase(BaseModel):
"""Common conversation creation fields shared by conversation contracts."""
workspace: LocalWorkspace = Field(
...,
description="Working directory for agent operations and tool execution",
)
conversation_id: UUID | None = Field(
default=None,
description=(
"Optional conversation ID. If not provided, a random UUID will be "
"generated."
),
)
confirmation_policy: ConfirmationPolicyBase = Field(
default=NeverConfirm(),
description="Controls when the conversation will prompt the user before "
"continuing. Defaults to never.",
)
security_analyzer: SecurityAnalyzerBase | None = Field(
default=None,
description="Optional security analyzer to evaluate action risks.",
)
initial_message: SendMessageRequest | None = Field(
default=None, description="Initial message to pass to the LLM"
)
max_iterations: int = Field(
default=500,
ge=1,
description="If set, the max number of iterations the agent will run "
"before stopping. This is useful to prevent infinite loops.",
)
stuck_detection: bool = Field(
default=True,
description="If true, the conversation will use stuck detection to "
"prevent infinite loops.",
)
secrets: dict[str, SecretSource] = Field(
default_factory=dict,
description="Secrets available in the conversation",
)
tool_module_qualnames: dict[str, str] = Field(
default_factory=dict,
description=(
"Mapping of tool names to their module qualnames from the client's "
"registry. These modules will be dynamically imported on the server "
"to register the tools for this conversation."
),
)
agent_definitions: list[AgentDefinition] = Field(
default_factory=list,
description=(
"Agent definitions from the client's registry. These are "
"registered on the server so that DelegateTool and TaskSetTool "
"can see user-registered subagents."
),
)
plugins: list[PluginSource] | None = Field(
default=None,
description=(
"List of plugins to load for this conversation. Plugins are loaded "
"and their skills/MCP config are merged into the agent. "
"Hooks are extracted and stored for runtime execution."
),
)
hook_config: HookConfig | None = Field(
default=None,
description=(
"Optional hook configuration for this conversation. Hooks are shell "
"scripts that run at key lifecycle events (PreToolUse, PostToolUse, "
"UserPromptSubmit, Stop, etc.). If both hook_config and plugins are "
"provided, they are merged with explicit hooks running before plugin "
"hooks."
),
)
tags: ConversationTags = Field(
default_factory=dict,
description=(
"Key-value tags for the conversation. Keys must be lowercase "
"alphanumeric. Values are arbitrary strings up to 256 characters."
),
)
autotitle: bool = Field(
default=True,
description=(
"If true, automatically generate a title for the conversation from "
"the first user message. Precedence: title_llm_profile (if set and "
"loads) → agent.llm → message truncation."
),
)
title_llm_profile: str | None = Field(
default=None,
description=(
"Optional LLM profile name for title generation. If set, the LLM "
"is loaded from LLMProfileStore (~/.openhands/profiles/) and used "
"for LLM-based title generation. This enables using a fast/cheap "
"model for titles regardless of the agent's main model. If not "
"set (or profile loading fails), title generation falls back to "
"the agent's LLM."
),
)
class StartConversationRequest(_StartConversationRequestBase):
"""Payload to create a new conversation.
Contains an Agent configuration along with conversation-specific options.
"""
agent: Agent
class StartACPConversationRequest(_StartConversationRequestBase):
"""Payload to create a conversation with ACP-capable agent support."""
agent: ACPEnabledAgent