opentool-python 0.1.0__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.
@@ -0,0 +1,310 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import threading
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Callable
7
+ from http import HTTPStatus
8
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
9
+ from typing import Any
10
+ from urllib.parse import urlparse
11
+
12
+ from ..dto import (
13
+ CLI_ARGUMENT_APIKEYS,
14
+ CLI_ARGUMENT_HOST,
15
+ CLI_ARGUMENT_PORT,
16
+ CLI_ARGUMENT_TAG,
17
+ DEFAULT_PREFIX,
18
+ DEFAULT_TOOL_HOST,
19
+ DEFAULT_TOOL_PORT,
20
+ DEFAULT_TOOL_TAG,
21
+ JSONRPC_VERSION,
22
+ LOAD_METADATA_NOT_FOUND_CODE,
23
+ LOAD_METADATA_NOT_FOUND_MESSAGE,
24
+ JsonRPCHttpRequestBody,
25
+ JsonRPCHttpResponseBody,
26
+ JsonRPCHttpResponseBodyError,
27
+ StatusInfo,
28
+ StatusType,
29
+ Version,
30
+ )
31
+ from ..tool.model import EventType
32
+ from .cli_arguments import CliArguments
33
+ from .cli_tool import CliTool
34
+ from .exceptions import InvalidJsonPayloadException, JsonParseException, ServerException
35
+
36
+
37
+ class Server(ABC):
38
+ def __init__(self, cli_tool: CliTool) -> None:
39
+ self.cli_tool = cli_tool
40
+
41
+ @abstractmethod
42
+ def start(self) -> None:
43
+ raise NotImplementedError
44
+
45
+ @abstractmethod
46
+ def stop(self) -> None:
47
+ raise NotImplementedError
48
+
49
+
50
+ class OpenToolServer(Server):
51
+ def __init__(self, cli_tool: CliTool, cli_arguments: CliArguments | None = None) -> None:
52
+ super().__init__(cli_tool=cli_tool)
53
+ self.cli_arguments = cli_arguments
54
+ self.version = DEFAULT_TOOL_TAG
55
+ self.host = DEFAULT_TOOL_HOST
56
+ self.port = DEFAULT_TOOL_PORT
57
+ self.prefix = DEFAULT_PREFIX
58
+ self.api_keys: list[str] = []
59
+ self._server: ThreadingHTTPServer | None = None
60
+
61
+ def init_cli(self) -> None:
62
+ cli_args = self.cli_arguments.parse() if self.cli_arguments else {}
63
+ cli_args = self.cli_tool.init_args(cli_args) or cli_args
64
+
65
+ self.version = str(cli_args.get(CLI_ARGUMENT_TAG, DEFAULT_TOOL_TAG))
66
+ self.host = str(cli_args.get(CLI_ARGUMENT_HOST, DEFAULT_TOOL_HOST))
67
+
68
+ raw_port = cli_args.get(CLI_ARGUMENT_PORT)
69
+ if raw_port is not None:
70
+ self.port = int(raw_port)
71
+
72
+ api_keys = cli_args.get(CLI_ARGUMENT_APIKEYS)
73
+ self.api_keys = list(api_keys) if isinstance(api_keys, list) else []
74
+
75
+ def start(self) -> None:
76
+ self.init_cli()
77
+ self.cli_tool.init()
78
+
79
+ server = self
80
+
81
+ class Handler(BaseHTTPRequestHandler):
82
+ protocol_version = "HTTP/1.1"
83
+
84
+ def log_message(self, fmt: str, *args: Any) -> None:
85
+ return None
86
+
87
+ def do_GET(self) -> None: # noqa: N802
88
+ if not self._authorize():
89
+ return
90
+ path = self._path_without_query()
91
+ if path == f"{server.prefix}/version":
92
+ self._respond_json(HTTPStatus.OK, Version(version=server.version).to_json())
93
+ return
94
+ if path == f"{server.prefix}/load":
95
+ try:
96
+ open_tool = server.cli_tool.load()
97
+ if open_tool is None:
98
+ self._respond_json(
99
+ HTTPStatus.NOT_FOUND,
100
+ {"code": LOAD_METADATA_NOT_FOUND_CODE, "message": LOAD_METADATA_NOT_FOUND_MESSAGE},
101
+ )
102
+ else:
103
+ self._respond_json(HTTPStatus.OK, open_tool.to_json())
104
+ except Exception as exc:
105
+ self._respond_json(
106
+ HTTPStatus.INTERNAL_SERVER_ERROR,
107
+ {"error": str(exc)},
108
+ )
109
+ return
110
+ self._respond_json(HTTPStatus.NOT_FOUND, {"error": "Not found"})
111
+
112
+ def do_POST(self) -> None: # noqa: N802
113
+ if not self._authorize():
114
+ return
115
+ path = self._path_without_query()
116
+ if path == f"{server.prefix}/call":
117
+ self._handle_call()
118
+ return
119
+ if path == f"{server.prefix}/streamCall":
120
+ self._handle_stream_call()
121
+ return
122
+ if path == f"{server.prefix}/stop":
123
+ self._handle_stop()
124
+ return
125
+ self._respond_json(HTTPStatus.NOT_FOUND, {"error": "Not found"})
126
+
127
+ def _handle_call(self) -> None:
128
+ payload_result = self._read_json_payload()
129
+ if isinstance(payload_result, tuple):
130
+ self._respond_json(payload_result[0], payload_result[1])
131
+ return
132
+
133
+ payload = payload_result
134
+ try:
135
+ body = JsonRPCHttpRequestBody.from_json(payload)
136
+ except Exception as exc:
137
+ self._respond_json(
138
+ HTTPStatus.BAD_REQUEST,
139
+ _json_parse_error_response(
140
+ JsonParseException(message=str(exc)),
141
+ id=str(payload.get("id", "")),
142
+ ),
143
+ )
144
+ return
145
+
146
+ try:
147
+ result = server.cli_tool.call(body.method, body.params)
148
+ response_body = JsonRPCHttpResponseBody(result=result, id=body.id)
149
+ self._respond_json(HTTPStatus.OK, response_body.to_json())
150
+ except Exception as exc:
151
+ response_body = JsonRPCHttpResponseBody(
152
+ result={},
153
+ id=body.id,
154
+ error=JsonRPCHttpResponseBodyError(code=500, message=str(exc)),
155
+ )
156
+ self._respond_json(HTTPStatus.OK, response_body.to_json())
157
+
158
+ def _handle_stream_call(self) -> None:
159
+ payload_result = self._read_json_payload()
160
+ if isinstance(payload_result, tuple):
161
+ self._respond_json(payload_result[0], payload_result[1])
162
+ return
163
+
164
+ payload = payload_result
165
+ try:
166
+ body = JsonRPCHttpRequestBody.from_json(payload)
167
+ except Exception as exc:
168
+ self._respond_json(
169
+ HTTPStatus.BAD_REQUEST,
170
+ _json_parse_error_response(JsonParseException(message=str(exc))),
171
+ )
172
+ return
173
+
174
+ self.send_response(HTTPStatus.OK)
175
+ self.send_header("Content-Type", "text/event-stream")
176
+ self.send_header("Cache-Control", "no-cache, no-store")
177
+ self.send_header("Connection", "close")
178
+ self.end_headers()
179
+
180
+ self._push_data(EventType.START, json.dumps({EventType.START: body.method}))
181
+
182
+ failed = False
183
+ try:
184
+ def send_event(event: str, data: dict[str, Any]) -> None:
185
+ nonlocal failed
186
+ if failed:
187
+ return
188
+ if event == EventType.DATA:
189
+ response_body = JsonRPCHttpResponseBody(result=data, id=body.id)
190
+ self._push_data(event, json.dumps(response_body.to_json()))
191
+ elif event == EventType.ERROR:
192
+ error = JsonRPCHttpResponseBodyError(
193
+ code=int(data.get("code", 500)),
194
+ message=str(data.get("message", data)),
195
+ )
196
+ response_body = JsonRPCHttpResponseBody(result={}, id=body.id, error=error)
197
+ self._push_data(event, json.dumps(response_body.to_json()))
198
+ failed = True
199
+
200
+ server.cli_tool.stream_call(body.method, body.params, send_event)
201
+ except (BrokenPipeError, ConnectionResetError):
202
+ return
203
+ except Exception as exc:
204
+ if not failed:
205
+ self._push_data(
206
+ EventType.ERROR,
207
+ json.dumps({"code": 500, "message": str(exc), "id": body.id}),
208
+ )
209
+ else:
210
+ if not failed:
211
+ self._push_data(EventType.DONE, json.dumps({EventType.DONE: body.method}))
212
+
213
+ def _handle_stop(self) -> None:
214
+ try:
215
+ server.cli_tool.dispose()
216
+ self._respond_json(HTTPStatus.OK, StatusInfo(status=StatusType.STOPPED).to_json())
217
+
218
+ def shutdown_async() -> None:
219
+ server.stop()
220
+
221
+ threading.Thread(target=shutdown_async, daemon=True).start()
222
+ except Exception as exc:
223
+ self._respond_json(
224
+ HTTPStatus.INTERNAL_SERVER_ERROR,
225
+ {"error": str(exc)},
226
+ )
227
+
228
+ def _read_json_payload(self) -> dict[str, Any] | tuple[int, dict[str, Any]]:
229
+ content_type = self.headers.get("Content-Type", "")
230
+ if "application/json" not in content_type:
231
+ return HTTPStatus.BAD_REQUEST, {"error": "Content-Type must be application/json"}
232
+
233
+ content_length = int(self.headers.get("Content-Length", "0"))
234
+ body = self.rfile.read(content_length).decode("utf-8") if content_length > 0 else ""
235
+
236
+ try:
237
+ payload = json.loads(body)
238
+ except json.JSONDecodeError as exc:
239
+ return (
240
+ HTTPStatus.BAD_REQUEST,
241
+ _json_parse_error_response(JsonParseException(message=str(exc))),
242
+ )
243
+
244
+ if not isinstance(payload, dict):
245
+ return (
246
+ HTTPStatus.BAD_REQUEST,
247
+ _json_parse_error_response(InvalidJsonPayloadException()),
248
+ )
249
+ return payload
250
+
251
+ def _path_without_query(self) -> str:
252
+ return urlparse(self.path).path
253
+
254
+ def _authorize(self) -> bool:
255
+ if not server.api_keys:
256
+ return True
257
+ authorization = self.headers.get("Authorization")
258
+ token_prefix = "Bearer "
259
+ if authorization is None or not authorization.startswith(token_prefix):
260
+ self._respond_text(HTTPStatus.UNAUTHORIZED, "Missing or malformed authorization header")
261
+ return False
262
+ token = authorization[len(token_prefix) :]
263
+ if token not in server.api_keys:
264
+ self._respond_text(HTTPStatus.UNAUTHORIZED, "Invalid authorization token")
265
+ return False
266
+ return True
267
+
268
+ def _respond_json(self, status: int, body: dict[str, Any]) -> None:
269
+ payload = json.dumps(body).encode("utf-8")
270
+ self.send_response(status)
271
+ self.send_header("Content-Type", "application/json")
272
+ self.send_header("Cache-Control", "no-cache")
273
+ self.send_header("Connection", "keep-alive")
274
+ self.send_header("Content-Length", str(len(payload)))
275
+ self.end_headers()
276
+ self.wfile.write(payload)
277
+
278
+ def _respond_text(self, status: int, body: str) -> None:
279
+ payload = body.encode("utf-8")
280
+ self.send_response(status)
281
+ self.send_header("Content-Type", "text/plain; charset=utf-8")
282
+ self.send_header("Content-Length", str(len(payload)))
283
+ self.end_headers()
284
+ self.wfile.write(payload)
285
+
286
+ def _push_data(self, event_type: str, data_string: str) -> None:
287
+ payload = f"event:{event_type}\\ndata:{data_string}\\n\\n".encode("utf-8")
288
+ self.wfile.write(payload)
289
+ self.wfile.flush()
290
+
291
+ self._server = ThreadingHTTPServer((self.host, self.port), Handler)
292
+ print(f"Start Server: http://{self.host}:{self.port}{self.prefix}")
293
+ self._server.serve_forever(poll_interval=0.1)
294
+
295
+ def stop(self) -> None:
296
+ server = self._server
297
+ if server is None:
298
+ return
299
+ server.shutdown()
300
+ server.server_close()
301
+ self._server = None
302
+
303
+
304
+ def _json_parse_error_response(exception: ServerException, id: str | None = None) -> dict[str, Any]:
305
+ return {
306
+ "jsonrpc": JSONRPC_VERSION,
307
+ "result": {},
308
+ "error": exception.to_json(),
309
+ "id": id,
310
+ }
@@ -0,0 +1,18 @@
1
+ from .exceptions import (
2
+ FunctionNotSupportedException,
3
+ InvalidArgumentsException,
4
+ JsonParserException,
5
+ ToolBreakException,
6
+ )
7
+ from .model import Event, EventType
8
+ from .tool import Tool
9
+
10
+ __all__ = [
11
+ "Tool",
12
+ "Event",
13
+ "EventType",
14
+ "FunctionNotSupportedException",
15
+ "InvalidArgumentsException",
16
+ "ToolBreakException",
17
+ "JsonParserException",
18
+ ]
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+
8
+ @dataclass
9
+ class FunctionNotSupportedException(Exception):
10
+ function_name: str
11
+ code: int = 405
12
+
13
+ @property
14
+ def message(self) -> str:
15
+ return f"Function Not Supported: {self.function_name}"
16
+
17
+ def to_json(self) -> dict[str, Any]:
18
+ return {"code": self.code, "message": self.message}
19
+
20
+ def __str__(self) -> str:
21
+ return json.dumps(self.to_json(), ensure_ascii=False)
22
+
23
+
24
+ @dataclass
25
+ class InvalidArgumentsException(Exception):
26
+ arguments: dict[str, Any] | None
27
+ code: int = 400
28
+
29
+ @property
30
+ def message(self) -> str:
31
+ return f"Invalid Arguments: {json.dumps(self.arguments, ensure_ascii=False)}"
32
+
33
+ def to_json(self) -> dict[str, Any]:
34
+ return {"code": self.code, "message": self.message}
35
+
36
+ def __str__(self) -> str:
37
+ return json.dumps(self.to_json(), ensure_ascii=False)
38
+
39
+
40
+ @dataclass
41
+ class ToolBreakException(Exception):
42
+ message: str | None
43
+ code: int = 500
44
+
45
+ def to_json(self) -> dict[str, Any]:
46
+ return {"code": self.code, "message": self.message}
47
+
48
+ def __str__(self) -> str:
49
+ return json.dumps(self.to_json(), ensure_ascii=False)
50
+
51
+
52
+ @dataclass
53
+ class JsonParserException(Exception):
54
+ code: int = 404
55
+ message: str = "Json Parser NOT implement"
56
+
57
+ def to_json(self) -> dict[str, Any]:
58
+ return {"code": self.code, "message": self.message}
59
+
60
+ def __str__(self) -> str:
61
+ return json.dumps(self.to_json(), ensure_ascii=False)
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+
7
+ class EventType:
8
+ START = "start"
9
+ DATA = "data"
10
+ ERROR = "error"
11
+ DONE = "done"
12
+
13
+
14
+ @dataclass
15
+ class Event:
16
+ name: str
17
+ data: dict[str, Any]
18
+
19
+ @classmethod
20
+ def from_json(cls, payload: dict[str, Any]) -> "Event":
21
+ return cls(name=str(payload["name"]), data=payload.get("data") or {})
22
+
23
+ def to_json(self) -> dict[str, Any]:
24
+ return {"name": self.name, "data": self.data}
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections.abc import Callable
5
+ from typing import Any
6
+
7
+ from ..model.opentool import OpenTool
8
+
9
+
10
+ class Tool(ABC):
11
+ def init(self) -> None:
12
+ return None
13
+
14
+ @abstractmethod
15
+ def call(self, name: str, arguments: dict[str, Any] | None) -> dict[str, Any]:
16
+ raise NotImplementedError
17
+
18
+ def stream_call(
19
+ self,
20
+ name: str,
21
+ arguments: dict[str, Any] | None,
22
+ on_event: Callable[[str, dict[str, Any]], None],
23
+ ) -> None:
24
+ return None
25
+
26
+ def load(self) -> OpenTool | None:
27
+ return None
28
+
29
+ def dispose(self) -> None:
30
+ return None
@@ -0,0 +1,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: opentool-python
3
+ Version: 0.1.0
4
+ Summary: Python SDK for OpenTool-compatible clients and servers
5
+ Author: OpenTool Hub
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/opentool-hub/opentool-python
8
+ Project-URL: Issues, https://github.com/opentool-hub/opentool-python/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: jsonschema[format]<5,>=4.22
15
+ Requires-Dist: referencing<1,>=0.30
16
+ Dynamic: license-file
17
+
18
+ # OpenTool SDK for Python
19
+
20
+ English | [中文](README-zh_CN.md)
21
+
22
+ OpenTool SDK for Python provides the core building blocks to host or consume OpenTool-compatible tools in Python: an HTTP/JSON-RPC server runtime, an HTTP client with unary + SSE streaming calls, and a JSON schema loader. This project is designed to align with [opentool-dart](https://github.com/opentool-hub/opentool-dart) so multi-language SDK behavior stays consistent.
23
+
24
+ ## Library Entrypoints
25
+
26
+ - `opentool_python.opentool_server` - server runtime, CLI argument helper, and Tool base class.
27
+ - `opentool_python.opentool_client` - HTTP/JSON-RPC client plus DTO/LLM models.
28
+ - `opentool_python.opentool_schema` - JSON specification models and loader.
29
+
30
+ ## Features
31
+
32
+ - `OpenToolServer` mounts `/opentool` JSON-RPC endpoints backed by your `Tool` implementation, with optional Bearer API-key auth.
33
+ - `OpenToolClient` supports version lookup, unary `call`, SSE `stream_call`, metadata `load`, and remote `stop`.
34
+ - `OpenToolJsonLoader` parses OpenTool JSON specs and resolves `$ref` schema references.
35
+ - End-to-end examples in `example/server.py` and `example/client.py` demonstrate CRUD + streaming usage.
36
+
37
+ ## Requirements
38
+
39
+ - Python `>=3.10`
40
+ - Schema validation uses `jsonschema` and `referencing`, installed with the SDK.
41
+
42
+ ## Installation
43
+
44
+ Install from PyPI:
45
+
46
+ ```sh
47
+ python -m pip install opentool-python
48
+ ```
49
+
50
+ Install as editable package in local development:
51
+
52
+ ```sh
53
+ pip install -e .
54
+ ```
55
+
56
+ Or install with source path from another project:
57
+
58
+ ```sh
59
+ pip install /absolute/path/to/opentool-python
60
+ ```
61
+
62
+ ## Quick Start
63
+
64
+ ### Implement a Tool
65
+
66
+ ```python
67
+ from pathlib import Path
68
+ from opentool_python.json_loader import OpenToolJsonLoader
69
+ from opentool_python.opentool_server import (
70
+ CliTool,
71
+ EventType,
72
+ FunctionNotSupportedException,
73
+ OpenTool,
74
+ )
75
+
76
+
77
+ class MockTool(CliTool):
78
+ def call(self, name, arguments):
79
+ if name == "count":
80
+ return {"count": 1}
81
+ return FunctionNotSupportedException(function_name=name).to_json()
82
+
83
+ def stream_call(self, name, arguments, on_event):
84
+ if name == "sequentiallyRead":
85
+ on_event(EventType.DATA, {"data": "Hello"})
86
+ else:
87
+ on_event(
88
+ EventType.ERROR,
89
+ FunctionNotSupportedException(function_name=name).to_json(),
90
+ )
91
+
92
+ def load(self) -> OpenTool | None:
93
+ return OpenToolJsonLoader().load_from_file(
94
+ str(Path("example/mock_tool.json"))
95
+ )
96
+ ```
97
+
98
+ ### Launch the server
99
+
100
+ ```sh
101
+ PYTHONPATH=src python example/server.py
102
+ ```
103
+
104
+ `CliArguments` can pass built-in and custom CLI switches into your `CliTool.init_args`, so you can preload state before handlers accept requests.
105
+
106
+ ### Call the tool from Python
107
+
108
+ ```python
109
+ from opentool_python.opentool_client import FunctionCall, HostType, OpenToolClient
110
+
111
+ client = OpenToolClient(
112
+ tool_host=HostType.LOCALHOST,
113
+ tool_port=17002,
114
+ tool_api_key="your-key",
115
+ )
116
+
117
+ print(client.version().to_json())
118
+ print(client.call(FunctionCall(id="call-0", name="count", arguments={})).to_json())
119
+
120
+ client.stream_call(
121
+ FunctionCall(id="call-1", name="sequentiallyRead", arguments={}),
122
+ lambda event, tool_return: print(event, tool_return.result),
123
+ )
124
+
125
+ print(client.load().to_json())
126
+ print(client.stop().to_json())
127
+ ```
128
+
129
+ ## CLI and HTTP Reference
130
+
131
+ - Base path defaults to `/opentool`.
132
+ - Endpoints:
133
+ - `GET /version`
134
+ - `POST /call`
135
+ - `POST /streamCall`
136
+ - `GET /load` returns HTTP 404 with code `TOOL_NOT_FOUND` when metadata is unavailable.
137
+ - `POST /stop`
138
+ - Built-in CLI flags (via `CliArguments`):
139
+ - `--opentoolServerTag`
140
+ - `--opentoolServerHost`
141
+ - `--opentoolServerPort`
142
+ - repeatable `--opentoolServerApiKeys`
143
+ - Streaming protocol is Server-Sent Events with event names `start`, `data`, `error`, and `done`.
144
+
145
+ ## JSON Specification Loader
146
+
147
+ - Import `opentool_python.opentool_schema` if you only need spec models and parsing.
148
+ - `OpenToolJsonLoader().load_from_file(path)` loads local files and resolves `$ref` entries under `#/schemas/*`.
149
+ - `OpenToolJsonLoader().load(json_string)` loads from raw JSON strings.
150
+ - Specification reference: [OpenTool Spec](https://github.com/opentool-hub/opentool-spec).
151
+
152
+ Schemas default to JSON Schema Draft 2020-12. A recognized `$schema` selects its declared draft. `Schema.to_json()` returns a dictionary or boolean and preserves references, annotations, and extension keywords. Named `#/schemas/...` references are scoped to each OpenTool document, including documents loaded over HTTP.
153
+
154
+ ```python
155
+ from opentool_python.opentool_schema import Schema
156
+
157
+ schema = Schema.from_json({"type": "string", "minLength": 3})
158
+ result = schema.validate("ab")
159
+ print(result.is_valid, result.errors)
160
+ ```
161
+
162
+ Use `validate_formats=True` to assert formats. To supply external schemas, pass a synchronous `schema_reference_resolver` callback to `OpenTool.from_json`, `OpenToolJsonLoader`, or `OpenToolClient`. The callback receives the resource URI and returns an object, a boolean, or `None` if unavailable.
163
+
164
+ ## Examples and Development
165
+
166
+ - Example files:
167
+ - `example/server.py`
168
+ - `example/client.py`
169
+ - `example/mock_tool.json`
170
+ - Common local workflow:
171
+
172
+ ```sh
173
+ python -m compileall src example
174
+ PYTHONPATH=src python example/server.py
175
+ PYTHONPATH=src python example/client.py
176
+ ```
177
+
178
+ ## Compatibility Note
179
+
180
+ This Python SDK is currently a baseline implementation focused on API shape and protocol compatibility with `opentool-dart`.
@@ -0,0 +1,34 @@
1
+ opentool_python/__init__.py,sha256=o6No_JnyXS_uji6WUfIAQL5fl5biCYcudoXh4EyewYw,1528
2
+ opentool_python/dto.py,sha256=bfSkUaSDmckzPfLCjW6gFggzUx-GITVY4e-6h0lHQQ0,3547
3
+ opentool_python/json_loader.py,sha256=7OWRgYSK-gX74HgRYi9iOJ0bgnn_6dYeT6Wqa9BjHZ8,827
4
+ opentool_python/opentool_client.py,sha256=tpMBNmU-zY0TNSVF_D3BECFX7qzVlH-tvJghuXtT3C0,683
5
+ opentool_python/opentool_schema.py,sha256=uWlwSAPNGt7AbBtKMgAzic85LRZ9ezqKmGQaJ0Cs9Rw,742
6
+ opentool_python/opentool_server.py,sha256=2QAos7OsOxBsti9Wf2SDrkcZ6Rag23r33Gf7ZCjNh4c,478
7
+ opentool_python/client/__init__.py,sha256=bJKMc95Eza--_rbFmtDC6aTq9Amvtl1hLZzECk1K3sU,465
8
+ opentool_python/client/exceptions.py,sha256=0VfSsbfvZ-jaFR9R6CT8go-68TOptm-nqjgGq_Rzn1Y,1160
9
+ opentool_python/client/opentool_client.py,sha256=etuAbifZGmN_1YBqvDyVwe37XctDDRnhzJVNzLre4Wc,8885
10
+ opentool_python/llm/__init__.py,sha256=n0pyI255rPgtim_Tu5Ale0uOCSBa6R6UrSX1TZTWfSw,86
11
+ opentool_python/llm/model.py,sha256=KI45fyu2rVzkr2sfA_6wWd3iUFkgWmz-jBDxYFdtlSA,852
12
+ opentool_python/model/__init__.py,sha256=8KsoqayQiE13rb9mwL5cltMtwAb8swmyB9CGFZExX4I,577
13
+ opentool_python/model/data_type.py,sha256=SQgw0ACF6oYB-NRGQ4SPGBnmVMPfdl6u3t7dYlsoZwA,174
14
+ opentool_python/model/function_model.py,sha256=L3gY-d3FZ3sd_hTm0lEKSllidfKY_Ieglmy9J68ku1Q,2411
15
+ opentool_python/model/info.py,sha256=0X_DdH7ObPAStsLDUOboEQDfKSmofqh0O6r3dz8lAtQ,659
16
+ opentool_python/model/opentool.py,sha256=VK2Ddb1gF_W5NDrsjX5s7hrjtJA0G86dPOuuBkl1gHw,2121
17
+ opentool_python/model/parameter.py,sha256=hS1Gi3_DOYimGQgbpgEYUS8wWvca9ylQmhC3sSSP05Y,1141
18
+ opentool_python/model/return_model.py,sha256=HJ5xNUCnL98vOb18OlYI5Glx7VUBSzT-skdeB-TfjIo,799
19
+ opentool_python/model/schema.py,sha256=iWh4XpRRpBLbCRRXeN3USCldzaj2io80K_sMIYCG94g,9525
20
+ opentool_python/model/server.py,sha256=ghBNNa8rtN16N5ojJW6yqaJ4k1JpKrLS7SGyZ8DN99k,546
21
+ opentool_python/server/__init__.py,sha256=1sMtmoPqqNyR2LLG4VNql65D-AxGiiBgDCnSeyg-JLA,189
22
+ opentool_python/server/cli_arguments.py,sha256=FsM_bzjI0Z-jfJF5oGurjPz_qacw18316r1-1_7VJeU,1356
23
+ opentool_python/server/cli_tool.py,sha256=veoJLpA0CXDuVwXSTL0QqeREOYCJCP-r_HSXQMsd4po,211
24
+ opentool_python/server/exceptions.py,sha256=pcYmg7YmLapHIUvq1cvbgj-b70unOQAqyUJ-8J96ACg,719
25
+ opentool_python/server/opentool_server.py,sha256=Oi5SiueRwTB8WjLjZc0awFfWkV6HonJyB5bfz3pyKdM,12698
26
+ opentool_python/tool/__init__.py,sha256=jJV_WE3TjZP9AzeB7EPC2s1l6i4BXlWqclMIvp1sU_c,382
27
+ opentool_python/tool/exceptions.py,sha256=LsiNkkq6nZhlDE6jhJJzN96TVOSBwuqgY9OvOQCqb9Q,1554
28
+ opentool_python/tool/model.py,sha256=kkg0BCCPiBiubgjG-zzIysmuNUnK2O0bzDJvtcHRcpA,505
29
+ opentool_python/tool/tool.py,sha256=Eym4TGbVuzxJQJqqaeQU7L41n55bTUNdKRpR1wG6UKg,677
30
+ opentool_python-0.1.0.dist-info/licenses/LICENSE,sha256=gljelJoITV4eS5oP7Hbp9x4-JmznEJC0Le7Y5j4ecxM,1069
31
+ opentool_python-0.1.0.dist-info/METADATA,sha256=WSwYa9F_iuOW-MqyYGRC77MW4nh3JpS6QFOheh2UnV4,6016
32
+ opentool_python-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
33
+ opentool_python-0.1.0.dist-info/top_level.txt,sha256=IM2y5SZDYQ85hRxU3V_jiBOTnWzqwEV-8mXuO1ioVKM,16
34
+ opentool_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenTool Hub
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.