sectr 0.0.4__tar.gz
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.
- sectr-0.0.4/PKG-INFO +18 -0
- sectr-0.0.4/README.md +0 -0
- sectr-0.0.4/pyproject.toml +39 -0
- sectr-0.0.4/pyproject.toml.orig +40 -0
- sectr-0.0.4/src/sectr/__init__.py +53 -0
- sectr-0.0.4/src/sectr/adapters/__init__.py +12 -0
- sectr-0.0.4/src/sectr/adapters/claude_code.py +45 -0
- sectr-0.0.4/src/sectr/adapters/langgraph.py +80 -0
- sectr-0.0.4/src/sectr/adapters/openai_agents.py +399 -0
- sectr-0.0.4/src/sectr/app.py +79 -0
- sectr-0.0.4/src/sectr/context.py +285 -0
- sectr-0.0.4/src/sectr/drive.py +86 -0
- sectr-0.0.4/src/sectr/events.py +177 -0
- sectr-0.0.4/src/sectr/inspect.py +143 -0
- sectr-0.0.4/src/sectr/py.typed +0 -0
- sectr-0.0.4/src/sectr/server.py +129 -0
- sectr-0.0.4/src/sectr/tools.py +199 -0
sectr-0.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: sectr
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Sectr Python SDK: framework-agnostic agent deployment (decorators + runner-contract adapter)
|
|
5
|
+
Author: Donald Pinckney
|
|
6
|
+
Author-email: Donald Pinckney <donald_pinckney@icloud.com>
|
|
7
|
+
Requires-Dist: fastapi
|
|
8
|
+
Requires-Dist: uvicorn
|
|
9
|
+
Requires-Dist: claude-code-sdk ; extra == 'claude-code'
|
|
10
|
+
Requires-Dist: fastmcp ; extra == 'claude-code'
|
|
11
|
+
Requires-Dist: langgraph ; extra == 'langgraph'
|
|
12
|
+
Requires-Dist: openai-agents>=0.2,<1 ; extra == 'openai-agents'
|
|
13
|
+
Requires-Python: >=3.12
|
|
14
|
+
Provides-Extra: claude-code
|
|
15
|
+
Provides-Extra: langgraph
|
|
16
|
+
Provides-Extra: openai-agents
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
sectr-0.0.4/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sectr"
|
|
3
|
+
version = "0.0.4"
|
|
4
|
+
description = "Sectr Python SDK: framework-agnostic agent deployment (decorators + runner-contract adapter)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"fastapi",
|
|
9
|
+
"uvicorn",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[[project.authors]]
|
|
13
|
+
name = "Donald Pinckney"
|
|
14
|
+
email = "donald_pinckney@icloud.com"
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
openai-agents = ["openai-agents>=0.2,<1"]
|
|
18
|
+
langgraph = ["langgraph"]
|
|
19
|
+
claude-code = [
|
|
20
|
+
"claude-code-sdk",
|
|
21
|
+
"fastmcp",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"pytest>=8",
|
|
27
|
+
"httpx>=0.27",
|
|
28
|
+
"pytest-asyncio>=0.24",
|
|
29
|
+
"jsonschema>=4.23",
|
|
30
|
+
"rfc3339-validator>=0.1.4",
|
|
31
|
+
"mypy>=2.3.1",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.pytest.ini_options]
|
|
35
|
+
asyncio_mode = "auto"
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["uv_build>=0.9.26,<0.10.0"]
|
|
39
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sectr"
|
|
3
|
+
version = "0.0.4"
|
|
4
|
+
description = "Sectr Python SDK: framework-agnostic agent deployment (decorators + runner-contract adapter)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Donald Pinckney", email = "donald_pinckney@icloud.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
# The FastAPI runner-contract adapter (sectr.server) — base dep (decided
|
|
12
|
+
# 2026-09-10): every HOSTED variant needs it, so it belongs to the SDK's
|
|
13
|
+
# core; the extras exist only for framework imports the core doesn't
|
|
14
|
+
# require.
|
|
15
|
+
"fastapi",
|
|
16
|
+
"uvicorn",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.optional-dependencies]
|
|
20
|
+
# Framework adapters (each extra mirrors an examples/ variant).
|
|
21
|
+
openai-agents = ["openai-agents>=0.2,<1"]
|
|
22
|
+
langgraph = ["langgraph"]
|
|
23
|
+
claude-code = ["claude-code-sdk", "fastmcp"]
|
|
24
|
+
|
|
25
|
+
[dependency-groups]
|
|
26
|
+
dev = [
|
|
27
|
+
"pytest>=8",
|
|
28
|
+
"httpx>=0.27",
|
|
29
|
+
"pytest-asyncio>=0.24",
|
|
30
|
+
"jsonschema>=4.23",
|
|
31
|
+
"rfc3339-validator>=0.1.4",
|
|
32
|
+
"mypy>=2.3.1",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
asyncio_mode = "auto"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["uv_build>=0.9.26,<0.10.0"]
|
|
40
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Sectr Python SDK: framework-agnostic agent deployment.
|
|
2
|
+
|
|
3
|
+
Core (framework-free): `AgentApp` (route registry), `SessionContext` (the
|
|
4
|
+
handler argument), `sectr.events` (typed events mirroring
|
|
5
|
+
schema/event.schema.json), `sectr.tools` (`@tool` registry + approval
|
|
6
|
+
gating). Handlers are async generators of events; the FastAPI server
|
|
7
|
+
adapter (`sectr.server`) speaks the runner invoke contract, and framework
|
|
8
|
+
adapters (`sectr.adapters.*`) wrap native framework objects into handlers.
|
|
9
|
+
|
|
10
|
+
Contracts: `../schema/` is the single source of truth — see `schema/PLAN.md`
|
|
11
|
+
and `sdk-python/PLAN.md`.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .app import AgentApp, Handler, Route
|
|
15
|
+
from .context import SessionContext
|
|
16
|
+
from .events import (
|
|
17
|
+
Envelope,
|
|
18
|
+
Error,
|
|
19
|
+
MessageDelta,
|
|
20
|
+
MessageEnd,
|
|
21
|
+
MessageStart,
|
|
22
|
+
NoMoreActions,
|
|
23
|
+
RunnerEvent,
|
|
24
|
+
TranscriptCompacted,
|
|
25
|
+
ToolApprovalRequired,
|
|
26
|
+
ToolCall,
|
|
27
|
+
ToolResult,
|
|
28
|
+
NoMoreActions,
|
|
29
|
+
)
|
|
30
|
+
from .tools import ApprovalRequired, Tool, tool
|
|
31
|
+
|
|
32
|
+
__version__ = "0.1.0"
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"AgentApp",
|
|
36
|
+
"ApprovalRequired",
|
|
37
|
+
"Envelope",
|
|
38
|
+
"Error",
|
|
39
|
+
"Handler",
|
|
40
|
+
"MessageDelta",
|
|
41
|
+
"MessageEnd",
|
|
42
|
+
"MessageStart",
|
|
43
|
+
"Route",
|
|
44
|
+
"RunnerEvent",
|
|
45
|
+
"SessionContext",
|
|
46
|
+
"Tool",
|
|
47
|
+
"ToolApprovalRequired",
|
|
48
|
+
"ToolCall",
|
|
49
|
+
"ToolResult",
|
|
50
|
+
"NoMoreActions",
|
|
51
|
+
"TranscriptCompacted",
|
|
52
|
+
"tool",
|
|
53
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Framework adapters (optional extras). Each adapter is a handler factory:
|
|
2
|
+
it takes the framework's native object and returns a
|
|
3
|
+
`(SessionContext) -> AsyncIterator[RunnerEvent]` handler, so registration is
|
|
4
|
+
one line per app:
|
|
5
|
+
|
|
6
|
+
app.add_route("/chat", openai_agents(agent))
|
|
7
|
+
app.add_route("/chat", langgraph(graph))
|
|
8
|
+
app.add_route("/chat", claude_code(options))
|
|
9
|
+
|
|
10
|
+
Mapping details (pause/resume semantics, transcript reconstruction) are
|
|
11
|
+
documented per module and pinned by the variant sketches in `examples/`.
|
|
12
|
+
"""
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Claude Code adapter (`sectr[claude-code]` extra) — the near-black-box
|
|
2
|
+
case: the CLI owns its loop, tools, and permissions.
|
|
3
|
+
|
|
4
|
+
`claude_code(**options)` returns a handler that (see
|
|
5
|
+
`examples/claude-code/main.py`; session-memory mapping decided in
|
|
6
|
+
`examples/claude-code/PLAN.md`):
|
|
7
|
+
|
|
8
|
+
- runs the `claude` CLI (via claude-code-sdk) with `--resume` keyed to our
|
|
9
|
+
session_id (option (b): the CLI's workspace state persists across turns;
|
|
10
|
+
the journal stays authoritative for approvals, SSE, audit, transcript);
|
|
11
|
+
- assistant text → MESSAGE_*; ToolUseBlock → TOOL_CALL; ToolResultBlock →
|
|
12
|
+
TOOL_RESULT; result message → NO_MORE_ACTIONS (turn over);
|
|
13
|
+
- pause: the CLI's `can_use_tool` permission callback fires → the adapter
|
|
14
|
+
captures (tool_name, input), terminates the subprocess, journals
|
|
15
|
+
TOOL_APPROVAL_REQUIRED as the final frame (turn suspended, no sentinel);
|
|
16
|
+
- resume: `--resume` the same session; when the CLI re-issues the gated
|
|
17
|
+
tool and asks permission, the adapter answers from
|
|
18
|
+
`ctx.decision` (allow/deny). At most ONE pending approval per
|
|
19
|
+
session is possible by the turn model.
|
|
20
|
+
|
|
21
|
+
CI note: the `claude` binary is a shim script emitting canned JSONL
|
|
22
|
+
(examples/claude-code/tests/shim_claude.py). Implementation pending.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from collections.abc import AsyncIterator
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from ..app import Handler
|
|
31
|
+
from ..context import SessionContext
|
|
32
|
+
from ..events import RunnerEvent
|
|
33
|
+
|
|
34
|
+
__all__ = ["claude_code"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def claude_code(**options: Any) -> Handler:
|
|
38
|
+
"""Handler factory wrapping claude-code-sdk options. Implementation
|
|
39
|
+
pending."""
|
|
40
|
+
|
|
41
|
+
async def handler(ctx: SessionContext) -> AsyncIterator[RunnerEvent]:
|
|
42
|
+
raise NotImplementedError("sectr.adapters.claude_code")
|
|
43
|
+
yield # pragma: no cover - makes this an async generator
|
|
44
|
+
|
|
45
|
+
return handler
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""LangGraph adapter (`sectr[langgraph]` extra).
|
|
2
|
+
|
|
3
|
+
`langgraph(graph)` returns a handler that (see `examples/langgraph/main.py`):
|
|
4
|
+
|
|
5
|
+
- normal turn: transcript → `{"messages": [...]}` state; `graph.astream`
|
|
6
|
+
(stream_mode="messages") → MESSAGE_* / TOOL_CALL / TOOL_RESULT;
|
|
7
|
+
stream end → NO_MORE_ACTIONS (turn over). No LangGraph checkpointer — the journal
|
|
8
|
+
is the checkpoint.
|
|
9
|
+
- pause: the sectr-gated tool raises `ApprovalRequired` inside the tool
|
|
10
|
+
node; the adapter catches it escaping `astream` → journaled
|
|
11
|
+
TOOL_APPROVAL_REQUIRED as the final frame (turn suspended, no sentinel).
|
|
12
|
+
- resume: rebuild messages from the transcript, execute the approved tool
|
|
13
|
+
via the sectr tool wrapper (or denial), append the ToolMessage, continue.
|
|
14
|
+
|
|
15
|
+
Post-MVP option (recorded in sdk-python/PLAN.md): a session-scoped
|
|
16
|
+
checkpointer workspace enables idiomatic `interrupt()` /
|
|
17
|
+
`Command(resume=decision)` tool bodies. Implementation pending.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from collections.abc import AsyncIterator
|
|
23
|
+
from typing import Any, cast
|
|
24
|
+
|
|
25
|
+
from ..app import Handler
|
|
26
|
+
from ..context import SessionContext
|
|
27
|
+
from ..events import RunnerEvent
|
|
28
|
+
from ..tools import ApprovalRequired
|
|
29
|
+
|
|
30
|
+
__all__ = ["langgraph", "as_langgraph_tool"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def langgraph(graph: Any) -> Handler:
|
|
34
|
+
"""Handler factory wrapping a compiled LangGraph. Implementation pending."""
|
|
35
|
+
|
|
36
|
+
async def handler(ctx: SessionContext) -> AsyncIterator[RunnerEvent]:
|
|
37
|
+
raise NotImplementedError("sectr.adapters.langgraph")
|
|
38
|
+
yield # pragma: no cover - makes this an async generator
|
|
39
|
+
|
|
40
|
+
return handler
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def as_langgraph_tool(t: Any) -> Any:
|
|
44
|
+
"""Convert a sectr `Tool` into a LangGraph/langchain-compatible tool,
|
|
45
|
+
preserving approval gating: the coroutine raises `ApprovalRequired`
|
|
46
|
+
inside the tool node when gated (the adapter catches it escaping
|
|
47
|
+
`astream` and ends the turn). Skeleton implementation — schema inferred
|
|
48
|
+
from the original function signature; golden tests arrive with the
|
|
49
|
+
adapter's functional round."""
|
|
50
|
+
import inspect
|
|
51
|
+
|
|
52
|
+
from langchain_core.tools import StructuredTool
|
|
53
|
+
from pydantic import create_model
|
|
54
|
+
|
|
55
|
+
if t.description is None:
|
|
56
|
+
raise ValueError(f"tool {t.name!r} needs a docstring (used as the LLM description)")
|
|
57
|
+
|
|
58
|
+
params = inspect.signature(t.fn).parameters
|
|
59
|
+
fields = {
|
|
60
|
+
pname: (
|
|
61
|
+
p.annotation if p.annotation is not inspect.Parameter.empty else str,
|
|
62
|
+
... if p.default is inspect.Parameter.empty else p.default,
|
|
63
|
+
)
|
|
64
|
+
for pname, p in params.items()
|
|
65
|
+
}
|
|
66
|
+
# pydantic's create_model overloads don't cleanly accept a heterogeneous
|
|
67
|
+
# kwargs dict — a cast is honest here; schema correctness is golden-tested
|
|
68
|
+
# with the adapter's functional round.
|
|
69
|
+
args_schema = create_model(f"{t.name}_args", **cast(Any, fields))
|
|
70
|
+
|
|
71
|
+
from ..tools import is_gated
|
|
72
|
+
|
|
73
|
+
async def _run(**kwargs: Any) -> Any:
|
|
74
|
+
if is_gated(t.name):
|
|
75
|
+
raise ApprovalRequired(t.name, kwargs)
|
|
76
|
+
return await t(**kwargs)
|
|
77
|
+
|
|
78
|
+
return StructuredTool.from_function(
|
|
79
|
+
coroutine=_run, name=t.name, description=t.description, args_schema=args_schema
|
|
80
|
+
)
|