vidbyte-cli 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.
Files changed (269) hide show
  1. vidbyte_cli/__init__.py +11 -0
  2. vidbyte_cli/__main__.py +6 -0
  3. vidbyte_cli/cli.py +18 -0
  4. vidbyte_cli/commands/__init__.py +86 -0
  5. vidbyte_cli/commands/agent_options.py +53 -0
  6. vidbyte_cli/commands/agents/__init__.py +31 -0
  7. vidbyte_cli/commands/agents/suggestion/__init__.py +38 -0
  8. vidbyte_cli/commands/agents/suggestion/prompts/__init__.py +3 -0
  9. vidbyte_cli/commands/agents/suggestion/prompts/all_categories.md +5 -0
  10. vidbyte_cli/commands/agents/suggestion/prompts/approaches.md +100 -0
  11. vidbyte_cli/commands/agents/suggestion/prompts/avoid.md +100 -0
  12. vidbyte_cli/commands/agents/suggestion/prompts/blockers.md +100 -0
  13. vidbyte_cli/commands/agents/suggestion/prompts/capability.md +100 -0
  14. vidbyte_cli/commands/agents/suggestion/prompts/categories.md +5 -0
  15. vidbyte_cli/commands/agents/suggestion/prompts/category.md +5 -0
  16. vidbyte_cli/commands/agents/suggestion/prompts/completed.md +100 -0
  17. vidbyte_cli/commands/agents/suggestion/prompts/constraint.md +100 -0
  18. vidbyte_cli/commands/agents/suggestion/prompts/context.md +100 -0
  19. vidbyte_cli/commands/agents/suggestion/prompts/count.md +5 -0
  20. vidbyte_cli/commands/agents/suggestion/prompts/critic_model.md +100 -0
  21. vidbyte_cli/commands/agents/suggestion/prompts/decision.md +100 -0
  22. vidbyte_cli/commands/agents/suggestion/prompts/dry_run.md +5 -0
  23. vidbyte_cli/commands/agents/suggestion/prompts/extra_compute.md +5 -0
  24. vidbyte_cli/commands/agents/suggestion/prompts/feedback_accept.md +7 -0
  25. vidbyte_cli/commands/agents/suggestion/prompts/feedback_group.md +7 -0
  26. vidbyte_cli/commands/agents/suggestion/prompts/feedback_project.md +100 -0
  27. vidbyte_cli/commands/agents/suggestion/prompts/feedback_reason.md +98 -0
  28. vidbyte_cli/commands/agents/suggestion/prompts/feedback_reject.md +7 -0
  29. vidbyte_cli/commands/agents/suggestion/prompts/feedback_suggestion.md +101 -0
  30. vidbyte_cli/commands/agents/suggestion/prompts/files.md +100 -0
  31. vidbyte_cli/commands/agents/suggestion/prompts/forbidden.md +100 -0
  32. vidbyte_cli/commands/agents/suggestion/prompts/goal.md +100 -0
  33. vidbyte_cli/commands/agents/suggestion/prompts/handoff.md +7 -0
  34. vidbyte_cli/commands/agents/suggestion/prompts/horizon.md +5 -0
  35. vidbyte_cli/commands/agents/suggestion/prompts/hypotheses.md +100 -0
  36. vidbyte_cli/commands/agents/suggestion/prompts/idea.md +100 -0
  37. vidbyte_cli/commands/agents/suggestion/prompts/in_progress.md +100 -0
  38. vidbyte_cli/commands/agents/suggestion/prompts/input.md +100 -0
  39. vidbyte_cli/commands/agents/suggestion/prompts/library.py +29 -0
  40. vidbyte_cli/commands/agents/suggestion/prompts/max_messages.md +5 -0
  41. vidbyte_cli/commands/agents/suggestion/prompts/max_output_tokens.md +5 -0
  42. vidbyte_cli/commands/agents/suggestion/prompts/max_total_tokens.md +5 -0
  43. vidbyte_cli/commands/agents/suggestion/prompts/mistakes.md +100 -0
  44. vidbyte_cli/commands/agents/suggestion/prompts/outcomes.md +100 -0
  45. vidbyte_cli/commands/agents/suggestion/prompts/project.md +100 -0
  46. vidbyte_cli/commands/agents/suggestion/prompts/project_create.md +7 -0
  47. vidbyte_cli/commands/agents/suggestion/prompts/project_description.md +98 -0
  48. vidbyte_cli/commands/agents/suggestion/prompts/project_group.md +7 -0
  49. vidbyte_cli/commands/agents/suggestion/prompts/project_key.md +96 -0
  50. vidbyte_cli/commands/agents/suggestion/prompts/project_list.md +7 -0
  51. vidbyte_cli/commands/agents/suggestion/prompts/project_title.md +99 -0
  52. vidbyte_cli/commands/agents/suggestion/prompts/provider.md +5 -0
  53. vidbyte_cli/commands/agents/suggestion/prompts/question.md +100 -0
  54. vidbyte_cli/commands/agents/suggestion/prompts/risks.md +100 -0
  55. vidbyte_cli/commands/agents/suggestion/prompts/rounds.md +5 -0
  56. vidbyte_cli/commands/agents/suggestion/prompts/run.md +5 -0
  57. vidbyte_cli/commands/agents/suggestion/prompts/success.md +100 -0
  58. vidbyte_cli/commands/agents/suggestion/prompts/timeout.md +5 -0
  59. vidbyte_cli/commands/agents/suggestion/prompts/trajectory.md +100 -0
  60. vidbyte_cli/commands/agents/suggestion/prompts/view.md +5 -0
  61. vidbyte_cli/commands/agents/suggestion/prompts/view_all.md +5 -0
  62. vidbyte_cli/commands/agents/suggestion/render.py +77 -0
  63. vidbyte_cli/commands/agents/suggestion/request_builder.py +240 -0
  64. vidbyte_cli/commands/agents/suggestion/suggest.py +154 -0
  65. vidbyte_cli/commands/agents/suggestion/suggestion_categories.py +77 -0
  66. vidbyte_cli/commands/agents/suggestion/suggestion_feedback.py +85 -0
  67. vidbyte_cli/commands/agents/suggestion/suggestion_handoff.py +67 -0
  68. vidbyte_cli/commands/agents/suggestion/suggestion_projects.py +95 -0
  69. vidbyte_cli/commands/auth/__init__.py +0 -0
  70. vidbyte_cli/commands/auth/login.py +100 -0
  71. vidbyte_cli/commands/auth/logout.py +41 -0
  72. vidbyte_cli/commands/auth/whoami.py +51 -0
  73. vidbyte_cli/commands/billing/__init__.py +20 -0
  74. vidbyte_cli/commands/billing/prompts/__init__.py +1 -0
  75. vidbyte_cli/commands/billing/prompts/confirm.md +16 -0
  76. vidbyte_cli/commands/billing/prompts/group.md +13 -0
  77. vidbyte_cli/commands/billing/prompts/idempotency_key.md +16 -0
  78. vidbyte_cli/commands/billing/prompts/library.py +18 -0
  79. vidbyte_cli/commands/billing/prompts/top_up.md +16 -0
  80. vidbyte_cli/commands/billing/top_up.py +65 -0
  81. vidbyte_cli/commands/config/__init__.py +0 -0
  82. vidbyte_cli/commands/config/get.py +45 -0
  83. vidbyte_cli/commands/config/set.py +39 -0
  84. vidbyte_cli/commands/provider/__init__.py +9 -0
  85. vidbyte_cli/commands/provider/login.py +102 -0
  86. vidbyte_cli/commands/provider/logout.py +45 -0
  87. vidbyte_cli/commands/provider/whoami.py +49 -0
  88. vidbyte_cli/commands/research/__init__.py +5 -0
  89. vidbyte_cli/commands/research/add.py +41 -0
  90. vidbyte_cli/commands/research/options.py +134 -0
  91. vidbyte_cli/commands/research/render.py +157 -0
  92. vidbyte_cli/commands/research/resume.py +37 -0
  93. vidbyte_cli/commands/research/start.py +37 -0
  94. vidbyte_cli/commands/research/status.py +32 -0
  95. vidbyte_cli/commands/research/thread.py +30 -0
  96. vidbyte_cli/commands/research/threads.py +35 -0
  97. vidbyte_cli/commands/research/watch.py +93 -0
  98. vidbyte_cli/commands/runtime/__init__.py +23 -0
  99. vidbyte_cli/commands/runtime/adversarial_team.py +41 -0
  100. vidbyte_cli/commands/runtime/doctor.py +44 -0
  101. vidbyte_cli/commands/runtime/list.py +47 -0
  102. vidbyte_cli/commands/runtime/persistence.py +141 -0
  103. vidbyte_cli/commands/runtime/same_host_ensemble.py +221 -0
  104. vidbyte_cli/commands/runtime/stages.py +553 -0
  105. vidbyte_cli/commands/runtime/task_board.py +1342 -0
  106. vidbyte_cli/commands/setup/__init__.py +0 -0
  107. vidbyte_cli/commands/setup/doctor.py +64 -0
  108. vidbyte_cli/commands/setup/prompts/__init__.py +1 -0
  109. vidbyte_cli/commands/setup/prompts/browser.md +7 -0
  110. vidbyte_cli/commands/setup/prompts/library.py +18 -0
  111. vidbyte_cli/commands/setup/prompts/star.md +13 -0
  112. vidbyte_cli/commands/setup/star.py +132 -0
  113. vidbyte_cli/lib/__init__.py +0 -0
  114. vidbyte_cli/lib/api/__init__.py +0 -0
  115. vidbyte_cli/lib/api/client.py +236 -0
  116. vidbyte_cli/lib/api/endpoints/__init__.py +0 -0
  117. vidbyte_cli/lib/api/endpoints/auth.py +24 -0
  118. vidbyte_cli/lib/api/endpoints/billing.py +36 -0
  119. vidbyte_cli/lib/api/endpoints/research.py +103 -0
  120. vidbyte_cli/lib/api/endpoints/runtime.py +113 -0
  121. vidbyte_cli/lib/api/problem.py +86 -0
  122. vidbyte_cli/lib/api/response.py +101 -0
  123. vidbyte_cli/lib/api/retry.py +116 -0
  124. vidbyte_cli/lib/api/runtime_payment.py +89 -0
  125. vidbyte_cli/lib/auth/__init__.py +16 -0
  126. vidbyte_cli/lib/auth/credentials.py +223 -0
  127. vidbyte_cli/lib/auth/input.py +59 -0
  128. vidbyte_cli/lib/auth/keyring_store.py +126 -0
  129. vidbyte_cli/lib/auth/provider_credentials.py +157 -0
  130. vidbyte_cli/lib/auth/provider_input.py +54 -0
  131. vidbyte_cli/lib/auth/provider_resolver.py +89 -0
  132. vidbyte_cli/lib/auth/provider_store.py +149 -0
  133. vidbyte_cli/lib/auth/provider_verifier.py +219 -0
  134. vidbyte_cli/lib/auth/resolver.py +63 -0
  135. vidbyte_cli/lib/auth/verifier.py +38 -0
  136. vidbyte_cli/lib/config/__init__.py +25 -0
  137. vidbyte_cli/lib/config/atomic.py +47 -0
  138. vidbyte_cli/lib/config/config.py +141 -0
  139. vidbyte_cli/lib/config/migration.py +81 -0
  140. vidbyte_cli/lib/config/models.py +145 -0
  141. vidbyte_cli/lib/config/paths.py +66 -0
  142. vidbyte_cli/lib/config/resolver.py +137 -0
  143. vidbyte_cli/lib/constants/__init__.py +1 -0
  144. vidbyte_cli/lib/constants/runtime.py +186 -0
  145. vidbyte_cli/lib/errors/__init__.py +12 -0
  146. vidbyte_cli/lib/errors/cli_error.py +86 -0
  147. vidbyte_cli/lib/errors/codes.py +43 -0
  148. vidbyte_cli/lib/errors/failures.py +2910 -0
  149. vidbyte_cli/lib/errors/handler.py +72 -0
  150. vidbyte_cli/lib/files/__init__.py +6 -0
  151. vidbyte_cli/lib/files/documents.py +94 -0
  152. vidbyte_cli/lib/files/store.py +156 -0
  153. vidbyte_cli/lib/io/__init__.py +16 -0
  154. vidbyte_cli/lib/io/attachments.py +129 -0
  155. vidbyte_cli/lib/io/codex_attachments.py +47 -0
  156. vidbyte_cli/lib/io/prompt.py +71 -0
  157. vidbyte_cli/lib/io/streams.py +37 -0
  158. vidbyte_cli/lib/io/terminal.py +66 -0
  159. vidbyte_cli/lib/output/__init__.py +7 -0
  160. vidbyte_cli/lib/output/formats.py +29 -0
  161. vidbyte_cli/lib/output/manager.py +95 -0
  162. vidbyte_cli/lib/output/models.py +49 -0
  163. vidbyte_cli/lib/runtime/__init__.py +9 -0
  164. vidbyte_cli/lib/runtime/application.py +134 -0
  165. vidbyte_cli/lib/runtime/context.py +250 -0
  166. vidbyte_cli/lib/runtime/options.py +159 -0
  167. vidbyte_cli/lib/runtime/version.py +30 -0
  168. vidbyte_cli/lib/runtime_primitives/__init__.py +28 -0
  169. vidbyte_cli/lib/runtime_primitives/executor.py +54 -0
  170. vidbyte_cli/lib/runtime_primitives/gate.py +105 -0
  171. vidbyte_cli/lib/runtime_primitives/hosts.py +45 -0
  172. vidbyte_cli/lib/runtime_primitives/planner.py +79 -0
  173. vidbyte_cli/lib/runtime_primitives/stages.py +338 -0
  174. vidbyte_cli/lib/runtime_primitives/task_board.py +982 -0
  175. vidbyte_cli/lib/runtime_primitives/task_board_checkpoints.py +473 -0
  176. vidbyte_cli/lib/runtime_primitives/task_board_system.md +33 -0
  177. vidbyte_cli/lib/runtime_primitives/task_board_tasks.py +111 -0
  178. vidbyte_cli/lib/runtime_primitives/verification.py +68 -0
  179. vidbyte_cli/services/__init__.py +5 -0
  180. vidbyte_cli/services/ensemble/__init__.py +7 -0
  181. vidbyte_cli/services/ensemble/prompts/__init__.py +6 -0
  182. vidbyte_cli/services/ensemble/prompts/implementer_system.md +546 -0
  183. vidbyte_cli/services/ensemble/prompts/implementer_turn.md +49 -0
  184. vidbyte_cli/services/ensemble/prompts/library.py +188 -0
  185. vidbyte_cli/services/ensemble/prompts/planner_system.md +728 -0
  186. vidbyte_cli/services/ensemble/prompts/planner_turn.md +6 -0
  187. vidbyte_cli/services/ensemble/prompts/role_system.md +104 -0
  188. vidbyte_cli/services/ensemble/prompts/role_turn.md +19 -0
  189. vidbyte_cli/services/ensemble/prompts/selector_final_turn.md +19 -0
  190. vidbyte_cli/services/ensemble/prompts/selector_round_turn.md +17 -0
  191. vidbyte_cli/services/ensemble/prompts/selector_system.md +89 -0
  192. vidbyte_cli/services/ensemble/runner.py +30 -0
  193. vidbyte_cli/services/ensemble/sdk.py +134 -0
  194. vidbyte_cli/services/ensemble/service.py +233 -0
  195. vidbyte_cli/services/ensemble/settings.py +60 -0
  196. vidbyte_cli/services/persistence/__init__.py +6 -0
  197. vidbyte_cli/services/persistence/prompts/__init__.py +6 -0
  198. vidbyte_cli/services/persistence/prompts/library.py +46 -0
  199. vidbyte_cli/services/persistence/prompts/persistence_system.md +1 -0
  200. vidbyte_cli/services/persistence/prompts/persistence_turn.md +3 -0
  201. vidbyte_cli/services/persistence/runner.py +35 -0
  202. vidbyte_cli/services/persistence/session.py +116 -0
  203. vidbyte_cli/services/suggestions/__init__.py +7 -0
  204. vidbyte_cli/services/suggestions/categories.py +270 -0
  205. vidbyte_cli/services/suggestions/context.py +222 -0
  206. vidbyte_cli/services/suggestions/extra_compute.py +53 -0
  207. vidbyte_cli/services/suggestions/handoff.py +173 -0
  208. vidbyte_cli/services/suggestions/message_tools.py +47 -0
  209. vidbyte_cli/services/suggestions/project.py +253 -0
  210. vidbyte_cli/services/suggestions/prompts/__init__.py +1 -0
  211. vidbyte_cli/services/suggestions/prompts/categories/__init__.py +3 -0
  212. vidbyte_cli/services/suggestions/prompts/categories/adjacent_opportunity.md +37 -0
  213. vidbyte_cli/services/suggestions/prompts/categories/alternative.md +38 -0
  214. vidbyte_cli/services/suggestions/prompts/categories/big_bets.md +37 -0
  215. vidbyte_cli/services/suggestions/prompts/categories/bottleneck.md +38 -0
  216. vidbyte_cli/services/suggestions/prompts/categories/business_growth.md +38 -0
  217. vidbyte_cli/services/suggestions/prompts/categories/completion.md +37 -0
  218. vidbyte_cli/services/suggestions/prompts/categories/continuation.md +37 -0
  219. vidbyte_cli/services/suggestions/prompts/categories/coordination.md +37 -0
  220. vidbyte_cli/services/suggestions/prompts/categories/creative_exploration.md +37 -0
  221. vidbyte_cli/services/suggestions/prompts/categories/cross_domain.md +37 -0
  222. vidbyte_cli/services/suggestions/prompts/categories/delegation.md +37 -0
  223. vidbyte_cli/services/suggestions/prompts/categories/experiment.md +37 -0
  224. vidbyte_cli/services/suggestions/prompts/categories/feedback.md +37 -0
  225. vidbyte_cli/services/suggestions/prompts/categories/goal_clarification.md +37 -0
  226. vidbyte_cli/services/suggestions/prompts/categories/immediate_next_steps.md +37 -0
  227. vidbyte_cli/services/suggestions/prompts/categories/investigation.md +37 -0
  228. vidbyte_cli/services/suggestions/prompts/categories/learning.md +37 -0
  229. vidbyte_cli/services/suggestions/prompts/categories/leverage.md +37 -0
  230. vidbyte_cli/services/suggestions/prompts/categories/long_term_directions.md +37 -0
  231. vidbyte_cli/services/suggestions/prompts/categories/preparation.md +37 -0
  232. vidbyte_cli/services/suggestions/prompts/categories/prerequisite.md +37 -0
  233. vidbyte_cli/services/suggestions/prompts/categories/prioritization.md +37 -0
  234. vidbyte_cli/services/suggestions/prompts/categories/product_experience.md +37 -0
  235. vidbyte_cli/services/suggestions/prompts/categories/quick_wins.md +37 -0
  236. vidbyte_cli/services/suggestions/prompts/categories/reframing.md +37 -0
  237. vidbyte_cli/services/suggestions/prompts/categories/risk_prevention.md +37 -0
  238. vidbyte_cli/services/suggestions/prompts/categories/simplification.md +37 -0
  239. vidbyte_cli/services/suggestions/prompts/categories/stop_or_defer.md +37 -0
  240. vidbyte_cli/services/suggestions/prompts/categories/strategy.md +37 -0
  241. vidbyte_cli/services/suggestions/prompts/categories/technical_possibilities.md +37 -0
  242. vidbyte_cli/services/suggestions/prompts/categories/verification.md +37 -0
  243. vidbyte_cli/services/suggestions/prompts/critic.md +209 -0
  244. vidbyte_cli/services/suggestions/prompts/critic_message.md +8 -0
  245. vidbyte_cli/services/suggestions/prompts/generator.md +64 -0
  246. vidbyte_cli/services/suggestions/prompts/library.py +72 -0
  247. vidbyte_cli/services/suggestions/prompts/message_generator_argument.md +1 -0
  248. vidbyte_cli/services/suggestions/prompts/message_generator_tool.md +1 -0
  249. vidbyte_cli/services/suggestions/prompts/message_parent_argument.md +1 -0
  250. vidbyte_cli/services/suggestions/prompts/message_parent_tool.md +1 -0
  251. vidbyte_cli/services/suggestions/prompts/message_receipt.md +1 -0
  252. vidbyte_cli/services/suggestions/prompts/revision.md +8 -0
  253. vidbyte_cli/services/suggestions/sdk.py +228 -0
  254. vidbyte_cli/services/suggestions/selection.py +78 -0
  255. vidbyte_cli/services/suggestions/service.py +540 -0
  256. vidbyte_cli/types/__init__.py +0 -0
  257. vidbyte_cli/types/api.py +101 -0
  258. vidbyte_cli/types/attachments.py +79 -0
  259. vidbyte_cli/types/ensemble.py +189 -0
  260. vidbyte_cli/types/provider.py +62 -0
  261. vidbyte_cli/types/research.py +260 -0
  262. vidbyte_cli/types/runtime.py +1098 -0
  263. vidbyte_cli/types/suggestions.py +518 -0
  264. vidbyte_cli-0.1.0.dist-info/METADATA +335 -0
  265. vidbyte_cli-0.1.0.dist-info/RECORD +269 -0
  266. vidbyte_cli-0.1.0.dist-info/WHEEL +5 -0
  267. vidbyte_cli-0.1.0.dist-info/entry_points.txt +2 -0
  268. vidbyte_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  269. vidbyte_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,518 @@
