weakincentives 0.2.0__py3-none-any.whl → 0.3.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.

Potentially problematic release.


This version of weakincentives might be problematic. Click here for more details.

Files changed (36) hide show
  1. weakincentives/__init__.py +26 -2
  2. weakincentives/adapters/__init__.py +6 -5
  3. weakincentives/adapters/core.py +7 -17
  4. weakincentives/adapters/litellm.py +594 -0
  5. weakincentives/adapters/openai.py +286 -57
  6. weakincentives/events.py +103 -0
  7. weakincentives/examples/__init__.py +67 -0
  8. weakincentives/examples/code_review_prompt.py +118 -0
  9. weakincentives/examples/code_review_session.py +171 -0
  10. weakincentives/examples/code_review_tools.py +376 -0
  11. weakincentives/{prompts → prompt}/__init__.py +6 -8
  12. weakincentives/{prompts → prompt}/_types.py +1 -1
  13. weakincentives/{prompts/text.py → prompt/markdown.py} +19 -9
  14. weakincentives/{prompts → prompt}/prompt.py +216 -66
  15. weakincentives/{prompts → prompt}/response_format.py +9 -6
  16. weakincentives/{prompts → prompt}/section.py +25 -4
  17. weakincentives/{prompts/structured.py → prompt/structured_output.py} +16 -5
  18. weakincentives/{prompts → prompt}/tool.py +6 -6
  19. weakincentives/prompt/versioning.py +144 -0
  20. weakincentives/serde/__init__.py +0 -14
  21. weakincentives/serde/dataclass_serde.py +3 -17
  22. weakincentives/session/__init__.py +31 -0
  23. weakincentives/session/reducers.py +60 -0
  24. weakincentives/session/selectors.py +45 -0
  25. weakincentives/session/session.py +168 -0
  26. weakincentives/tools/__init__.py +69 -0
  27. weakincentives/tools/errors.py +22 -0
  28. weakincentives/tools/planning.py +538 -0
  29. weakincentives/tools/vfs.py +590 -0
  30. weakincentives-0.3.0.dist-info/METADATA +231 -0
  31. weakincentives-0.3.0.dist-info/RECORD +35 -0
  32. weakincentives-0.2.0.dist-info/METADATA +0 -173
  33. weakincentives-0.2.0.dist-info/RECORD +0 -20
  34. /weakincentives/{prompts → prompt}/errors.py +0 -0
  35. {weakincentives-0.2.0.dist-info → weakincentives-0.3.0.dist-info}/WHEEL +0 -0
  36. {weakincentives-0.2.0.dist-info → weakincentives-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,118 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ """Prompt scaffolding shared by the code review examples."""
14
+
15
+ from __future__ import annotations
16
+
17
+ import textwrap
18
+ from dataclasses import dataclass, field
19
+
20
+ from ..prompt import MarkdownSection, Prompt
21
+ from ..session import Session
22
+ from ..tools import PlanningToolsSection, VfsToolsSection
23
+ from .code_review_tools import build_tools
24
+
25
+
26
+ @dataclass
27
+ class ReviewGuidance:
28
+ focus: str = field(
29
+ default=(
30
+ "Identify potential issues, risks, and follow-up questions for the changes "
31
+ "under review."
32
+ ),
33
+ metadata={
34
+ "description": "Default framing instructions for the review assistant.",
35
+ },
36
+ )
37
+
38
+
39
+ @dataclass
40
+ class ReviewTurnParams:
41
+ request: str = field(
42
+ metadata={
43
+ "description": "User-provided review task or question to address.",
44
+ }
45
+ )
46
+
47
+
48
+ @dataclass
49
+ class ReviewResponse:
50
+ summary: str
51
+ issues: list[str]
52
+ next_steps: list[str]
53
+
54
+
55
+ def build_code_review_prompt(session: Session) -> Prompt[ReviewResponse]:
56
+ tools = build_tools()
57
+ guidance_section = MarkdownSection[ReviewGuidance](
58
+ title="Code Review Brief",
59
+ template=textwrap.dedent(
60
+ """
61
+ You are a code review assistant working in this repository.
62
+ Every response must stay anchored to the specific task described
63
+ in the review request. If the task is unclear, ask for the missing
64
+ details before proceeding.
65
+
66
+ Use the available tools to stay grounded:
67
+ - `show_git_log` retrieves commit history relevant to the task.
68
+ - `show_git_branches` lists branches that match specified filters.
69
+ - `show_git_tags` lists tags that match specified filters.
70
+ - `show_current_time` reports the present time (default UTC or a
71
+ requested timezone).
72
+ - `vfs_list_directory` lists directories and files staged in the virtual
73
+ filesystem snapshot.
74
+ - `vfs_read_file` reads staged file contents.
75
+ - `vfs_write_file` stages ASCII edits before applying them to the host
76
+ workspace.
77
+ - `vfs_delete_entry` removes staged files or directories that are no
78
+ longer needed.
79
+ If the task requires information beyond these capabilities, ask the
80
+ user for clarification rather than guessing.
81
+
82
+ Maintain a concise working plan for multi-step investigations. Use the
83
+ planning tools to capture the current objective, record step details
84
+ as you gather evidence, and mark tasks complete when finished.
85
+
86
+ Always provide a JSON response with the following keys:
87
+ - summary: Single paragraph capturing the overall state of the changes.
88
+ - issues: List of concrete problems, risks, or follow-up questions tied
89
+ to the task.
90
+ - next_steps: List of actionable recommendations or follow-ups that
91
+ help complete the task or mitigate the issues.
92
+ """
93
+ ).strip(),
94
+ default_params=ReviewGuidance(),
95
+ tools=tools,
96
+ key="code-review-brief",
97
+ )
98
+ planning_section = PlanningToolsSection(session=session)
99
+ vfs_section = VfsToolsSection(session=session)
100
+ user_turn_section = MarkdownSection[ReviewTurnParams](
101
+ title="Review Request",
102
+ template="${request}",
103
+ key="review-request",
104
+ )
105
+ return Prompt[ReviewResponse](
106
+ ns="examples/code-review",
107
+ key="code-review-session",
108
+ name="code_review_agent",
109
+ sections=[guidance_section, planning_section, vfs_section, user_turn_section],
110
+ )
111
+
112
+
113
+ __all__ = [
114
+ "ReviewGuidance",
115
+ "ReviewTurnParams",
116
+ "ReviewResponse",
117
+ "build_code_review_prompt",
118
+ ]
@@ -0,0 +1,171 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ """Session orchestration for the code review examples."""
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ from dataclasses import dataclass
19
+ from typing import Any, Protocol
20
+
21
+ from ..adapters import PromptResponse
22
+ from ..events import EventBus, InProcessEventBus, ToolInvoked
23
+ from ..prompt import Prompt, SupportsDataclass
24
+ from ..serde import dump
25
+ from ..session import (
26
+ DataEvent,
27
+ Session,
28
+ ToolData,
29
+ select_all,
30
+ select_latest,
31
+ )
32
+ from .code_review_prompt import (
33
+ ReviewResponse,
34
+ ReviewTurnParams,
35
+ build_code_review_prompt,
36
+ )
37
+ from .code_review_tools import (
38
+ BranchListResult,
39
+ GitLogResult,
40
+ TagListResult,
41
+ TimeQueryResult,
42
+ )
43
+
44
+
45
+ @dataclass(slots=True, frozen=True)
46
+ class ToolCallLog:
47
+ """Recorded tool invocation captured by the session."""
48
+
49
+ name: str
50
+ prompt_name: str
51
+ message: str
52
+ value: dict[str, Any]
53
+ call_id: str | None
54
+
55
+
56
+ class SupportsReviewEvaluate(Protocol):
57
+ """Protocol describing the adapter interface consumed by the session."""
58
+
59
+ def evaluate(
60
+ self,
61
+ prompt: Prompt[ReviewResponse],
62
+ *params: SupportsDataclass,
63
+ parse_output: bool = True,
64
+ bus: EventBus,
65
+ ) -> PromptResponse[ReviewResponse]: ...
66
+
67
+
68
+ class CodeReviewSession:
69
+ """Interactive session wrapper shared by example adapters."""
70
+
71
+ def __init__(
72
+ self,
73
+ adapter: SupportsReviewEvaluate,
74
+ *,
75
+ bus: EventBus | None = None,
76
+ ) -> None:
77
+ self._adapter = adapter
78
+ self._bus = bus or InProcessEventBus()
79
+ self._session = Session(bus=self._bus)
80
+ self._prompt = build_code_review_prompt(self._session)
81
+ self._bus.subscribe(ToolInvoked, self._display_tool_event)
82
+ self._register_tool_history()
83
+
84
+ def evaluate(self, request: str) -> str:
85
+ response = self._adapter.evaluate(
86
+ self._prompt,
87
+ ReviewTurnParams(request=request),
88
+ bus=self._bus,
89
+ )
90
+ if response.output is not None:
91
+ rendered_output = dump(response.output, exclude_none=True)
92
+ return json.dumps(
93
+ rendered_output,
94
+ ensure_ascii=False,
95
+ indent=2,
96
+ )
97
+ if response.text:
98
+ return response.text
99
+ return "(no response from assistant)"
100
+
101
+ def render_tool_history(self) -> str:
102
+ history = select_all(self._session, ToolCallLog)
103
+ if not history:
104
+ return "No tool calls recorded yet."
105
+
106
+ lines: list[str] = []
107
+ for index, record in enumerate(history, start=1):
108
+ lines.append(
109
+ f"{index}. {record.name} ({record.prompt_name}) → {record.message}"
110
+ )
111
+ if record.call_id:
112
+ lines.append(f" call_id: {record.call_id}")
113
+ if record.value:
114
+ payload_dump = json.dumps(record.value, ensure_ascii=False)
115
+ lines.append(f" payload: {payload_dump}")
116
+ return "\n".join(lines)
117
+
118
+ def _display_tool_event(self, event: object) -> None:
119
+ if not isinstance(event, ToolInvoked):
120
+ return
121
+
122
+ serialized_params = dump(event.params, exclude_none=True)
123
+ payload = dump(event.result.value, exclude_none=True)
124
+ print(
125
+ f"[tool] {event.name} called with {serialized_params}\n"
126
+ f" → {event.result.message}"
127
+ )
128
+ if payload:
129
+ print(f" payload: {payload}")
130
+ latest = select_latest(self._session, ToolCallLog)
131
+ if latest is not None:
132
+ count = len(select_all(self._session, ToolCallLog))
133
+ print(f" (session recorded this call as #{count})")
134
+
135
+ def _register_tool_history(self) -> None:
136
+ for result_type in (
137
+ GitLogResult,
138
+ TimeQueryResult,
139
+ BranchListResult,
140
+ TagListResult,
141
+ ):
142
+ self._session.register_reducer(
143
+ result_type,
144
+ self._record_tool_call,
145
+ slice_type=ToolCallLog,
146
+ )
147
+
148
+ def _record_tool_call(
149
+ self,
150
+ slice_values: tuple[ToolCallLog, ...],
151
+ event: DataEvent,
152
+ ) -> tuple[ToolCallLog, ...]:
153
+ if not isinstance(event, ToolData):
154
+ return slice_values
155
+
156
+ payload = dump(event.value, exclude_none=True)
157
+ record = ToolCallLog(
158
+ name=event.source.name,
159
+ prompt_name=event.source.prompt_name,
160
+ message=event.source.result.message,
161
+ value=payload,
162
+ call_id=event.source.call_id,
163
+ )
164
+ return slice_values + (record,)
165
+
166
+
167
+ __all__ = [
168
+ "ToolCallLog",
169
+ "SupportsReviewEvaluate",
170
+ "CodeReviewSession",
171
+ ]
@@ -0,0 +1,376 @@
1
+ # Licensed under the Apache License, Version 2.0 (the "License");
2
+ # you may not use this file except in compliance with the License.
3
+ # You may obtain a copy of the License at
4
+ #
5
+ # http://www.apache.org/licenses/LICENSE-2.0
6
+ #
7
+ # Unless required by applicable law or agreed to in writing, software
8
+ # distributed under the License is distributed on an "AS IS" BASIS,
9
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ # See the License for the specific language governing permissions and
11
+ # limitations under the License.
12
+
13
+ """Tool metadata and handlers shared by the code review examples."""
14
+
15
+ from __future__ import annotations
16
+
17
+ import subprocess # nosec B404
18
+ from collections.abc import Sequence
19
+ from dataclasses import dataclass, field
20
+ from datetime import datetime
21
+ from pathlib import Path
22
+ from typing import Any
23
+ from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
24
+
25
+ from ..prompt import Tool, ToolResult
26
+
27
+ REPO_ROOT = Path(__file__).resolve().parents[3]
28
+ MAX_OUTPUT_CHARS = 4000
29
+
30
+
31
+ def _truncate(text: str, max_chars: int = MAX_OUTPUT_CHARS) -> str:
32
+ if len(text) <= max_chars:
33
+ return text
34
+ truncated = text[: max_chars - 20]
35
+ return f"{truncated}\n... (truncated {len(text) - len(truncated)} characters)"
36
+
37
+
38
+ def _run_git_command(args: Sequence[str]) -> subprocess.CompletedProcess[str]:
39
+ """Execute a git command rooted at the repository directory."""
40
+ # Tool handlers build git commands from validated dataclass inputs.
41
+ return subprocess.run( # nosec B603
42
+ list(args),
43
+ cwd=REPO_ROOT,
44
+ check=False,
45
+ capture_output=True,
46
+ text=True,
47
+ )
48
+
49
+
50
+ @dataclass
51
+ class GitLogParams:
52
+ """Parameters for querying git log history."""
53
+
54
+ revision_range: str | None = field(
55
+ default=None,
56
+ metadata={
57
+ "description": (
58
+ "Commit range spec passed to `git log` (for example 'main..HEAD')."
59
+ )
60
+ },
61
+ )
62
+ path: str | None = field(
63
+ default=None,
64
+ metadata={
65
+ "description": "Restrict history to a specific file or directory.",
66
+ },
67
+ )
68
+ max_count: int | None = field(
69
+ default=20,
70
+ metadata={"description": "Maximum number of commits to return."},
71
+ )
72
+ skip: int | None = field(
73
+ default=None,
74
+ metadata={
75
+ "description": "Number of commits to skip from the top of the result.",
76
+ },
77
+ )
78
+ author: str | None = field(
79
+ default=None,
80
+ metadata={
81
+ "description": ("Filter commits to those authored by the provided string."),
82
+ },
83
+ )
84
+ since: str | None = field(
85
+ default=None,
86
+ metadata={
87
+ "description": (
88
+ "Only include commits after this date/time (forwarded to git)."
89
+ ),
90
+ },
91
+ )
92
+ until: str | None = field(
93
+ default=None,
94
+ metadata={
95
+ "description": (
96
+ "Only include commits up to this date/time (forwarded to git)."
97
+ ),
98
+ },
99
+ )
100
+ grep: str | None = field(
101
+ default=None,
102
+ metadata={
103
+ "description": (
104
+ "Include commits whose messages match this regular expression."
105
+ ),
106
+ },
107
+ )
108
+ additional_args: tuple[str, ...] = field(
109
+ default_factory=tuple,
110
+ metadata={
111
+ "description": "Extra raw arguments forwarded to `git log`.",
112
+ },
113
+ )
114
+
115
+
116
+ @dataclass
117
+ class GitLogResult:
118
+ entries: list[str]
119
+
120
+
121
+ @dataclass
122
+ class TimeQueryParams:
123
+ """Parameters for requesting the current time."""
124
+
125
+ timezone: str | None = field(
126
+ default=None,
127
+ metadata={
128
+ "description": (
129
+ "IANA timezone identifier to convert the current time. Defaults to UTC."
130
+ ),
131
+ },
132
+ )
133
+
134
+
135
+ @dataclass
136
+ class TimeQueryResult:
137
+ iso_timestamp: str
138
+ timezone: str
139
+ source: str
140
+
141
+
142
+ @dataclass
143
+ class BranchListParams:
144
+ """Parameters for listing git branches."""
145
+
146
+ include_remote: bool = field(
147
+ default=False,
148
+ metadata={
149
+ "description": "Include remote branches when set to true (uses --all).",
150
+ },
151
+ )
152
+ pattern: str | None = field(
153
+ default=None,
154
+ metadata={
155
+ "description": "Optional glob to filter branch names (passed to git).",
156
+ },
157
+ )
158
+ contains: str | None = field(
159
+ default=None,
160
+ metadata={
161
+ "description": "Only branches containing this commit (uses --contains).",
162
+ },
163
+ )
164
+
165
+
166
+ @dataclass
167
+ class BranchListResult:
168
+ branches: list[str]
169
+
170
+
171
+ @dataclass
172
+ class TagListParams:
173
+ """Parameters for listing git tags."""
174
+
175
+ pattern: str | None = field(
176
+ default=None,
177
+ metadata={
178
+ "description": "Optional glob to filter tags (passed to git).",
179
+ },
180
+ )
181
+ sort: str | None = field(
182
+ default=None,
183
+ metadata={
184
+ "description": "Sort directive forwarded to git tag --sort (for example '-version:refname').",
185
+ },
186
+ )
187
+ contains: str | None = field(
188
+ default=None,
189
+ metadata={
190
+ "description": "Only tags containing this commit (uses --contains).",
191
+ },
192
+ )
193
+
194
+
195
+ @dataclass
196
+ class TagListResult:
197
+ tags: list[str]
198
+
199
+
200
+ def git_log_handler(params: GitLogParams) -> ToolResult[GitLogResult]:
201
+ max_count = params.max_count
202
+ args = ["git", "log", "--oneline"]
203
+ if max_count is not None:
204
+ max_count = max(1, max_count)
205
+ args.append(f"-n{max_count}")
206
+ if params.skip:
207
+ args.extend(["--skip", str(max(params.skip, 0))])
208
+ if params.author:
209
+ args.extend(["--author", params.author])
210
+ if params.since:
211
+ args.extend(["--since", params.since])
212
+ if params.until:
213
+ args.extend(["--until", params.until])
214
+ if params.grep:
215
+ args.extend(["--grep", params.grep])
216
+ if params.additional_args:
217
+ args.extend(params.additional_args)
218
+ if params.revision_range:
219
+ args.append(params.revision_range)
220
+ if params.path:
221
+ args.extend(["--", params.path])
222
+
223
+ result = _run_git_command(args)
224
+ if result.returncode != 0:
225
+ message = f"git log failed: {result.stderr.strip()}"
226
+ return ToolResult(message=message, value=GitLogResult(entries=[]))
227
+
228
+ entries = [line.strip() for line in result.stdout.splitlines() if line.strip()]
229
+ if not entries:
230
+ message = "No git log entries matched the query."
231
+ else:
232
+ joined_entries = "\n".join(entries)
233
+ message = (
234
+ f"Returned {len(entries)} git log entr{'y' if len(entries) == 1 else 'ies'}:\n"
235
+ f"{_truncate(joined_entries)}"
236
+ )
237
+ return ToolResult(message=message, value=GitLogResult(entries=entries))
238
+
239
+
240
+ def current_time_handler(params: TimeQueryParams) -> ToolResult[TimeQueryResult]:
241
+ requested_timezone = params.timezone or "UTC"
242
+ timezone_name = requested_timezone
243
+ try:
244
+ tzinfo = ZoneInfo(requested_timezone)
245
+ source = "zoneinfo"
246
+ except ZoneInfoNotFoundError:
247
+ tzinfo = ZoneInfo("UTC")
248
+ timezone_name = "UTC"
249
+ source = "fallback"
250
+
251
+ now = datetime.now(tzinfo)
252
+ iso_timestamp = now.isoformat()
253
+ if source == "fallback" and requested_timezone != "UTC":
254
+ message = (
255
+ f"Timezone '{requested_timezone}' not found. "
256
+ f"Using UTC instead.\nCurrent time (UTC): {iso_timestamp}"
257
+ )
258
+ else:
259
+ message = f"Current time in {timezone_name}: {iso_timestamp}"
260
+
261
+ return ToolResult(
262
+ message=message,
263
+ value=TimeQueryResult(
264
+ iso_timestamp=iso_timestamp,
265
+ timezone=timezone_name,
266
+ source=source,
267
+ ),
268
+ )
269
+
270
+
271
+ def branch_list_handler(params: BranchListParams) -> ToolResult[BranchListResult]:
272
+ args = ["git", "branch", "--list"]
273
+ if params.include_remote:
274
+ args.append("--all")
275
+ if params.contains:
276
+ args.extend(["--contains", params.contains])
277
+ if params.pattern:
278
+ args.append(params.pattern)
279
+
280
+ result = _run_git_command(args)
281
+ if result.returncode != 0:
282
+ message = f"git branch failed: {result.stderr.strip()}"
283
+ return ToolResult(message=message, value=BranchListResult(branches=[]))
284
+
285
+ branches = [line.strip().lstrip("* ") for line in result.stdout.splitlines()]
286
+ branches = [branch for branch in branches if branch]
287
+ if not branches:
288
+ message = "No branches matched the query."
289
+ else:
290
+ message = (
291
+ f"Returned {len(branches)} branch entr{'y' if len(branches) == 1 else 'ies'}:\n"
292
+ f"{_truncate('\n'.join(branches))}"
293
+ )
294
+ return ToolResult(message=message, value=BranchListResult(branches=branches))
295
+
296
+
297
+ def tag_list_handler(params: TagListParams) -> ToolResult[TagListResult]:
298
+ args = ["git", "tag", "--list"]
299
+ if params.contains:
300
+ args.extend(["--contains", params.contains])
301
+ if params.sort:
302
+ args.extend(["--sort", params.sort])
303
+ if params.pattern:
304
+ args.append(params.pattern)
305
+
306
+ result = _run_git_command(args)
307
+ if result.returncode != 0:
308
+ message = f"git tag failed: {result.stderr.strip()}"
309
+ return ToolResult(message=message, value=TagListResult(tags=[]))
310
+
311
+ tags = [line.strip() for line in result.stdout.splitlines() if line.strip()]
312
+ if not tags:
313
+ message = "No tags matched the query."
314
+ else:
315
+ message = (
316
+ f"Returned {len(tags)} tag entr{'y' if len(tags) == 1 else 'ies'}:\n"
317
+ f"{_truncate('\n'.join(tags))}"
318
+ )
319
+ return ToolResult(message=message, value=TagListResult(tags=tags))
320
+
321
+
322
+ def build_tools() -> tuple[Tool[Any, Any], ...]:
323
+ git_log_tool = Tool[GitLogParams, GitLogResult](
324
+ name="show_git_log",
325
+ description=(
326
+ "Inspect repository history using git log filters such as revision "
327
+ "ranges, authors, dates, grep patterns, and file paths."
328
+ ),
329
+ handler=git_log_handler,
330
+ )
331
+ current_time_tool = Tool[TimeQueryParams, TimeQueryResult](
332
+ name="show_current_time",
333
+ description="Fetch the current time in UTC or a provided timezone using zoneinfo.",
334
+ handler=current_time_handler,
335
+ )
336
+ branch_list_tool = Tool[BranchListParams, BranchListResult](
337
+ name="show_git_branches",
338
+ description=(
339
+ "List local or remote branches with optional glob filters and commit containment checks."
340
+ ),
341
+ handler=branch_list_handler,
342
+ )
343
+ tag_list_tool = Tool[TagListParams, TagListResult](
344
+ name="show_git_tags",
345
+ description=(
346
+ "List repository tags with optional glob filters, sorting, and commit containment checks."
347
+ ),
348
+ handler=tag_list_handler,
349
+ )
350
+ return (
351
+ git_log_tool,
352
+ current_time_tool,
353
+ branch_list_tool,
354
+ tag_list_tool,
355
+ )
356
+
357
+
358
+ __all__ = [
359
+ "REPO_ROOT",
360
+ "MAX_OUTPUT_CHARS",
361
+ "GitLogParams",
362
+ "GitLogResult",
363
+ "TimeQueryParams",
364
+ "TimeQueryResult",
365
+ "BranchListParams",
366
+ "BranchListResult",
367
+ "TagListParams",
368
+ "TagListResult",
369
+ "git_log_handler",
370
+ "current_time_handler",
371
+ "branch_list_handler",
372
+ "tag_list_handler",
373
+ "build_tools",
374
+ "_run_git_command",
375
+ "_truncate",
376
+ ]
@@ -10,7 +10,7 @@
10
10
  # See the License for the specific language governing permissions and
11
11
  # limitations under the License.
12
12
 
13
- """Prompt module scaffolding."""
13
+ """Prompt authoring primitives exposed by :mod:`weakincentives.prompt`."""
14
14
 
15
15
  from __future__ import annotations
16
16
 
@@ -21,25 +21,23 @@ from .errors import (
21
21
  PromptValidationError,
22
22
  SectionPath,
23
23
  )
24
- from .prompt import Prompt, PromptSectionNode, RenderedPrompt
24
+ from .markdown import MarkdownSection
25
+ from .prompt import Prompt
25
26
  from .section import Section
26
- from .structured import OutputParseError, parse_output
27
- from .text import TextSection
27
+ from .structured_output import OutputParseError, parse_structured_output
28
28
  from .tool import Tool, ToolResult
29
29
 
30
30
  __all__ = [
31
31
  "Prompt",
32
- "RenderedPrompt",
33
- "PromptSectionNode",
34
32
  "PromptError",
35
33
  "PromptRenderError",
36
34
  "PromptValidationError",
37
35
  "Section",
38
36
  "SectionPath",
39
37
  "SupportsDataclass",
40
- "TextSection",
38
+ "MarkdownSection",
41
39
  "Tool",
42
40
  "ToolResult",
43
41
  "OutputParseError",
44
- "parse_output",
42
+ "parse_structured_output",
45
43
  ]