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.
@@ -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.builtin_tools:
156
- raise UserError(f"{self.backend_label} does not support builtin tools")
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 = self._get_instructions(messages, model_request_parameters)
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
- supported_builtin_tools=frozenset([AbstractBuiltinTool]),
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
- supported_builtin_tools=frozenset([AbstractBuiltinTool]),
113
+ supported_native_tools=frozenset(),
115
114
  ),
116
115
  )
117
116
  self._model_name = model_name
@@ -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
- supported_builtin_tools=frozenset([AbstractBuiltinTool]),
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, mimeType=content_item.media_type))
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, mimeType=content_item.media_type))
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
@@ -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.server.fastmcp.server import StreamableHTTPASGIApp
66
- from mcp.server.lowlevel.server import Server as McpLowLevelServer
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
- mcp_server = McpLowLevelServer("nighthawk")
74
-
75
- @mcp_server.list_tools()
76
- async def list_tools() -> list[Any]:
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
- inputSchema=tool_definition.parameters_json_schema,
89
+ input_schema=tool_definition.parameters_json_schema,
90
90
  )
91
91
  )
92
- return tools
93
-
94
- @mcp_server.call_tool(validate_input=False)
95
- async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
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
- return tool_handler_result_to_low_level_mcp_content(
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
- return await call_tool_for_low_level_mcp_server(
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(), kind="step")
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.4
1
+ Metadata-Version: 2.5
2
2
  Name: nighthawk-python
3
- Version: 0.11.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>=1.87.0
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.27.0; extra == 'claude-code-cli'
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.1.48; extra == 'claude-code-sdk'
31
+ Requires-Dist: claude-agent-sdk==0.2.151; extra == 'claude-code-sdk'
32
32
  Provides-Extra: codex
33
- Requires-Dist: mcp>=1.27.0; extra == 'codex'
33
+ Requires-Dist: mcp>=2.1.1; extra == 'codex'
34
34
  Description-Content-Type: text/markdown
35
35
 
36
36
  [![PyPI](https://img.shields.io/pypi/v/nighthawk-python)](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
- step_executor = nh.AgentStepExecutor.from_configuration(
75
- configuration=nh.StepExecutorConfiguration(model="openai-responses:gpt-5.4-nano")
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=3R8GOISQ_RMgAj3hoCIotHi82_f8L7_4xE7EE63UVns,9719
12
- nighthawk/backends/claude_code_cli.py,sha256=edEu1kyuHzBl6TrxcN6KS51fmWooprhgc_ic9_67OAE,13698
13
- nighthawk/backends/claude_code_sdk.py,sha256=ATyyXX6Kk0GH6yQAz3LB-AevGStbSqUlb6mE4XMDnJ4,13545
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=w3R8_TpS9Nm766gdTI1s6YnwAAjECIsbGT4xkWXDEwE,14800
16
- nighthawk/backends/mcp_boundary.py,sha256=zKF7LgCQqF5zvYGBugQAT5T1QgkNF41hPGh6NBpuTrY,10644
17
- nighthawk/backends/mcp_server.py,sha256=rSJgKw6Zn6hSxmLu-va5H-4kZU5yoguXQekwSJRPBdA,7971
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=jInsTgBu5B0U1THHnDPjXwnaMYsU6duRHs0LMRVmMn4,13482
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.11.0.dist-info/METADATA,sha256=d8t4e4wlxXeLaXD7bZjy1kdhSRqfzBkDd0uXpQd2QA4,6214
49
- nighthawk_python-0.11.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
50
- nighthawk_python-0.11.0.dist-info/licenses/LICENSE,sha256=R8tjrX79o5Pbi_l_aFPPWDbTZkmz4is0CxmzMMs3f0A,1076
51
- nighthawk_python-0.11.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.32.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any