toolstream 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.
toolstream/_tools.py ADDED
@@ -0,0 +1,109 @@
1
+ """Tool registration system for toolstream.
2
+
3
+ Provides the @tool decorator for marking functions as LLM-callable tools,
4
+ and collect_tools() for discovering decorated functions across modules.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import inspect
10
+ from dataclasses import dataclass
11
+ from typing import Callable
12
+
13
+ from ._schema import _generate_schema
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class Tool:
18
+ """Metadata for a registered tool function."""
19
+
20
+ name: str
21
+ description: str
22
+ input_schema: dict
23
+ handler: Callable
24
+ inject: list[str]
25
+
26
+
27
+ def tool(
28
+ *,
29
+ name: str | None = None,
30
+ description: str | None = None,
31
+ inject: list[str] | None = None,
32
+ ) -> Callable:
33
+ """Decorator factory that attaches Tool metadata to a function.
34
+
35
+ Usage:
36
+ @tool()
37
+ def my_func(x: int) -> str:
38
+ ...
39
+
40
+ @tool(inject=["ctx"])
41
+ def my_func(ctx, x: int) -> str:
42
+ ...
43
+
44
+ The decorated function is returned unchanged -- this decorator only
45
+ attaches a _tool attribute, it does not wrap the function.
46
+ """
47
+ inject_list = inject or []
48
+
49
+ def decorator(fn: Callable) -> Callable:
50
+ # Validate that every injected param exists in the signature
51
+ sig = inspect.signature(fn)
52
+ for param_name in inject_list:
53
+ if param_name not in sig.parameters:
54
+ raise ValueError(
55
+ f"inject parameter {param_name!r} not found in "
56
+ f"signature of {fn.__name__}(). "
57
+ f"Available: {list(sig.parameters.keys())}"
58
+ )
59
+
60
+ # Resolve name
61
+ tool_name = name if name is not None else fn.__name__
62
+
63
+ # Resolve description from first line of docstring
64
+ if description is not None:
65
+ tool_description = description
66
+ elif fn.__doc__:
67
+ tool_description = fn.__doc__.strip().split("\n")[0].strip()
68
+ else:
69
+ tool_description = ""
70
+
71
+ # Generate input schema, excluding injected params
72
+ input_schema = _generate_schema(fn, inject=set(inject_list))
73
+
74
+ fn._tool = Tool(
75
+ name=tool_name,
76
+ description=tool_description,
77
+ input_schema=input_schema,
78
+ handler=fn,
79
+ inject=inject_list,
80
+ )
81
+
82
+ return fn
83
+
84
+ return decorator
85
+
86
+
87
+ def collect_tools(*modules) -> list[Tool]:
88
+ """Discover all @tool-decorated functions across the given modules.
89
+
90
+ Raises ValueError if two tools share the same name.
91
+ """
92
+ tools: list[Tool] = []
93
+ seen_names: dict[str, str] = {} # name -> module name for error messages
94
+
95
+ for module in modules:
96
+ module_name = getattr(module, "__name__", repr(module))
97
+ for attr_name in dir(module):
98
+ obj = getattr(module, attr_name)
99
+ if callable(obj) and hasattr(obj, "_tool"):
100
+ t: Tool = obj._tool
101
+ if t.name in seen_names:
102
+ raise ValueError(
103
+ f"Tool name collision: {t.name!r} found in both "
104
+ f"{seen_names[t.name]} and {module_name}"
105
+ )
106
+ seen_names[t.name] = module_name
107
+ tools.append(t)
108
+
109
+ return tools
toolstream/config.py ADDED
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from ._agent import AgentSandbox
8
+ from ._context import ToolContext
9
+ from ._tools import Tool
10
+
11
+
12
+ @dataclass
13
+ class SessionConfig:
14
+ model: str
15
+ api_key: str
16
+ base_url: str
17
+ system_prompt: str
18
+ cwd: str | None = None
19
+ env: dict[str, str] = field(default_factory=dict)
20
+ agent: str | None = None
21
+ tools: list[Tool] | None = None
22
+ tool_context: ToolContext | None = None
23
+ tool_env: dict[str, str] = field(default_factory=dict)
24
+ max_completion_tokens: int = 16384
25
+ sandbox: AgentSandbox | None = None
26
+ metadata: dict[str, str] | None = None
toolstream/events.py ADDED
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class StepStart:
8
+ session_id: str
9
+ message_id: str
10
+ timestamp: int
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class Text:
15
+ session_id: str
16
+ message_id: str
17
+ text: str
18
+ timestamp: int
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class ToolUse:
23
+ session_id: str
24
+ message_id: str
25
+ tool: str
26
+ call_id: str
27
+ status: str # "completed", "error", etc.
28
+ input: dict
29
+ output: str
30
+ title: str
31
+ timestamp: int
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class StepFinish:
36
+ session_id: str
37
+ message_id: str
38
+ reason: str # "stop", "tool-calls"
39
+ input_tokens: int
40
+ output_tokens: int
41
+ reasoning_tokens: int
42
+ cache_read_tokens: int
43
+ cache_write_tokens: int
44
+ cost: float
45
+ timestamp: int
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class Error:
50
+ session_id: str
51
+ name: str
52
+ message: str
53
+ data: dict
54
+ timestamp: int
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class Result:
59
+ session_id: str
60
+ total_input_tokens: int
61
+ total_output_tokens: int
62
+ total_cost: float
63
+ steps: int
toolstream/py.typed ADDED
File without changes
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: toolstream
3
+ Version: 0.1.0
4
+ Summary: Typed streaming SDK for LLM tool-calling loops
5
+ Keywords: rlsbl
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: httpx
@@ -0,0 +1,16 @@
1
+ toolstream/__init__.py,sha256=Lt50_6y_ZOnxWSSQEsoRPbbfBau_ZPAKvR5MqAxrABU,915
2
+ toolstream/_agent.py,sha256=l3FipLcg_0QszIcekBrq-ORzGbnl2vBcu3fgGOZE7Ls,6550
3
+ toolstream/_builtin_tools.py,sha256=tGYC0iteb1_F_X4iNW20frtucwkBwGCyP7PREk1FCTA,4107
4
+ toolstream/_context.py,sha256=KMFlPW5xqHDLAyps_UfnZOhHA_3lNsWT4UfRxkhNUts,366
5
+ toolstream/_direct.py,sha256=qBCfBjCVLyeFUiCUpo4UP62Jagn453x70kR-r1ny6ho,10300
6
+ toolstream/_invoke.py,sha256=pmvprfovXhzWOWVh0j7ArvAU122HESEmEWohCDT2LW4,3912
7
+ toolstream/_protocol.py,sha256=-hqqJX-xCg7bqQXMCHrjZ4FnWGKsiQ4QdLFQKdXBBII,2514
8
+ toolstream/_schema.py,sha256=qG2zLGXGFU5IYfF6YJRoV-TPEkjjJFQ016DWmNFBuxo,8382
9
+ toolstream/_session.py,sha256=ka5EKQloyYvnQO9_UarVWWWK5HY1brx_f7LOlRDb__0,5724
10
+ toolstream/_tools.py,sha256=nmNKs9l9FwY6v0FTXB1J2gd841pYQafOExbNLADOp2A,3201
11
+ toolstream/config.py,sha256=Gmt_0BTaymbdtQAZaYM_C0LtN9J1l44e4VIMFUE1yB0,711
12
+ toolstream/events.py,sha256=1ECr9gKTBX5JW7XfvuKFNkMABuAk3N9a2yMi3TdNSxQ,1070
13
+ toolstream/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ toolstream-0.1.0.dist-info/METADATA,sha256=zcA1Lg05a1mZhCva9-AoYUvOinJjnbDsvC82nL2PVI4,171
15
+ toolstream-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
16
+ toolstream-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any