1
+ """Strict contracts shared by the suggestion command and service.
2
+
3
+ The models define the in-process boundary before provider work begins and the
4
+ versioned envelopes emitted afterward. Provider adapters and command rendering
5
+ stay outside this module.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ from collections.abc import Mapping
12
+ from dataclasses import dataclass, field
13
+ from enum import StrEnum
14
+ from typing import Literal
15
+
16
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
17
+
18
+ from .attachments import AttachmentBundle
19
+
20
+ SCHEMA_VERSION = 1
21
+ MAX_CONTEXT_CHARS = 5_000_000
22
+ MAX_AGENT_MESSAGE_CHARS = 2_000
23
+
24
+ SUGGESTIONS_RESULT_KIND = "suggestions.result"
25
+ SUGGESTIONS_HANDOFF_KIND = "suggestions.handoff"
26
+ _FEEDBACK_REASON_SEPARATOR = "\nReason: "
27
+
28
+
29
+ class SuggestionHorizon(StrEnum):
30
+ """When a suggestion should become useful to its caller."""
31
+
32
+ NOW = "now"
33
+ NEXT = "next"
34
+ LATER = "later"
35
+ ANY = "any"
36
+
37
+
38
+ class IdeaHorizon(StrEnum):
39
+ """Horizon value stored on one idea; never the ``any`` filter wildcard."""
40
+
41
+ NOW = "now"
42
+ NEXT = "next"
43
+ LATER = "later"
44
+
45
+
46
+ class IdeaRelationship(StrEnum):
47
+ """How an idea relates to the caller's stated goal."""
48
+
49
+ DIRECT = "direct"
50
+ ALTERNATIVE = "alternative"
51
+ ADJACENT = "adjacent"
52
+ EXPLORATORY = "exploratory"
53
+
54
+
55
+ class IdeaReadiness(StrEnum):
56
+ """What must happen before an executor can start the idea."""
57
+
58
+ READY = "ready"
59
+ NEEDS_EVIDENCE = "needs_evidence"
60
+ NEEDS_DECISION = "needs_decision"
61
+ BLOCKED = "blocked"
62
+
63
+
64
+ class RunStatus(StrEnum):
65
+ """Terminal state of one suggestion run."""
66
+
67
+ COMPLETE = "complete"
68
+ PARTIAL = "partial"
69
+ NO_SUGGESTIONS = "no_suggestions"
70
+ NEEDS_INPUT = "needs_input"
71
+
72
+
73
+ class StopReason(StrEnum):
74
+ """Why the workflow stopped when it did."""
75
+
76
+ COMPLETED = "completed"
77
+ COUNT_SHORTFALL = "count_shortfall"
78
+ ROUND_LIMIT = "round_limit"
79
+ TOKEN_LIMIT = "token_limit"
80
+ TIME_LIMIT = "time_limit"
81
+ PROVIDER_FAILED = "provider_failed"
82
+ DRY_RUN = "dry_run"
83
+ PARENT_MESSAGE = "parent_message"
84
+
85
+
86
+ class SuggestionContextItem(BaseModel):
87
+ """One labeled piece of caller-supplied context with a stable run-local ref."""
88
+
89
+ model_config = ConfigDict(extra="forbid", frozen=True)
90
+ ref: str = Field(min_length=1, max_length=32)
91
+ kind: str = Field(min_length=1, max_length=64)
92
+ label: str = Field(min_length=1, max_length=256)
93
+ description: str = Field(min_length=1, max_length=4096)
94
+ content: str = Field(min_length=1, max_length=MAX_CONTEXT_CHARS)
95
+ source: str = Field(min_length=1, max_length=512)
96
+ caller_supplied: bool = True
97
+
98
+
99
+ @dataclass(frozen=True, slots=True)
100
+ class SuggestionContextPrimitive:
101
+ """SDK context primitive for one exact generator or critic context window."""
102
+
103
+ goal: str
104
+ description: str
105
+ items: tuple[SuggestionContextItem, ...] = ()
106
+ selected_categories: str = ""
107
+ kind: str = field(default="suggestion-context", init=False)
108
+ title: str = field(default="Suggestion Agent Context", init=False)
109
+ metadata: Mapping[str, object] = field(default_factory=dict)
110
+ primitive_id: str = field(default="suggestion-context:request", init=False)
111
+ primitive_frozen: bool = field(default=True, init=False)
112
+
113
+ def __post_init__(self) -> None:
114
+ if not isinstance(self.goal, str) or not self.goal.strip():
115
+ raise ValueError("Suggestion context goal must be a non-empty string.")
116
+ if not isinstance(self.description, str) or not self.description.strip():
117
+ raise ValueError("Suggestion context description must be a non-empty string.")
118
+ if not isinstance(self.items, tuple) or any(
119
+ not isinstance(item, SuggestionContextItem) for item in self.items
120
+ ):
121
+ raise TypeError("Suggestion context items must be a tuple of SuggestionContextItem.")
122
+ if not isinstance(self.metadata, Mapping):
123
+ raise TypeError("Suggestion context metadata must be a mapping.")
124
+
125
+ def to_context_text(self) -> str:
126
+ sections = [f"Goal: {self.goal}", "", "<Selected Categories>"]
127
+ sections.append(self.selected_categories or "No category was selected.")
128
+ sections.extend(("</Selected Categories>", ""))
129
+ for item in self.items:
130
+ sections.extend(
131
+ (
132
+ f"## [{item.ref}] {item.label} ({item.kind})",
133
+ f"Source: {item.source}",
134
+ item.content,
135
+ "",
136
+ )
137
+ )
138
+ return "\n".join(sections).rstrip()
139
+
140
+
141
+ class FeedbackType(StrEnum):
142
+ """The explicit user reaction stored in project memory."""
143
+
144
+ ACCEPTED = "accepted"
145
+ REJECTED = "rejected"
146
+
147
+
148
+ class SuggestionProjectRecord(BaseModel):
149
+ """One catalog entry linking a project key to its memory file."""
150
+
151
+ model_config = ConfigDict(extra="forbid", frozen=True)
152
+ key: str = Field(min_length=1, max_length=64, pattern=r"^[a-z0-9][a-z0-9_-]{0,63}$")
153
+ title: str = Field(min_length=1, max_length=200)
154
+ description: str = Field(min_length=1, max_length=4000)
155
+ memory_file: str = Field(min_length=1, max_length=256)
156
+
157
+
158
+ class SuggestionProjectCatalog(BaseModel):
159
+ """Versioned catalog document containing all project summaries."""
160
+
161
+ model_config = ConfigDict(extra="forbid", frozen=True)
162
+ schema_version: Literal[1] = 1
163
+ projects: tuple[SuggestionProjectRecord, ...] = ()
164
+
165
+
166
+ class SuggestionFeedback(BaseModel):
167
+ """One accepted or rejected suggestion recorded by an explicit user reaction."""
168
+
169
+ model_config = ConfigDict(extra="forbid", frozen=True)
170
+ type: FeedbackType
171
+ suggestion: str = Field(min_length=1, max_length=8192)
172
+ reason: str | None = Field(default=None, max_length=8192)
173
+ created_at: str = Field(min_length=1, max_length=64)
174
+
175
+ def context_text(self) -> str:
176
+ # The one rendering of a reaction as run context; suggestion_from_context inverts it,
177
+ # so the label and reason separator are defined together and cannot drift apart.
178
+ reason = f"{_FEEDBACK_REASON_SEPARATOR}{self.reason}" if self.reason else ""
179
+ return f"{self.type.value.title()} suggestion: {self.suggestion}{reason}"
180
+
181
+ @staticmethod
182
+ def suggestion_from_context(text: str) -> str:
183
+ # Recovers the verbatim suggestion so rejected feedback can suppress a repeated idea.
184
+ body = text.split(": ", 1)[1] if ": " in text else text
185
+ return body.split(_FEEDBACK_REASON_SEPARATOR, 1)[0].strip()
186
+
187
+
188
+ class SuggestionProjectMemory(BaseModel):
189
+ """Versioned per-project feedback document."""
190
+
191
+ model_config = ConfigDict(extra="forbid", frozen=True)
192
+ schema_version: Literal[1] = 1
193
+ project_key: str = Field(min_length=1, max_length=64)
194
+ feedback: tuple[SuggestionFeedback, ...] = ()
195
+
196
+
197
+ class SuggestionFeedbackCapture(BaseModel):
198
+ """Deterministic instructions a parent agent can use after explicit feedback."""
199
+
200
+ model_config = ConfigDict(extra="forbid", frozen=True)
201
+ project_key: str = Field(min_length=1, max_length=64)
202
+ instruction: str = Field(min_length=1, max_length=1024)
203
+ accept_command: str = Field(min_length=1, max_length=1024)
204
+ reject_command: str = Field(min_length=1, max_length=1024)
205
+
206
+
207
+ class ContextManifestEntry(BaseModel):
208
+ """What the manifest records about one context item without its body."""
209
+
210
+ model_config = ConfigDict(extra="forbid", frozen=True)
211
+ ref: str = Field(min_length=1, max_length=32)
212
+ kind: str = Field(min_length=1, max_length=64)
213
+ source: str = Field(min_length=1, max_length=512)
214
+ chars: int = Field(ge=0, le=MAX_CONTEXT_CHARS)
215
+ sha256: str = Field(min_length=8, max_length=128)
216
+ status: Literal["included", "truncated", "omitted", "not_supplied"] = "included"
217
+
218
+
219
+ class SuggestionSettings(BaseModel):
220
+ """The one validated settings object passed unchanged through the workflow."""
221
+
222
+ model_config = ConfigDict(extra="forbid", frozen=True)
223
+ requested_count: int = Field(ge=2, le=15, default=5)
224
+ categories: tuple[str, ...] = ()
225
+ all_categories: bool = False
226
+ extra_compute: bool = False
227
+ horizon: SuggestionHorizon = SuggestionHorizon.ANY
228
+ rounds: int = Field(ge=1, le=8, default=2)
229
+ max_messages: int = Field(ge=0, le=8, default=2)
230
+ provider: str | None = None
231
+ critic_model: str | None = None
232
+ max_output_tokens: int | None = Field(default=None, gt=0, le=5_000_000)
233
+ max_total_tokens: int | None = Field(default=None, gt=0, le=20_000_000)
234
+ timeout_seconds: int | None = Field(default=None, gt=0, le=86_400)
235
+ dry_run: bool = False
236
+
237
+ @model_validator(mode="after")
238
+ def _validate_categories(self) -> SuggestionSettings:
239
+ if self.all_categories and self.categories:
240
+ raise ValueError("all_categories and categories cannot both be selected.")
241
+ return self
242
+
243
+
244
+ class SuggestionRequest(BaseModel):
245
+ """Validated input to one suggestion run, built by the command layer."""
246
+
247
+ model_config = ConfigDict(extra="forbid", frozen=True, arbitrary_types_allowed=True)
248
+ goal: str = Field(min_length=1, max_length=4096)
249
+ context: SuggestionContextPrimitive
250
+ context_manifest: tuple[ContextManifestEntry, ...] = ()
251
+ context_warnings: tuple[str, ...] = ()
252
+ settings: SuggestionSettings
253
+ attachments: AttachmentBundle = Field(default_factory=AttachmentBundle)
254
+ project_key: str | None = Field(default=None, max_length=64)
255
+ prompt_version: str = Field(min_length=1, max_length=64, default="suggestions.v4")
256
+
257
+ @model_validator(mode="after")
258
+ def _context_goal_matches(self) -> SuggestionRequest:
259
+ if self.goal != self.context.goal:
260
+ raise ValueError("Suggestion request goal must match its context primitive goal.")
261
+ return self
262
+
263
+ @property
264
+ def context_items(self) -> tuple[SuggestionContextItem, ...]:
265
+ return self.context.items
266
+
267
+
268
+ class SuggestionDraft(BaseModel):
269
+ """One model-produced candidate before the CLI assigns identity and a handoff."""
270
+
271
+ model_config = ConfigDict(extra="forbid", frozen=True)
272
+ idea_id: str | None = Field(default=None, pattern=r"^idea-\d{3}$")
273
+ title: str = Field(min_length=1, max_length=256)
274
+ summary: str = Field(min_length=1, max_length=2048)
275
+ primary_category: str = Field(min_length=1, max_length=64)
276
+ secondary_categories: tuple[str, ...] = ()
277
+ horizon: IdeaHorizon = IdeaHorizon.NEXT
278
+ relationship: IdeaRelationship = IdeaRelationship.DIRECT
279
+ readiness: IdeaReadiness = IdeaReadiness.READY
280
+ why_now: str = Field(min_length=1, max_length=1024)
281
+ expected_benefit: str = Field(min_length=1, max_length=1024)
282
+ evidence_refs: tuple[str, ...] = ()
283
+ assumptions: tuple[str, ...] = ()
284
+ dependencies: tuple[str, ...] = ()
285
+ alternative_to: tuple[str, ...] = ()
286
+ first_action: str = Field(min_length=1, max_length=1024)
287
+ suggested_actions: tuple[str, ...] = Field(min_length=1, max_length=12)
288
+ decision_points: tuple[str, ...] = Field(default=(), max_length=12)
289
+ considerations: tuple[str, ...] = Field(default=(), min_length=8, max_length=10)
290
+ completion_criteria: str = Field(min_length=1, max_length=1024)
291
+ effort_estimate: str = Field(min_length=1, max_length=256)
292
+
293
+
294
+ class SuggestionCandidateBatch(BaseModel):
295
+ """Structured output requested from the generator for initial and revision turns."""
296
+
297
+ model_config = ConfigDict(extra="forbid", frozen=True)
298
+ ideas: tuple[SuggestionDraft, ...] = Field(max_length=30)
299
+
300
+
301
+ class SuggestionCriticHandoff(BaseModel):
302
+ """One general review of the whole candidate slate, handed to the generator."""
303
+
304
+ model_config = ConfigDict(extra="forbid", frozen=True)
305
+ handoff: str = Field(
306
+ min_length=1,
307
+ max_length=16384,
308
+ description=(
309
+ "The critic's complete review of the candidate slate, written for the generator "
310
+ "that will refine it. Grade the slate against the general suggestion rubric, name "
311
+ "candidates by identifier where a point concerns them, and cite the context "
312
+ "references behind every factual claim. The generator weighs this handoff with its "
313
+ "own judgment, so explain what is strong, what is weak, and why, rather than "
314
+ "issuing keep, revise, or reject verdicts."
315
+ ),
316
+ )
317
+
318
+
319
+ def _packet(models: tuple[BaseModel, ...]) -> str:
320
+ """Render one typed model tuple as the compact JSON a model window reads."""
321
+ return json.dumps(
322
+ [model.model_dump(mode="json") for model in models],
323
+ sort_keys=True,
324
+ separators=(",", ":"),
325
+ )
326
+
327
+
328
+ @dataclass(frozen=True, slots=True)
329
+ class SuggestionEvidence:
330
+ """One supplied record as a model sees it: its stable ref, its kind, its body."""
331
+
332
+ ref: str
333
+ kind: str
334
+ content: str
335
+
336
+
337
+ @dataclass(frozen=True, slots=True)
338
+ class SuggestionAgentContext:
339
+ """SDK context primitive holding exactly what one workflow stage may read.
340
+
341
+ Caller-only data has no field here at all, so a stage cannot place a source
342
+ path, a label, a manifest entry, a setting, or workflow state such as rank,
343
+ revision, or handoff into a model window. Candidates travel as the same
344
+ `SuggestionDraft` contract the generator returns, so no parallel projection
345
+ can drift.
346
+ """
347
+
348
+ description: str
349
+ evidence: tuple[SuggestionEvidence, ...] = ()
350
+ selected_categories: str = ""
351
+ candidates: tuple[SuggestionDraft, ...] = ()
352
+ kind: str = field(default="suggestion-context", init=False)
353
+ title: str = field(default="Suggestion Agent Context", init=False)
354
+ metadata: Mapping[str, object] = field(default_factory=dict, init=False)
355
+ primitive_id: str = field(default="suggestion-context:stage", init=False)
356
+ primitive_frozen: bool = field(default=True, init=False)
357
+
358
+ def __post_init__(self) -> None:
359
+ if not isinstance(self.description, str) or not self.description.strip():
360
+ raise ValueError("Suggestion agent context description must be a non-empty string.")
361
+
362
+ @classmethod
363
+ def for_stage(
364
+ cls,
365
+ context: SuggestionContextPrimitive,
366
+ selected_categories: str,
367
+ candidates: tuple[SuggestionDraft, ...] = (),
368
+ ) -> SuggestionAgentContext:
369
+ """Build one stage window from the caller snapshot without mutating it."""
370
+ return cls(
371
+ description="Stage context holds only evidence, category guidance, and candidates.",
372
+ evidence=tuple(
373
+ SuggestionEvidence(item.ref, item.kind, item.content) for item in context.items
374
+ ),
375
+ selected_categories=selected_categories,
376
+ candidates=candidates,
377
+ )
378
+
379
+ def to_context_text(self) -> str:
380
+ # Every stage prompt already states the goal, so this window stays task data only.
381
+ sections = [
382
+ "<Selected Categories>",
383
+ self.selected_categories or "No category was selected.",
384
+ "</Selected Categories>",
385
+ "",
386
+ ]
387
+ for item in self.evidence:
388
+ sections.extend((f"## [{item.ref}] {item.kind}", item.content, ""))
389
+ if self.candidates:
390
+ sections.extend(("<Candidates>", _packet(self.candidates), "</Candidates>"))
391
+ return "\n".join(sections).rstrip()
392
+
393
+
394
+ class SuggestionHandoffEvidence(BaseModel):
395
+ """One context record embedded in a final action handoff."""
396
+
397
+ model_config = ConfigDict(extra="forbid", frozen=True)
398
+ ref: str = Field(min_length=1, max_length=32)
399
+ source: str = Field(min_length=1, max_length=512)
400
+ content: str = Field(min_length=1, max_length=4096)
401
+ relevance: str = Field(min_length=1, max_length=1024)
402
+
403
+
404
+ class SuggestionHandoff(BaseModel):
405
+ """Action-centered packet for evaluating and executing one selected idea."""
406
+
407
+ model_config = ConfigDict(extra="forbid", frozen=True)
408
+ handoff_version: Literal[1] = 1
409
+ idea_id: str = Field(min_length=1, max_length=64)
410
+ idea_revision: int = Field(ge=1)
411
+ original_goal: str = Field(min_length=1, max_length=4096)
412
+ title: str = Field(min_length=1, max_length=256)
413
+ summary: str = Field(min_length=1, max_length=2048)
414
+ suggested_actions: tuple[str, ...] = Field(min_length=1, max_length=12)
415
+ decisions_along_way: tuple[str, ...] = Field(default=(), max_length=12)
416
+ considerations: tuple[str, ...] = Field(default=(), min_length=8, max_length=10)
417
+ evidence: tuple[SuggestionHandoffEvidence, ...] = ()
418
+ completion_checks: tuple[str, ...] = Field(min_length=1, max_length=12)
419
+ dependencies: tuple[str, ...] = ()
420
+ warnings: tuple[str, ...] = ()
421
+ forbidden_actions: tuple[str, ...] = ()
422
+ authority: Literal["not_granted_by_this_handoff"] = "not_granted_by_this_handoff"
423
+ stop_conditions: tuple[str, ...] = Field(min_length=1)
424
+ return_report: str = Field(min_length=1, max_length=2048)
425
+ execution_prompt: str = Field(min_length=1, max_length=16384)
426
+
427
+
428
+ class SuggestionIdea(BaseModel):
429
+ """One ranked suggestion with its deterministic handoff."""
430
+
431
+ model_config = ConfigDict(extra="forbid", frozen=True)
432
+ id: str = Field(pattern=r"^idea-\d{3}$")
433
+ revision: int = Field(ge=1)
434
+ rank: int = Field(ge=1)
435
+ title: str = Field(min_length=1, max_length=256)
436
+ summary: str = Field(min_length=1, max_length=2048)
437
+ primary_category: str = Field(min_length=1, max_length=64)
438
+ secondary_categories: tuple[str, ...] = ()
439
+ horizon: IdeaHorizon = IdeaHorizon.NEXT
440
+ relationship: IdeaRelationship = IdeaRelationship.DIRECT
441
+ readiness: IdeaReadiness = IdeaReadiness.READY
442
+ why_now: str = Field(min_length=1, max_length=1024)
443
+ expected_benefit: str = Field(min_length=1, max_length=1024)
444
+ evidence_refs: tuple[str, ...] = ()
445
+ assumptions: tuple[str, ...] = ()
446
+ dependencies: tuple[str, ...] = ()
447
+ alternative_to: tuple[str, ...] = ()
448
+ first_action: str = Field(min_length=1, max_length=1024)
449
+ suggested_actions: tuple[str, ...] = Field(min_length=1, max_length=12)
450
+ decision_points: tuple[str, ...] = Field(default=(), max_length=12)
451
+ considerations: tuple[str, ...] = Field(default=(), min_length=8, max_length=10)
452
+ completion_criteria: str = Field(min_length=1, max_length=1024)
453
+ effort_estimate: str = Field(min_length=1, max_length=256)
454
+ handoff: SuggestionHandoff
455
+
456
+ def to_draft(self) -> SuggestionDraft:
457
+ """Project this reviewed idea back to the candidate contract agents reason over."""
458
+ # Identity stays as idea_id; CLI-owned workflow fields stay behind.
459
+ values = self.model_dump(exclude={"id", "revision", "rank", "handoff"})
460
+ return SuggestionDraft.model_validate({**values, "idea_id": self.id})
461
+
462
+
463
+ class SuggestionResult(BaseModel):
464
+ """Whole validated outcome of one run, including shortfalls and usage."""
465
+
466
+ model_config = ConfigDict(extra="forbid", frozen=True)
467
+ run_id: str = Field(min_length=1, max_length=64)
468
+ status: RunStatus = RunStatus.COMPLETE
469
+ goal: str = Field(min_length=1, max_length=4096)
470
+ project_key: str | None = Field(default=None, max_length=64)
471
+ requested_count: int = Field(ge=2, le=15)
472
+ returned_count: int = Field(ge=0, le=15)
473
+ settings: SuggestionSettings
474
+ context_manifest: tuple[ContextManifestEntry, ...] = ()
475
+ ideas: tuple[SuggestionIdea, ...] = ()
476
+ category_coverage: dict[str, int] = Field(default_factory=dict)
477
+ missing_context: tuple[str, ...] = ()
478
+ warnings: tuple[str, ...] = ()
479
+ usage: dict[str, int] = Field(default_factory=dict)
480
+ stop_reason: StopReason = StopReason.COMPLETED
481
+ parent_message: str | None = Field(default=None, max_length=MAX_AGENT_MESSAGE_CHARS)
482
+ feedback_capture: SuggestionFeedbackCapture | None = None
483
+ prompt_version: str = Field(min_length=1, max_length=64, default="suggestions.v4")
484
+
485
+
486
+ __all__ = [
487
+ "ContextManifestEntry",
488
+ "FeedbackType",
489
+ "IdeaHorizon",
490
+ "IdeaReadiness",
491
+ "IdeaRelationship",
492
+ "MAX_AGENT_MESSAGE_CHARS",
493
+ "MAX_CONTEXT_CHARS",
494
+ "RunStatus",
495
+ "SCHEMA_VERSION",
496
+ "SUGGESTIONS_HANDOFF_KIND",
497
+ "SUGGESTIONS_RESULT_KIND",
498
+ "StopReason",
499
+ "SuggestionAgentContext",
500
+ "SuggestionCandidateBatch",
501
+ "SuggestionContextItem",
502
+ "SuggestionContextPrimitive",
503
+ "SuggestionCriticHandoff",
504
+ "SuggestionDraft",
505
+ "SuggestionFeedback",
506
+ "SuggestionFeedbackCapture",
507
+ "SuggestionEvidence",
508
+ "SuggestionHandoff",
509
+ "SuggestionHandoffEvidence",
510
+ "SuggestionHorizon",
511
+ "SuggestionIdea",
512
+ "SuggestionProjectCatalog",
513
+ "SuggestionProjectMemory",
514
+ "SuggestionProjectRecord",
515
+ "SuggestionRequest",
516
+ "SuggestionResult",
517
+ "SuggestionSettings",
518
+ ]