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 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}")