outsail-sdk 0.28.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- outsail/__init__.py +30 -0
- outsail/agent_tools.py +212 -0
- outsail/client.py +985 -0
- outsail/errors.py +23 -0
- outsail/py.typed +1 -0
- outsail/streaming.py +91 -0
- outsail/types.py +374 -0
- outsail_sdk-0.28.1.dist-info/METADATA +87 -0
- outsail_sdk-0.28.1.dist-info/RECORD +12 -0
- outsail_sdk-0.28.1.dist-info/WHEEL +5 -0
- outsail_sdk-0.28.1.dist-info/licenses/LICENSE +21 -0
- outsail_sdk-0.28.1.dist-info/top_level.txt +1 -0
outsail/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Outsail Python SDK — Context Intelligence for AI agents.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from outsail import OutsailClient
|
|
7
|
+
|
|
8
|
+
async def main():
|
|
9
|
+
async with OutsailClient(api_key="osk_...") as client:
|
|
10
|
+
pack = await client.research(objective="Assess OAuth guidance", discover=True)
|
|
11
|
+
print(pack.answer)
|
|
12
|
+
|
|
13
|
+
asyncio.run(main())
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .client import OutsailClient
|
|
17
|
+
from .errors import OutsailApiError, OutsailError
|
|
18
|
+
from .streaming import SseEvent, stream_events
|
|
19
|
+
from . import agent_tools
|
|
20
|
+
from . import types
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"OutsailClient",
|
|
24
|
+
"OutsailError",
|
|
25
|
+
"OutsailApiError",
|
|
26
|
+
"SseEvent",
|
|
27
|
+
"stream_events",
|
|
28
|
+
"agent_tools",
|
|
29
|
+
"types",
|
|
30
|
+
]
|
outsail/agent_tools.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OpenAI and Claude tool definitions generated from the Outsail Capability
|
|
3
|
+
Registry. These functions let you register Outsail capabilities as tools
|
|
4
|
+
for OpenAI Responses / Chat Completions API and Claude Messages API.
|
|
5
|
+
|
|
6
|
+
Usage (OpenAI)::
|
|
7
|
+
|
|
8
|
+
from outsail import OutsailClient
|
|
9
|
+
from outsail.agent_tools import create_openai_tools, execute_tool_call
|
|
10
|
+
|
|
11
|
+
async with OutsailClient(base_url="...", api_key="...") as client:
|
|
12
|
+
tools = await create_openai_tools(client)
|
|
13
|
+
# Pass tools to OpenAI: tools parameter
|
|
14
|
+
# When the model returns a tool call:
|
|
15
|
+
result = await execute_tool_call(client, tool_call.function.name, json.loads(tool_call.function.arguments))
|
|
16
|
+
|
|
17
|
+
Usage (Claude)::
|
|
18
|
+
|
|
19
|
+
from outsail import OutsailClient
|
|
20
|
+
from outsail.agent_tools import create_claude_tools, execute_tool_call
|
|
21
|
+
|
|
22
|
+
async with OutsailClient(base_url="...", api_key="...") as client:
|
|
23
|
+
tools = await create_claude_tools(client)
|
|
24
|
+
# Pass tools to Claude: tools parameter
|
|
25
|
+
# When Claude uses a tool:
|
|
26
|
+
result = await execute_tool_call(client, tool_call.name, tool_call.input)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import asyncio
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from outsail.client import OutsailClient
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
# Tool definition generators
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
async def create_openai_tools(client: OutsailClient) -> list[dict]:
|
|
42
|
+
"""Fetch OpenAI-compatible tool definitions from the Outsail server.
|
|
43
|
+
|
|
44
|
+
Tools are generated from the curated Capability Registry — only
|
|
45
|
+
agent-safe operations are included.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
client: Authenticated OutsailClient instance.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of OpenAI function tool definitions.
|
|
52
|
+
"""
|
|
53
|
+
response = await client._request("GET", "/developer/tools/openai")
|
|
54
|
+
return response.get("tools", [])
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def create_claude_tools(client: OutsailClient) -> list[dict]:
|
|
58
|
+
"""Fetch Claude-compatible tool definitions from the Outsail server.
|
|
59
|
+
|
|
60
|
+
Tools are generated from the curated Capability Registry — only
|
|
61
|
+
agent-safe operations are included.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
client: Authenticated OutsailClient instance.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
List of Claude tool definitions.
|
|
68
|
+
"""
|
|
69
|
+
response = await client._request("GET", "/developer/tools/claude")
|
|
70
|
+
return response.get("tools", [])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
# Tool call executor
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
TOOL_HANDLERS: dict[str, callable] = {
|
|
78
|
+
# ── Research ────────────────────────────────────────────────────
|
|
79
|
+
"outsail_research_start": lambda client, args: client.start_research(
|
|
80
|
+
objective=args.get("objective", ""),
|
|
81
|
+
urls=args.get("urls"),
|
|
82
|
+
profile=args.get("profile"),
|
|
83
|
+
depth=args.get("depth"),
|
|
84
|
+
discover=args.get("discover"),
|
|
85
|
+
max_sources=args.get("max_sources"),
|
|
86
|
+
),
|
|
87
|
+
"outsail_research_status": lambda client, args: client.get_research_job(args["id"]),
|
|
88
|
+
|
|
89
|
+
# ── Context Packs ───────────────────────────────────────────────
|
|
90
|
+
"outsail_context_pack_get": lambda client, args: client.get_context_pack(args["id"]),
|
|
91
|
+
"outsail_agent_context_get": lambda client, args: client.agent_context(
|
|
92
|
+
args["id"], profile=args.get("profile"),
|
|
93
|
+
),
|
|
94
|
+
|
|
95
|
+
# ── Core Tools ──────────────────────────────────────────────────
|
|
96
|
+
"outsail_verify": lambda client, args: client.verify(
|
|
97
|
+
args["claim"],
|
|
98
|
+
urls=args.get("urls"),
|
|
99
|
+
discover=args.get("discover"),
|
|
100
|
+
profile=args.get("profile"),
|
|
101
|
+
),
|
|
102
|
+
"outsail_extract": lambda client, args: client.extract(args["url"]),
|
|
103
|
+
"outsail_map": lambda client, args: client.map(
|
|
104
|
+
args["url"],
|
|
105
|
+
max_pages=args.get("max_pages"),
|
|
106
|
+
max_depth=args.get("max_depth"),
|
|
107
|
+
),
|
|
108
|
+
"outsail_compare": lambda client, args: client.compare(
|
|
109
|
+
args["items"],
|
|
110
|
+
objective=args.get("objective"),
|
|
111
|
+
profile=args.get("profile"),
|
|
112
|
+
depth=args.get("depth"),
|
|
113
|
+
discover=args.get("discover"),
|
|
114
|
+
),
|
|
115
|
+
"outsail_report": lambda client, args: client.report(args["context_pack"]),
|
|
116
|
+
"outsail_decision_report": lambda client, args: client.report(args["context_pack"]),
|
|
117
|
+
"outsail_discover": lambda client, args: client.discover(
|
|
118
|
+
args["query"],
|
|
119
|
+
profile=args.get("profile"),
|
|
120
|
+
providers=args.get("providers"),
|
|
121
|
+
limit=args.get("limit"),
|
|
122
|
+
),
|
|
123
|
+
|
|
124
|
+
# ── Monitors ────────────────────────────────────────────────────
|
|
125
|
+
"outsail_monitor_list": lambda client, _args: client.list_monitors(),
|
|
126
|
+
"outsail_monitor_get": lambda client, args: client.get_monitor(args["id"]),
|
|
127
|
+
"outsail_monitor_create": lambda client, args: client.create_monitor(
|
|
128
|
+
objective=args.get("objective", ""),
|
|
129
|
+
urls=args.get("urls"),
|
|
130
|
+
rss_feeds=args.get("rss_feeds"),
|
|
131
|
+
profile=args.get("profile"),
|
|
132
|
+
interval_minutes=args.get("interval_minutes"),
|
|
133
|
+
materiality_threshold=args.get("materiality_threshold"),
|
|
134
|
+
),
|
|
135
|
+
"outsail_monitor_run": lambda client, args: client.check_monitor(args["id"]),
|
|
136
|
+
"outsail_monitor_pause": lambda client, args: client.set_monitor_status(args["id"], "paused"),
|
|
137
|
+
"outsail_monitor_resume": lambda client, args: client.set_monitor_status(args["id"], "active"),
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def execute_tool_call(
|
|
142
|
+
client: OutsailClient,
|
|
143
|
+
tool_name: str,
|
|
144
|
+
args: dict[str, Any],
|
|
145
|
+
) -> Any:
|
|
146
|
+
"""Execute a tool call by name with validated arguments.
|
|
147
|
+
|
|
148
|
+
Determines the correct SDK method from the tool name, calls it,
|
|
149
|
+
and returns the result. For long-running research tools, returns
|
|
150
|
+
a job handle — use ``poll_tool_result`` to wait for completion.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
client: Authenticated OutsailClient instance.
|
|
154
|
+
tool_name: Tool name (e.g. ``"outsail_research_start"``).
|
|
155
|
+
args: Tool arguments (validated against registry schema).
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Tool execution result.
|
|
159
|
+
|
|
160
|
+
Raises:
|
|
161
|
+
ValueError: If the tool name is unknown.
|
|
162
|
+
OutsailApiError: If the API call fails.
|
|
163
|
+
"""
|
|
164
|
+
handler = TOOL_HANDLERS.get(tool_name)
|
|
165
|
+
if handler is None:
|
|
166
|
+
raise ValueError(
|
|
167
|
+
f"Unknown tool: {tool_name}. "
|
|
168
|
+
"Available tools can be fetched via create_openai_tools() or create_claude_tools()."
|
|
169
|
+
)
|
|
170
|
+
return await handler(client, args)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def poll_tool_result(
|
|
174
|
+
client: OutsailClient,
|
|
175
|
+
job_id: str,
|
|
176
|
+
*,
|
|
177
|
+
poll_interval_ms: int = 2000,
|
|
178
|
+
timeout_ms: int = 300_000,
|
|
179
|
+
) -> Any:
|
|
180
|
+
"""Poll a research job until completion.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
client: Authenticated OutsailClient instance.
|
|
184
|
+
job_id: Research job ID from :meth:`start_research` /
|
|
185
|
+
``outsail_research_start``.
|
|
186
|
+
poll_interval_ms: Polling interval in milliseconds (default 2000).
|
|
187
|
+
timeout_ms: Maximum wait time in milliseconds (default 300000 = 5 min).
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Completed job result with ``context_pack`` if successful.
|
|
191
|
+
|
|
192
|
+
Raises:
|
|
193
|
+
TimeoutError: If the job does not complete within ``timeout_ms``.
|
|
194
|
+
RuntimeError: If the job fails.
|
|
195
|
+
"""
|
|
196
|
+
start_time = asyncio.get_event_loop().time()
|
|
197
|
+
deadline = start_time + (timeout_ms / 1000)
|
|
198
|
+
|
|
199
|
+
while True:
|
|
200
|
+
now = asyncio.get_event_loop().time()
|
|
201
|
+
if now >= deadline:
|
|
202
|
+
raise TimeoutError(f"Research job {job_id} timed out after {timeout_ms}ms")
|
|
203
|
+
|
|
204
|
+
await asyncio.sleep(poll_interval_ms / 1000)
|
|
205
|
+
|
|
206
|
+
job = await client.get_research_job(job_id)
|
|
207
|
+
|
|
208
|
+
if job.status == "completed":
|
|
209
|
+
return job
|
|
210
|
+
if job.status == "failed":
|
|
211
|
+
error_msg = getattr(job, "error", None) or "Unknown error"
|
|
212
|
+
raise RuntimeError(f"Research job {job_id} failed: {error_msg}")
|