nighthawk-python 0.11.0__py3-none-any.whl → 0.12.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.
- nighthawk/backends/base.py +29 -3
- nighthawk/backends/claude_code_cli.py +1 -2
- nighthawk/backends/claude_code_sdk.py +1 -2
- nighthawk/backends/codex.py +1 -2
- nighthawk/backends/mcp_boundary.py +2 -2
- nighthawk/backends/mcp_server.py +28 -16
- nighthawk/runtime/step_executor.py +1 -1
- {nighthawk_python-0.11.0.dist-info → nighthawk_python-0.12.0.dist-info}/METADATA +9 -9
- {nighthawk_python-0.11.0.dist-info → nighthawk_python-0.12.0.dist-info}/RECORD +11 -11
- {nighthawk_python-0.11.0.dist-info → nighthawk_python-0.12.0.dist-info}/WHEEL +1 -1
- {nighthawk_python-0.11.0.dist-info → nighthawk_python-0.12.0.dist-info}/licenses/LICENSE +0 -0
nighthawk/backends/base.py
CHANGED
|
@@ -9,6 +9,7 @@ from pydantic import BaseModel, ConfigDict, field_validator
|
|
|
9
9
|
from pydantic_ai.exceptions import UnexpectedModelBehavior, UserError
|
|
10
10
|
from pydantic_ai.messages import (
|
|
11
11
|
CachePoint,
|
|
12
|
+
InstructionPart,
|
|
12
13
|
ModelMessage,
|
|
13
14
|
ModelRequest,
|
|
14
15
|
RetryPromptPart,
|
|
@@ -73,6 +74,31 @@ def _collect_system_prompt_text(model_request: ModelRequest) -> str:
|
|
|
73
74
|
return "\n\n".join(parts)
|
|
74
75
|
|
|
75
76
|
|
|
77
|
+
def _collect_instruction_text(
|
|
78
|
+
messages: list[ModelMessage],
|
|
79
|
+
model_request_parameters: ModelRequestParameters,
|
|
80
|
+
) -> str | None:
|
|
81
|
+
instruction_parts = model_request_parameters.instruction_parts
|
|
82
|
+
if instruction_parts is not None:
|
|
83
|
+
return InstructionPart.join(instruction_parts)
|
|
84
|
+
|
|
85
|
+
model_request_list = [message for message in reversed(messages) if isinstance(message, ModelRequest)]
|
|
86
|
+
if not model_request_list:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
most_recent_model_request = model_request_list[0]
|
|
90
|
+
if most_recent_model_request.instructions is not None:
|
|
91
|
+
return most_recent_model_request.instructions
|
|
92
|
+
|
|
93
|
+
if len(model_request_list) < 2:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
if all(isinstance(part, ToolReturnPart | RetryPromptPart) for part in most_recent_model_request.parts):
|
|
97
|
+
return model_request_list[1].instructions
|
|
98
|
+
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
76
102
|
def _resolve_current_tool_result_max_tokens() -> int:
|
|
77
103
|
return resolve_current_tool_result_rendering_policy().tool_result_max_tokens
|
|
78
104
|
|
|
@@ -152,8 +178,8 @@ class BackendModelBase(Model):
|
|
|
152
178
|
messages: list[ModelMessage],
|
|
153
179
|
model_request_parameters: ModelRequestParameters,
|
|
154
180
|
) -> PreparedRequestParts:
|
|
155
|
-
if model_request_parameters.
|
|
156
|
-
raise UserError(f"{self.backend_label} does not support
|
|
181
|
+
if model_request_parameters.native_tools:
|
|
182
|
+
raise UserError(f"{self.backend_label} does not support native tools")
|
|
157
183
|
|
|
158
184
|
if model_request_parameters.allow_image_output:
|
|
159
185
|
raise UserError(f"{self.backend_label} does not support image output")
|
|
@@ -162,7 +188,7 @@ class BackendModelBase(Model):
|
|
|
162
188
|
|
|
163
189
|
system_prompt_text = _collect_system_prompt_text(model_request)
|
|
164
190
|
|
|
165
|
-
instructions =
|
|
191
|
+
instructions = _collect_instruction_text(messages, model_request_parameters)
|
|
166
192
|
if instructions:
|
|
167
193
|
system_prompt_text = "\n\n".join([system_prompt_text, instructions]) if system_prompt_text else instructions
|
|
168
194
|
|
|
@@ -8,7 +8,6 @@ import tempfile
|
|
|
8
8
|
from typing import IO, TypedDict
|
|
9
9
|
|
|
10
10
|
from pydantic import field_validator
|
|
11
|
-
from pydantic_ai.builtin_tools import AbstractBuiltinTool
|
|
12
11
|
from pydantic_ai.exceptions import UnexpectedModelBehavior, UserError
|
|
13
12
|
from pydantic_ai.messages import ModelMessage, ModelResponse, TextPart
|
|
14
13
|
from pydantic_ai.models import ModelRequestParameters
|
|
@@ -137,7 +136,7 @@ class ClaudeCodeCliModel(BackendModelBase):
|
|
|
137
136
|
supports_json_object_output=False,
|
|
138
137
|
supports_image_output=False,
|
|
139
138
|
default_structured_output_mode="native",
|
|
140
|
-
|
|
139
|
+
supported_native_tools=frozenset(),
|
|
141
140
|
),
|
|
142
141
|
)
|
|
143
142
|
self._model_name = model_name
|
|
@@ -7,7 +7,6 @@ from datetime import datetime
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
from opentelemetry import context as otel_context
|
|
10
|
-
from pydantic_ai.builtin_tools import AbstractBuiltinTool
|
|
11
10
|
from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
12
11
|
from pydantic_ai.messages import ModelMessage, ModelResponse, TextPart
|
|
13
12
|
from pydantic_ai.models import ModelRequestParameters
|
|
@@ -111,7 +110,7 @@ class ClaudeCodeSdkModel(BackendModelBase):
|
|
|
111
110
|
supports_json_object_output=False,
|
|
112
111
|
supports_image_output=False,
|
|
113
112
|
default_structured_output_mode="native",
|
|
114
|
-
|
|
113
|
+
supported_native_tools=frozenset(),
|
|
115
114
|
),
|
|
116
115
|
)
|
|
117
116
|
self._model_name = model_name
|
nighthawk/backends/codex.py
CHANGED
|
@@ -8,7 +8,6 @@ from dataclasses import replace
|
|
|
8
8
|
from typing import IO, Any, Literal, TypedDict
|
|
9
9
|
|
|
10
10
|
from pydantic import field_validator
|
|
11
|
-
from pydantic_ai.builtin_tools import AbstractBuiltinTool
|
|
12
11
|
from pydantic_ai.exceptions import UnexpectedModelBehavior, UserError
|
|
13
12
|
from pydantic_ai.messages import ModelMessage, ModelResponse, TextPart
|
|
14
13
|
from pydantic_ai.models import ModelRequestParameters
|
|
@@ -165,7 +164,7 @@ class CodexModel(BackendModelBase):
|
|
|
165
164
|
supports_json_object_output=False,
|
|
166
165
|
supports_image_output=False,
|
|
167
166
|
default_structured_output_mode="native",
|
|
168
|
-
|
|
167
|
+
supported_native_tools=frozenset(),
|
|
169
168
|
json_schema_transformer=_CodexJsonSchemaTransformer,
|
|
170
169
|
),
|
|
171
170
|
)
|
|
@@ -92,10 +92,10 @@ def _tool_outcome_to_mcp_content_list(
|
|
|
92
92
|
|
|
93
93
|
if isinstance(content_item, BinaryContent):
|
|
94
94
|
if content_item.is_image:
|
|
95
|
-
content_block_list.append(mcp_types.ImageContent(type="image", data=content_item.base64,
|
|
95
|
+
content_block_list.append(mcp_types.ImageContent(type="image", data=content_item.base64, mime_type=content_item.media_type))
|
|
96
96
|
continue
|
|
97
97
|
if content_item.is_audio:
|
|
98
|
-
content_block_list.append(mcp_types.AudioContent(type="audio", data=content_item.base64,
|
|
98
|
+
content_block_list.append(mcp_types.AudioContent(type="audio", data=content_item.base64, mime_type=content_item.media_type))
|
|
99
99
|
continue
|
|
100
100
|
# Non-image / non-audio blobs project to text per spec §8.3:
|
|
101
101
|
# MCP's rich transport only has first-class slots for image and
|
nighthawk/backends/mcp_server.py
CHANGED
|
@@ -15,7 +15,7 @@ import time
|
|
|
15
15
|
from collections.abc import AsyncIterator, Awaitable, Callable
|
|
16
16
|
from contextlib import asynccontextmanager
|
|
17
17
|
from contextvars import copy_context
|
|
18
|
-
from typing import Any
|
|
18
|
+
from typing import Any, cast
|
|
19
19
|
|
|
20
20
|
from opentelemetry import context as otel_context
|
|
21
21
|
from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
@@ -62,20 +62,20 @@ class McpServer:
|
|
|
62
62
|
raise RuntimeError("MCP tool server is already started")
|
|
63
63
|
|
|
64
64
|
import uvicorn
|
|
65
|
-
from mcp
|
|
66
|
-
from mcp.server
|
|
65
|
+
from mcp import types as mcp_types
|
|
66
|
+
from mcp.server import Server as McpLowLevelServer
|
|
67
|
+
from mcp.server import ServerRequestContext
|
|
67
68
|
from mcp.server.streamable_http_manager import (
|
|
69
|
+
StreamableHTTPASGIApp,
|
|
68
70
|
StreamableHTTPSessionManager,
|
|
69
71
|
)
|
|
70
72
|
from starlette.applications import Starlette
|
|
71
73
|
from starlette.routing import Route
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
from mcp import types as mcp_types
|
|
78
|
-
|
|
75
|
+
async def list_tools(
|
|
76
|
+
_context: ServerRequestContext[Any, Any],
|
|
77
|
+
_parameters: mcp_types.PaginatedRequestParams | None,
|
|
78
|
+
) -> mcp_types.ListToolsResult:
|
|
79
79
|
tools: list[mcp_types.Tool] = []
|
|
80
80
|
for tool_name in sorted(self._tool_name_to_handler.keys()):
|
|
81
81
|
tool_definition = self._tool_name_to_tool_definition.get(tool_name)
|
|
@@ -86,13 +86,17 @@ class McpServer:
|
|
|
86
86
|
mcp_types.Tool(
|
|
87
87
|
name=tool_name,
|
|
88
88
|
description=tool_definition.description or "",
|
|
89
|
-
|
|
89
|
+
input_schema=tool_definition.parameters_json_schema,
|
|
90
90
|
)
|
|
91
91
|
)
|
|
92
|
-
return tools
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
return mcp_types.ListToolsResult(tools=tools)
|
|
93
|
+
|
|
94
|
+
async def call_tool(
|
|
95
|
+
_context: ServerRequestContext[Any, Any],
|
|
96
|
+
parameters: mcp_types.CallToolRequestParams,
|
|
97
|
+
) -> mcp_types.CallToolResult:
|
|
98
|
+
name = parameters.name
|
|
99
|
+
arguments = parameters.arguments or {}
|
|
96
100
|
handler = self._tool_name_to_handler.get(name)
|
|
97
101
|
if handler is None:
|
|
98
102
|
tool_outcome: ToolOutcome = {
|
|
@@ -100,19 +104,27 @@ class McpServer:
|
|
|
100
104
|
"error": {"kind": "resolution", "message": f"Unknown tool: {name}", "guidance": "Choose a visible tool name and retry."},
|
|
101
105
|
}
|
|
102
106
|
tool_handler_result: ToolHandlerResult = build_tool_result_observation(tool_outcome=tool_outcome)
|
|
103
|
-
|
|
107
|
+
content_block_list = tool_handler_result_to_low_level_mcp_content(
|
|
104
108
|
tool_name=name,
|
|
105
109
|
tool_handler_result=tool_handler_result,
|
|
106
110
|
rendering_policy=self._tool_result_rendering_policy,
|
|
107
111
|
)
|
|
112
|
+
return mcp_types.CallToolResult(content=cast(list[mcp_types.ContentBlock], content_block_list))
|
|
108
113
|
|
|
109
|
-
|
|
114
|
+
content_block_list = await call_tool_for_low_level_mcp_server(
|
|
110
115
|
tool_name=name,
|
|
111
116
|
arguments=arguments,
|
|
112
117
|
tool_handler=handler,
|
|
113
118
|
parent_otel_context=self._parent_otel_context,
|
|
114
119
|
rendering_policy=self._tool_result_rendering_policy,
|
|
115
120
|
)
|
|
121
|
+
return mcp_types.CallToolResult(content=cast(list[mcp_types.ContentBlock], content_block_list))
|
|
122
|
+
|
|
123
|
+
mcp_server = McpLowLevelServer(
|
|
124
|
+
"nighthawk",
|
|
125
|
+
on_list_tools=list_tools,
|
|
126
|
+
on_call_tool=call_tool,
|
|
127
|
+
)
|
|
116
128
|
|
|
117
129
|
session_manager = StreamableHTTPSessionManager(app=mcp_server)
|
|
118
130
|
streamable_http_asgi = StreamableHTTPASGIApp(session_manager)
|
|
@@ -337,7 +337,7 @@ class AgentStepExecutor:
|
|
|
337
337
|
|
|
338
338
|
usage_meter = get_current_usage_meter()
|
|
339
339
|
if usage_meter is not None and hasattr(result, "usage"):
|
|
340
|
-
usage_meter.record(result.usage
|
|
340
|
+
usage_meter.record(result.usage, kind="step")
|
|
341
341
|
|
|
342
342
|
step_outcome = self._parse_agent_result(result)
|
|
343
343
|
bindings = self._extract_bindings(binding_names=binding_names, step_context=step_context)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: nighthawk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: A Python library where Python controls flow and LLMs or coding agents reason within constrained Natural blocks.
|
|
5
5
|
Project-URL: Repository, https://github.com/kurusugawa-computer/nighthawk-python
|
|
6
6
|
Project-URL: Documentation, https://kurusugawa-computer.github.io/nighthawk-python/
|
|
@@ -20,17 +20,17 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: >=3.13
|
|
22
22
|
Requires-Dist: headson>=0.16.1
|
|
23
|
-
Requires-Dist: pydantic-ai-slim>=
|
|
23
|
+
Requires-Dist: pydantic-ai-slim>=2.37.0
|
|
24
24
|
Requires-Dist: pydantic>=2.13.3
|
|
25
25
|
Requires-Dist: pyyaml>=6.0.3
|
|
26
26
|
Requires-Dist: tenacity>=9.1.4
|
|
27
27
|
Requires-Dist: tiktoken>=0.12.0
|
|
28
28
|
Provides-Extra: claude-code-cli
|
|
29
|
-
Requires-Dist: mcp>=1.
|
|
29
|
+
Requires-Dist: mcp>=2.1.1; extra == 'claude-code-cli'
|
|
30
30
|
Provides-Extra: claude-code-sdk
|
|
31
|
-
Requires-Dist: claude-agent-sdk==0.
|
|
31
|
+
Requires-Dist: claude-agent-sdk==0.2.151; extra == 'claude-code-sdk'
|
|
32
32
|
Provides-Extra: codex
|
|
33
|
-
Requires-Dist: mcp>=1.
|
|
33
|
+
Requires-Dist: mcp>=2.1.1; extra == 'codex'
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
|
|
36
36
|
[](https://pypi.org/project/nighthawk-python)
|
|
@@ -68,12 +68,12 @@ For other providers, see [Pydantic AI providers](https://kurusugawa-computer.git
|
|
|
68
68
|
```py
|
|
69
69
|
import nighthawk as nh
|
|
70
70
|
|
|
71
|
+
|
|
71
72
|
def python_average(numbers):
|
|
72
73
|
return sum(numbers) / len(numbers)
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
75
|
+
|
|
76
|
+
step_executor = nh.AgentStepExecutor.from_configuration(configuration=nh.StepExecutorConfiguration(model="openai-responses:gpt-5.4-nano"))
|
|
77
77
|
|
|
78
78
|
with nh.run(step_executor):
|
|
79
79
|
|
|
@@ -8,13 +8,13 @@ nighthawk/testing.py,sha256=FCZObwxx1m9FJ28Gktlb7KmB9CCyjN_jLNyrXcysjiE,7600
|
|
|
8
8
|
nighthawk/ulid.py,sha256=w195Uc1zx92LPONcbaSxA-ZwPnmXPiV3DzhtdBuSZdo,636
|
|
9
9
|
nighthawk/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
nighthawk/backends/_text_fallback.py,sha256=0agbam4KsWwVEo7mgdHDlOzckEuqmd79EF1K6Oj6Kiw,5703
|
|
11
|
-
nighthawk/backends/base.py,sha256=
|
|
12
|
-
nighthawk/backends/claude_code_cli.py,sha256=
|
|
13
|
-
nighthawk/backends/claude_code_sdk.py,sha256=
|
|
11
|
+
nighthawk/backends/base.py,sha256=bROIX11lzKv7njjBKe32ccn1aN7NKzO2tTItQecw1TM,10598
|
|
12
|
+
nighthawk/backends/claude_code_cli.py,sha256=C_UA5ISxQBzh-q5SMuwcyT769BaJ2mWhBK_m9Rc_mrI,13618
|
|
13
|
+
nighthawk/backends/claude_code_sdk.py,sha256=6Q2Wr46WbVRtguIKFsCHZ7goKBRrOmPKM9I63Q-FHL8,13465
|
|
14
14
|
nighthawk/backends/claude_code_settings.py,sha256=wjHf8P38yrOvbhqUVT3uZ1qFjLlMIQ9de8DKHcRJn1g,1061
|
|
15
|
-
nighthawk/backends/codex.py,sha256=
|
|
16
|
-
nighthawk/backends/mcp_boundary.py,sha256=
|
|
17
|
-
nighthawk/backends/mcp_server.py,sha256=
|
|
15
|
+
nighthawk/backends/codex.py,sha256=Ds8QY-EyizILWu16LpkZS6yruM_kg9DrYbDUJ3rvgBI,14720
|
|
16
|
+
nighthawk/backends/mcp_boundary.py,sha256=baua0hU9ooehkbqtxNssIBWE8rcWPiqJFjSyn_dp0gc,10646
|
|
17
|
+
nighthawk/backends/mcp_server.py,sha256=ZlK0_WsPtbL1FqkDPKuKH2c0veBGhj2D8rFYYV612LU,8605
|
|
18
18
|
nighthawk/backends/text_projection.py,sha256=ZG3bwoC1Rb3v2fxIA-s9zaqwZUxR1mRV4l2H179Kuks,13051
|
|
19
19
|
nighthawk/backends/tool_bridge.py,sha256=d50isolJruaJi6EdAkIKEO1M1lbmkx63IuwnXzER8G8,10780
|
|
20
20
|
nighthawk/natural/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -36,7 +36,7 @@ nighthawk/runtime/runner.py,sha256=o5xzv_pUBhwX83Iri45_OrQ2s1He_loRYSbj5XT14-U,2
|
|
|
36
36
|
nighthawk/runtime/scoping.py,sha256=eRzW-1TmAvYCy3jPNPxPjeVQjHhhIe6uRv-yIWJsAf8,17678
|
|
37
37
|
nighthawk/runtime/step_context.py,sha256=f93Ed7QDGfpcBBXLyY1hE3-lzjJN99t8RKwXvEwgdRw,6207
|
|
38
38
|
nighthawk/runtime/step_contract.py,sha256=nOIWXRSwH2x66vUUAgnwjlanzI0pvXGHI8ITWimqjc4,6917
|
|
39
|
-
nighthawk/runtime/step_executor.py,sha256=
|
|
39
|
+
nighthawk/runtime/step_executor.py,sha256=vRyVZdPWFh1b-GX44BN34wDlVKa2FyHoLr_XEeyb3tk,13480
|
|
40
40
|
nighthawk/runtime/tool_calls.py,sha256=IZQWk2IH8rPF6hjVBzBDn0cpGDkG9k368dDFpmquYOg,3318
|
|
41
41
|
nighthawk/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
42
|
nighthawk/tools/_tool_return_content.py,sha256=tUkSuFjYcxwDLjK1DzKpapoWayIkBCzqqClMjIBRiSM,3110
|
|
@@ -45,7 +45,7 @@ nighthawk/tools/contracts.py,sha256=m6t-F1SwJ1BAfMFbTq9ppY8Vwbsrn_gYbl7O3VTiwf0,
|
|
|
45
45
|
nighthawk/tools/execution.py,sha256=B9Oo-AyXKSEiq9fpwmD0LQ6hzX7OoGPnDWO_xl8ecns,8842
|
|
46
46
|
nighthawk/tools/provided.py,sha256=gsZ7xcrNmEmwcvdkQi4YndTAIcfpwY1jTxpKJsYU4QQ,2079
|
|
47
47
|
nighthawk/tools/registry.py,sha256=wsZznwI2xPTflN6tfVE8je5keNVqBHHtTPd9bq0Cu7g,6425
|
|
48
|
-
nighthawk_python-0.
|
|
49
|
-
nighthawk_python-0.
|
|
50
|
-
nighthawk_python-0.
|
|
51
|
-
nighthawk_python-0.
|
|
48
|
+
nighthawk_python-0.12.0.dist-info/METADATA,sha256=4NS3DvWh_bFlv96CDVCkhzB5iL6XI5jEeK9iVIINhCU,6209
|
|
49
|
+
nighthawk_python-0.12.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
50
|
+
nighthawk_python-0.12.0.dist-info/licenses/LICENSE,sha256=R8tjrX79o5Pbi_l_aFPPWDbTZkmz4is0CxmzMMs3f0A,1076
|
|
51
|
+
nighthawk_python-0.12.0.dist-info/RECORD,,
|
|
File without changes
|