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.
- vidbyte_cli/__init__.py +11 -0
- vidbyte_cli/__main__.py +6 -0
- vidbyte_cli/cli.py +18 -0
- vidbyte_cli/commands/__init__.py +86 -0
- vidbyte_cli/commands/agent_options.py +53 -0
- vidbyte_cli/commands/agents/__init__.py +31 -0
- vidbyte_cli/commands/agents/suggestion/__init__.py +38 -0
- vidbyte_cli/commands/agents/suggestion/prompts/__init__.py +3 -0
- vidbyte_cli/commands/agents/suggestion/prompts/all_categories.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/approaches.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/avoid.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/blockers.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/capability.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/categories.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/category.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/completed.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/constraint.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/context.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/count.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/critic_model.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/decision.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/dry_run.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/extra_compute.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_accept.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_group.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_project.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_reason.md +98 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_reject.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/feedback_suggestion.md +101 -0
- vidbyte_cli/commands/agents/suggestion/prompts/files.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/forbidden.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/goal.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/handoff.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/horizon.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/hypotheses.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/idea.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/in_progress.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/input.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/library.py +29 -0
- vidbyte_cli/commands/agents/suggestion/prompts/max_messages.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/max_output_tokens.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/max_total_tokens.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/mistakes.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/outcomes.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_create.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_description.md +98 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_group.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_key.md +96 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_list.md +7 -0
- vidbyte_cli/commands/agents/suggestion/prompts/project_title.md +99 -0
- vidbyte_cli/commands/agents/suggestion/prompts/provider.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/question.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/risks.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/rounds.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/run.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/success.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/timeout.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/trajectory.md +100 -0
- vidbyte_cli/commands/agents/suggestion/prompts/view.md +5 -0
- vidbyte_cli/commands/agents/suggestion/prompts/view_all.md +5 -0
- vidbyte_cli/commands/agents/suggestion/render.py +77 -0
- vidbyte_cli/commands/agents/suggestion/request_builder.py +240 -0
- vidbyte_cli/commands/agents/suggestion/suggest.py +154 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_categories.py +77 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_feedback.py +85 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_handoff.py +67 -0
- vidbyte_cli/commands/agents/suggestion/suggestion_projects.py +95 -0
- vidbyte_cli/commands/auth/__init__.py +0 -0
- vidbyte_cli/commands/auth/login.py +100 -0
- vidbyte_cli/commands/auth/logout.py +41 -0
- vidbyte_cli/commands/auth/whoami.py +51 -0
- vidbyte_cli/commands/billing/__init__.py +20 -0
- vidbyte_cli/commands/billing/prompts/__init__.py +1 -0
- vidbyte_cli/commands/billing/prompts/confirm.md +16 -0
- vidbyte_cli/commands/billing/prompts/group.md +13 -0
- vidbyte_cli/commands/billing/prompts/idempotency_key.md +16 -0
- vidbyte_cli/commands/billing/prompts/library.py +18 -0
- vidbyte_cli/commands/billing/prompts/top_up.md +16 -0
- vidbyte_cli/commands/billing/top_up.py +65 -0
- vidbyte_cli/commands/config/__init__.py +0 -0
- vidbyte_cli/commands/config/get.py +45 -0
- vidbyte_cli/commands/config/set.py +39 -0
- vidbyte_cli/commands/provider/__init__.py +9 -0
- vidbyte_cli/commands/provider/login.py +102 -0
- vidbyte_cli/commands/provider/logout.py +45 -0
- vidbyte_cli/commands/provider/whoami.py +49 -0
- vidbyte_cli/commands/research/__init__.py +5 -0
- vidbyte_cli/commands/research/add.py +41 -0
- vidbyte_cli/commands/research/options.py +134 -0
- vidbyte_cli/commands/research/render.py +157 -0
- vidbyte_cli/commands/research/resume.py +37 -0
- vidbyte_cli/commands/research/start.py +37 -0
- vidbyte_cli/commands/research/status.py +32 -0
- vidbyte_cli/commands/research/thread.py +30 -0
- vidbyte_cli/commands/research/threads.py +35 -0
- vidbyte_cli/commands/research/watch.py +93 -0
- vidbyte_cli/commands/runtime/__init__.py +23 -0
- vidbyte_cli/commands/runtime/adversarial_team.py +41 -0
- vidbyte_cli/commands/runtime/doctor.py +44 -0
- vidbyte_cli/commands/runtime/list.py +47 -0
- vidbyte_cli/commands/runtime/persistence.py +141 -0
- vidbyte_cli/commands/runtime/same_host_ensemble.py +221 -0
- vidbyte_cli/commands/runtime/stages.py +553 -0
- vidbyte_cli/commands/runtime/task_board.py +1342 -0
- vidbyte_cli/commands/setup/__init__.py +0 -0
- vidbyte_cli/commands/setup/doctor.py +64 -0
- vidbyte_cli/commands/setup/prompts/__init__.py +1 -0
- vidbyte_cli/commands/setup/prompts/browser.md +7 -0
- vidbyte_cli/commands/setup/prompts/library.py +18 -0
- vidbyte_cli/commands/setup/prompts/star.md +13 -0
- vidbyte_cli/commands/setup/star.py +132 -0
- vidbyte_cli/lib/__init__.py +0 -0
- vidbyte_cli/lib/api/__init__.py +0 -0
- vidbyte_cli/lib/api/client.py +236 -0
- vidbyte_cli/lib/api/endpoints/__init__.py +0 -0
- vidbyte_cli/lib/api/endpoints/auth.py +24 -0
- vidbyte_cli/lib/api/endpoints/billing.py +36 -0
- vidbyte_cli/lib/api/endpoints/research.py +103 -0
- vidbyte_cli/lib/api/endpoints/runtime.py +113 -0
- vidbyte_cli/lib/api/problem.py +86 -0
- vidbyte_cli/lib/api/response.py +101 -0
- vidbyte_cli/lib/api/retry.py +116 -0
- vidbyte_cli/lib/api/runtime_payment.py +89 -0
- vidbyte_cli/lib/auth/__init__.py +16 -0
- vidbyte_cli/lib/auth/credentials.py +223 -0
- vidbyte_cli/lib/auth/input.py +59 -0
- vidbyte_cli/lib/auth/keyring_store.py +126 -0
- vidbyte_cli/lib/auth/provider_credentials.py +157 -0
- vidbyte_cli/lib/auth/provider_input.py +54 -0
- vidbyte_cli/lib/auth/provider_resolver.py +89 -0
- vidbyte_cli/lib/auth/provider_store.py +149 -0
- vidbyte_cli/lib/auth/provider_verifier.py +219 -0
- vidbyte_cli/lib/auth/resolver.py +63 -0
- vidbyte_cli/lib/auth/verifier.py +38 -0
- vidbyte_cli/lib/config/__init__.py +25 -0
- vidbyte_cli/lib/config/atomic.py +47 -0
- vidbyte_cli/lib/config/config.py +141 -0
- vidbyte_cli/lib/config/migration.py +81 -0
- vidbyte_cli/lib/config/models.py +145 -0
- vidbyte_cli/lib/config/paths.py +66 -0
- vidbyte_cli/lib/config/resolver.py +137 -0
- vidbyte_cli/lib/constants/__init__.py +1 -0
- vidbyte_cli/lib/constants/runtime.py +186 -0
- vidbyte_cli/lib/errors/__init__.py +12 -0
- vidbyte_cli/lib/errors/cli_error.py +86 -0
- vidbyte_cli/lib/errors/codes.py +43 -0
- vidbyte_cli/lib/errors/failures.py +2910 -0
- vidbyte_cli/lib/errors/handler.py +72 -0
- vidbyte_cli/lib/files/__init__.py +6 -0
- vidbyte_cli/lib/files/documents.py +94 -0
- vidbyte_cli/lib/files/store.py +156 -0
- vidbyte_cli/lib/io/__init__.py +16 -0
- vidbyte_cli/lib/io/attachments.py +129 -0
- vidbyte_cli/lib/io/codex_attachments.py +47 -0
- vidbyte_cli/lib/io/prompt.py +71 -0
- vidbyte_cli/lib/io/streams.py +37 -0
- vidbyte_cli/lib/io/terminal.py +66 -0
- vidbyte_cli/lib/output/__init__.py +7 -0
- vidbyte_cli/lib/output/formats.py +29 -0
- vidbyte_cli/lib/output/manager.py +95 -0
- vidbyte_cli/lib/output/models.py +49 -0
- vidbyte_cli/lib/runtime/__init__.py +9 -0
- vidbyte_cli/lib/runtime/application.py +134 -0
- vidbyte_cli/lib/runtime/context.py +250 -0
- vidbyte_cli/lib/runtime/options.py +159 -0
- vidbyte_cli/lib/runtime/version.py +30 -0
- vidbyte_cli/lib/runtime_primitives/__init__.py +28 -0
- vidbyte_cli/lib/runtime_primitives/executor.py +54 -0
- vidbyte_cli/lib/runtime_primitives/gate.py +105 -0
- vidbyte_cli/lib/runtime_primitives/hosts.py +45 -0
- vidbyte_cli/lib/runtime_primitives/planner.py +79 -0
- vidbyte_cli/lib/runtime_primitives/stages.py +338 -0
- vidbyte_cli/lib/runtime_primitives/task_board.py +982 -0
- vidbyte_cli/lib/runtime_primitives/task_board_checkpoints.py +473 -0
- vidbyte_cli/lib/runtime_primitives/task_board_system.md +33 -0
- vidbyte_cli/lib/runtime_primitives/task_board_tasks.py +111 -0
- vidbyte_cli/lib/runtime_primitives/verification.py +68 -0
- vidbyte_cli/services/__init__.py +5 -0
- vidbyte_cli/services/ensemble/__init__.py +7 -0
- vidbyte_cli/services/ensemble/prompts/__init__.py +6 -0
- vidbyte_cli/services/ensemble/prompts/implementer_system.md +546 -0
- vidbyte_cli/services/ensemble/prompts/implementer_turn.md +49 -0
- vidbyte_cli/services/ensemble/prompts/library.py +188 -0
- vidbyte_cli/services/ensemble/prompts/planner_system.md +728 -0
- vidbyte_cli/services/ensemble/prompts/planner_turn.md +6 -0
- vidbyte_cli/services/ensemble/prompts/role_system.md +104 -0
- vidbyte_cli/services/ensemble/prompts/role_turn.md +19 -0
- vidbyte_cli/services/ensemble/prompts/selector_final_turn.md +19 -0
- vidbyte_cli/services/ensemble/prompts/selector_round_turn.md +17 -0
- vidbyte_cli/services/ensemble/prompts/selector_system.md +89 -0
- vidbyte_cli/services/ensemble/runner.py +30 -0
- vidbyte_cli/services/ensemble/sdk.py +134 -0
- vidbyte_cli/services/ensemble/service.py +233 -0
- vidbyte_cli/services/ensemble/settings.py +60 -0
- vidbyte_cli/services/persistence/__init__.py +6 -0
- vidbyte_cli/services/persistence/prompts/__init__.py +6 -0
- vidbyte_cli/services/persistence/prompts/library.py +46 -0
- vidbyte_cli/services/persistence/prompts/persistence_system.md +1 -0
- vidbyte_cli/services/persistence/prompts/persistence_turn.md +3 -0
- vidbyte_cli/services/persistence/runner.py +35 -0
- vidbyte_cli/services/persistence/session.py +116 -0
- vidbyte_cli/services/suggestions/__init__.py +7 -0
- vidbyte_cli/services/suggestions/categories.py +270 -0
- vidbyte_cli/services/suggestions/context.py +222 -0
- vidbyte_cli/services/suggestions/extra_compute.py +53 -0
- vidbyte_cli/services/suggestions/handoff.py +173 -0
- vidbyte_cli/services/suggestions/message_tools.py +47 -0
- vidbyte_cli/services/suggestions/project.py +253 -0
- vidbyte_cli/services/suggestions/prompts/__init__.py +1 -0
- vidbyte_cli/services/suggestions/prompts/categories/__init__.py +3 -0
- vidbyte_cli/services/suggestions/prompts/categories/adjacent_opportunity.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/alternative.md +38 -0
- vidbyte_cli/services/suggestions/prompts/categories/big_bets.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/bottleneck.md +38 -0
- vidbyte_cli/services/suggestions/prompts/categories/business_growth.md +38 -0
- vidbyte_cli/services/suggestions/prompts/categories/completion.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/continuation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/coordination.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/creative_exploration.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/cross_domain.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/delegation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/experiment.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/feedback.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/goal_clarification.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/immediate_next_steps.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/investigation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/learning.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/leverage.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/long_term_directions.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/preparation.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/prerequisite.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/prioritization.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/product_experience.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/quick_wins.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/reframing.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/risk_prevention.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/simplification.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/stop_or_defer.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/strategy.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/technical_possibilities.md +37 -0
- vidbyte_cli/services/suggestions/prompts/categories/verification.md +37 -0
- vidbyte_cli/services/suggestions/prompts/critic.md +209 -0
- vidbyte_cli/services/suggestions/prompts/critic_message.md +8 -0
- vidbyte_cli/services/suggestions/prompts/generator.md +64 -0
- vidbyte_cli/services/suggestions/prompts/library.py +72 -0
- vidbyte_cli/services/suggestions/prompts/message_generator_argument.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_generator_tool.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_parent_argument.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_parent_tool.md +1 -0
- vidbyte_cli/services/suggestions/prompts/message_receipt.md +1 -0
- vidbyte_cli/services/suggestions/prompts/revision.md +8 -0
- vidbyte_cli/services/suggestions/sdk.py +228 -0
- vidbyte_cli/services/suggestions/selection.py +78 -0
- vidbyte_cli/services/suggestions/service.py +540 -0
- vidbyte_cli/types/__init__.py +0 -0
- vidbyte_cli/types/api.py +101 -0
- vidbyte_cli/types/attachments.py +79 -0
- vidbyte_cli/types/ensemble.py +189 -0
- vidbyte_cli/types/provider.py +62 -0
- vidbyte_cli/types/research.py +260 -0
- vidbyte_cli/types/runtime.py +1098 -0
- vidbyte_cli/types/suggestions.py +518 -0
- vidbyte_cli-0.1.0.dist-info/METADATA +335 -0
- vidbyte_cli-0.1.0.dist-info/RECORD +269 -0
- vidbyte_cli-0.1.0.dist-info/WHEEL +5 -0
- vidbyte_cli-0.1.0.dist-info/entry_points.txt +2 -0
- vidbyte_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- vidbyte_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Builds the bounded context snapshot for one suggestion run.
|
|
2
|
+
|
|
3
|
+
Only explicitly supplied files are read; nothing scans the repository or the
|
|
4
|
+
Codex history. File contents are treated as task data, never as instructions,
|
|
5
|
+
and omissions or truncations are reported in the manifest rather than hidden.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import hashlib
|
|
11
|
+
import json
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Literal
|
|
15
|
+
|
|
16
|
+
from ...types.suggestions import (
|
|
17
|
+
MAX_CONTEXT_CHARS,
|
|
18
|
+
ContextManifestEntry,
|
|
19
|
+
SuggestionContextItem,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
ManifestStatus = Literal["included", "truncated", "omitted", "not_supplied"]
|
|
23
|
+
|
|
24
|
+
_MAX_FILE_CHARS = MAX_CONTEXT_CHARS
|
|
25
|
+
_MAX_TOTAL_CHARS = MAX_CONTEXT_CHARS
|
|
26
|
+
_SUPPORTED_SUFFIXES = {".txt", ".md", ".json"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class ContextSnapshot:
|
|
31
|
+
"""Resolved items plus the manifest and warnings the result must carry."""
|
|
32
|
+
|
|
33
|
+
items: tuple[SuggestionContextItem, ...]
|
|
34
|
+
manifest: tuple[ContextManifestEntry, ...]
|
|
35
|
+
warnings: tuple[str, ...]
|
|
36
|
+
by_ref: dict[str, str]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class _ContextAccumulator:
|
|
41
|
+
"""Mutable construction state kept private to one context build."""
|
|
42
|
+
|
|
43
|
+
items: list[SuggestionContextItem] = field(default_factory=list)
|
|
44
|
+
manifest: list[ContextManifestEntry] = field(default_factory=list)
|
|
45
|
+
warnings: list[str] = field(default_factory=list)
|
|
46
|
+
total: int = 0
|
|
47
|
+
counter: int = 0
|
|
48
|
+
|
|
49
|
+
def next_ref(self) -> str:
|
|
50
|
+
self.counter += 1
|
|
51
|
+
return f"ctx-{self.counter:03d}"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SuggestionContextBuilder:
|
|
55
|
+
"""Reads explicit inputs into stable refs with hashing and limit enforcement."""
|
|
56
|
+
|
|
57
|
+
def __init__(self) -> None:
|
|
58
|
+
# No state across runs; the counter below is per-build and starts at zero.
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def build(self, fields: dict[str, tuple[str, ...]], files: tuple[Path, ...]) -> ContextSnapshot:
|
|
62
|
+
# Resolves text fields first, then files, so refs stay deterministic per run.
|
|
63
|
+
return self._assemble(fields, files)
|
|
64
|
+
|
|
65
|
+
def _assemble(
|
|
66
|
+
self, fields: dict[str, tuple[str, ...]], files: tuple[Path, ...]
|
|
67
|
+
) -> ContextSnapshot:
|
|
68
|
+
# Keeps the orchestration small while each helper owns one input shape.
|
|
69
|
+
state = _ContextAccumulator()
|
|
70
|
+
self._append_fields(state, fields)
|
|
71
|
+
self._append_files(state, files)
|
|
72
|
+
if self._contradicts(fields):
|
|
73
|
+
state.warnings.append(
|
|
74
|
+
"Supplied context marks one statement both completed "
|
|
75
|
+
"and in-progress; both are preserved."
|
|
76
|
+
)
|
|
77
|
+
return ContextSnapshot(
|
|
78
|
+
items=tuple(state.items),
|
|
79
|
+
manifest=tuple(state.manifest),
|
|
80
|
+
warnings=tuple(state.warnings),
|
|
81
|
+
by_ref={item.ref: item.content for item in state.items},
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def _append_fields(
|
|
85
|
+
self, state: _ContextAccumulator, fields: dict[str, tuple[str, ...]]
|
|
86
|
+
) -> None:
|
|
87
|
+
# Preserves the caller's field order, which makes refs and manifests stable.
|
|
88
|
+
for kind, values in fields.items():
|
|
89
|
+
for value in values:
|
|
90
|
+
self._append_field(state, kind, value)
|
|
91
|
+
|
|
92
|
+
def _append_field(self, state: _ContextAccumulator, kind: str, value: str) -> None:
|
|
93
|
+
# Bounds repeated flag values before Pydantic sees them.
|
|
94
|
+
original = value.strip()
|
|
95
|
+
if not original:
|
|
96
|
+
return
|
|
97
|
+
ref = state.next_ref()
|
|
98
|
+
body, status, note = self._bound_text(original, f"{kind} context")
|
|
99
|
+
if not self._fits(state, len(body)):
|
|
100
|
+
state.manifest.append(self._manifest_entry(ref, kind, f"flag:{kind}", body, "omitted"))
|
|
101
|
+
state.warnings.append(f"Omitted {kind} context: total context budget exceeded.")
|
|
102
|
+
return
|
|
103
|
+
state.items.append(
|
|
104
|
+
SuggestionContextItem(
|
|
105
|
+
ref=ref,
|
|
106
|
+
kind=kind,
|
|
107
|
+
label=kind,
|
|
108
|
+
description="Caller-supplied context value.",
|
|
109
|
+
content=body,
|
|
110
|
+
source=f"flag:{kind}",
|
|
111
|
+
caller_supplied=True,
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
state.manifest.append(self._manifest_entry(ref, kind, f"flag:{kind}", body, status))
|
|
115
|
+
if status == "truncated":
|
|
116
|
+
state.warnings.append(note)
|
|
117
|
+
state.total += len(body)
|
|
118
|
+
|
|
119
|
+
def _append_files(self, state: _ContextAccumulator, files: tuple[Path, ...]) -> None:
|
|
120
|
+
# Reads files only after all repeated flag values have been accounted for.
|
|
121
|
+
for path in files:
|
|
122
|
+
self._append_file(state, path)
|
|
123
|
+
|
|
124
|
+
def _append_file(self, state: _ContextAccumulator, path: Path) -> None:
|
|
125
|
+
# Rejects invalid files before adding an item or charging the total budget.
|
|
126
|
+
ref = state.next_ref()
|
|
127
|
+
body, status, note = self._read_one(path)
|
|
128
|
+
if not body:
|
|
129
|
+
state.manifest.append(self._manifest_entry(ref, "file", str(path), body, "omitted"))
|
|
130
|
+
state.warnings.append(f"Omitted empty context file: {path}.")
|
|
131
|
+
return
|
|
132
|
+
if not self._fits(state, len(body)):
|
|
133
|
+
state.manifest.append(self._manifest_entry(ref, "file", str(path), body, "omitted"))
|
|
134
|
+
state.warnings.append(f"Omitted {path}: total context budget exceeded.")
|
|
135
|
+
return
|
|
136
|
+
state.items.append(
|
|
137
|
+
SuggestionContextItem(
|
|
138
|
+
ref=ref,
|
|
139
|
+
kind="file",
|
|
140
|
+
label=path.name,
|
|
141
|
+
description="Caller-supplied file content.",
|
|
142
|
+
content=body,
|
|
143
|
+
source=str(path),
|
|
144
|
+
caller_supplied=False,
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
entry_status: ManifestStatus = "truncated" if status == "truncated" else "included"
|
|
148
|
+
state.manifest.append(self._manifest_entry(ref, "file", str(path), body, entry_status))
|
|
149
|
+
if status == "truncated":
|
|
150
|
+
state.warnings.append(note)
|
|
151
|
+
state.total += len(body)
|
|
152
|
+
|
|
153
|
+
def _fits(self, state: _ContextAccumulator, chars: int) -> bool:
|
|
154
|
+
# Keeps the aggregate body budget independent from prompt and metadata text.
|
|
155
|
+
return state.total + chars <= _MAX_TOTAL_CHARS
|
|
156
|
+
|
|
157
|
+
def _manifest_entry(
|
|
158
|
+
self,
|
|
159
|
+
ref: str,
|
|
160
|
+
kind: str,
|
|
161
|
+
source: str,
|
|
162
|
+
body: str,
|
|
163
|
+
status: ManifestStatus,
|
|
164
|
+
) -> ContextManifestEntry:
|
|
165
|
+
# Hashes the exact bounded body that the agent will receive.
|
|
166
|
+
return ContextManifestEntry(
|
|
167
|
+
ref=ref,
|
|
168
|
+
kind=kind,
|
|
169
|
+
source=source,
|
|
170
|
+
chars=len(body),
|
|
171
|
+
sha256=hashlib.sha256(body.encode("utf-8")).hexdigest()[:16],
|
|
172
|
+
status=status,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def _bound_text(self, text: str, label: str) -> tuple[str, ManifestStatus, str]:
|
|
176
|
+
# Applies the same generous per-item cap to repeated flag values as to files.
|
|
177
|
+
if len(text) <= _MAX_FILE_CHARS:
|
|
178
|
+
return text, "included", ""
|
|
179
|
+
kept = text[:_MAX_FILE_CHARS]
|
|
180
|
+
return kept, "truncated", f"Truncated {label} to {_MAX_FILE_CHARS} chars."
|
|
181
|
+
|
|
182
|
+
def _read_one(self, path: Path) -> tuple[str, ManifestStatus, str]:
|
|
183
|
+
# Rejects directories, missing paths, and non-text shapes before reading.
|
|
184
|
+
if path.is_dir():
|
|
185
|
+
raise ValueError(f"context path is a directory: {path}")
|
|
186
|
+
if not path.exists():
|
|
187
|
+
raise ValueError(f"context file not found: {path}")
|
|
188
|
+
if path.suffix.lower() not in _SUPPORTED_SUFFIXES:
|
|
189
|
+
raise ValueError(f"unsupported context file type: {path}")
|
|
190
|
+
try:
|
|
191
|
+
text = path.read_text(encoding="utf-8-sig")
|
|
192
|
+
except OSError as error:
|
|
193
|
+
raise ValueError(f"unreadable context file: {path}") from error
|
|
194
|
+
if path.suffix.lower() == ".json":
|
|
195
|
+
try:
|
|
196
|
+
parsed = json.loads(text)
|
|
197
|
+
except json.JSONDecodeError as error:
|
|
198
|
+
raise ValueError(f"malformed JSON context file: {path}") from error
|
|
199
|
+
if (
|
|
200
|
+
isinstance(parsed, dict)
|
|
201
|
+
and "schema_version" in parsed
|
|
202
|
+
and parsed["schema_version"] != 1
|
|
203
|
+
):
|
|
204
|
+
raise ValueError(f"unsupported schema version in: {path}")
|
|
205
|
+
text = json.dumps(parsed, indent=2, sort_keys=True)
|
|
206
|
+
if len(text) > _MAX_FILE_CHARS:
|
|
207
|
+
kept = text[:_MAX_FILE_CHARS]
|
|
208
|
+
return kept, "truncated", f"Truncated {path} to {_MAX_FILE_CHARS} chars."
|
|
209
|
+
return text, "included", ""
|
|
210
|
+
|
|
211
|
+
def _contradicts(self, fields: dict[str, tuple[str, ...]]) -> bool:
|
|
212
|
+
# Surfaces exact-statement overlap between completed and in-progress lists.
|
|
213
|
+
completed = {
|
|
214
|
+
value.strip().lower() for value in fields.get("completed", ()) if value.strip()
|
|
215
|
+
}
|
|
216
|
+
in_progress = {
|
|
217
|
+
value.strip().lower() for value in fields.get("in_progress", ()) if value.strip()
|
|
218
|
+
}
|
|
219
|
+
return bool(completed & in_progress)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
__all__ = ["ContextSnapshot", "SuggestionContextBuilder"]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Runs one isolated generator context per selected category in parallel."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import math
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
from ...types.suggestions import (
|
|
11
|
+
SuggestionAgentContext,
|
|
12
|
+
SuggestionCandidateBatch,
|
|
13
|
+
SuggestionDraft,
|
|
14
|
+
SuggestionRequest,
|
|
15
|
+
)
|
|
16
|
+
from .categories import SuggestionCategories
|
|
17
|
+
from .prompts.library import SuggestionPrompts
|
|
18
|
+
|
|
19
|
+
AgentCall = Callable[
|
|
20
|
+
[Literal["generator", "critic"], str, SuggestionAgentContext, str | None],
|
|
21
|
+
Awaitable[SuggestionCandidateBatch],
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ExtraComputeService:
|
|
26
|
+
"""Fans out focused category windows and combines their typed drafts stably."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, categories: SuggestionCategories, prompts: SuggestionPrompts) -> None:
|
|
29
|
+
self._categories = categories
|
|
30
|
+
self._prompts = prompts
|
|
31
|
+
|
|
32
|
+
async def generate(
|
|
33
|
+
self,
|
|
34
|
+
request: SuggestionRequest,
|
|
35
|
+
category_ids: tuple[str, ...],
|
|
36
|
+
pool_size: int,
|
|
37
|
+
call: AgentCall,
|
|
38
|
+
) -> tuple[SuggestionDraft, ...]:
|
|
39
|
+
per_category = max(1, math.ceil(pool_size / len(category_ids)))
|
|
40
|
+
|
|
41
|
+
async def run_one(category_id: str) -> SuggestionCandidateBatch:
|
|
42
|
+
context = SuggestionAgentContext.for_stage(
|
|
43
|
+
request.context, self._categories.prompt_section((category_id,))
|
|
44
|
+
)
|
|
45
|
+
prompt = self._prompts.generator_turn(request.goal, per_category)
|
|
46
|
+
return await call("generator", prompt, context, None)
|
|
47
|
+
|
|
48
|
+
batches = await asyncio.gather(*(run_one(category) for category in category_ids))
|
|
49
|
+
drafts = tuple(draft for batch in batches for draft in batch.ideas)
|
|
50
|
+
return drafts[:pool_size]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__all__ = ["AgentCall", "ExtraComputeService"]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Builds deterministic execution handoffs from validated suggestion fields."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ...types.suggestions import (
|
|
6
|
+
SuggestionDraft,
|
|
7
|
+
SuggestionHandoff,
|
|
8
|
+
SuggestionHandoffEvidence,
|
|
9
|
+
SuggestionIdea,
|
|
10
|
+
SuggestionRequest,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SuggestionHandoffBuilder:
|
|
15
|
+
"""Builds one self-contained action packet without another model call."""
|
|
16
|
+
|
|
17
|
+
def build(self, idea: SuggestionIdea, request: SuggestionRequest) -> SuggestionHandoff:
|
|
18
|
+
"""Rebuild a handoff from the idea fields so the prompt cannot drift."""
|
|
19
|
+
return self._build(
|
|
20
|
+
idea_id=idea.id,
|
|
21
|
+
revision=idea.revision,
|
|
22
|
+
title=idea.title,
|
|
23
|
+
summary=idea.summary,
|
|
24
|
+
actions=idea.suggested_actions,
|
|
25
|
+
decisions=idea.decision_points,
|
|
26
|
+
considerations=idea.considerations,
|
|
27
|
+
evidence_refs=idea.evidence_refs,
|
|
28
|
+
completion=idea.completion_criteria,
|
|
29
|
+
dependencies=idea.dependencies,
|
|
30
|
+
why_now=idea.why_now,
|
|
31
|
+
request=request,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def build_draft(
|
|
35
|
+
self, draft: SuggestionDraft, request: SuggestionRequest, idea_id: str, revision: int
|
|
36
|
+
) -> SuggestionHandoff:
|
|
37
|
+
"""Build the provisional handoff used when the critic sees generated candidates."""
|
|
38
|
+
return self._build(
|
|
39
|
+
idea_id=idea_id,
|
|
40
|
+
revision=revision,
|
|
41
|
+
title=draft.title,
|
|
42
|
+
summary=draft.summary,
|
|
43
|
+
actions=draft.suggested_actions,
|
|
44
|
+
decisions=draft.decision_points,
|
|
45
|
+
considerations=draft.considerations,
|
|
46
|
+
evidence_refs=draft.evidence_refs,
|
|
47
|
+
completion=draft.completion_criteria,
|
|
48
|
+
dependencies=draft.dependencies,
|
|
49
|
+
why_now=draft.why_now,
|
|
50
|
+
request=request,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def _build(
|
|
54
|
+
self,
|
|
55
|
+
*,
|
|
56
|
+
idea_id: str,
|
|
57
|
+
revision: int,
|
|
58
|
+
title: str,
|
|
59
|
+
summary: str,
|
|
60
|
+
actions: tuple[str, ...],
|
|
61
|
+
decisions: tuple[str, ...],
|
|
62
|
+
considerations: tuple[str, ...],
|
|
63
|
+
evidence_refs: tuple[str, ...],
|
|
64
|
+
completion: str,
|
|
65
|
+
dependencies: tuple[str, ...],
|
|
66
|
+
why_now: str,
|
|
67
|
+
request: SuggestionRequest,
|
|
68
|
+
) -> SuggestionHandoff:
|
|
69
|
+
handoff = SuggestionHandoff(
|
|
70
|
+
idea_id=idea_id,
|
|
71
|
+
idea_revision=revision,
|
|
72
|
+
original_goal=request.goal,
|
|
73
|
+
title=title,
|
|
74
|
+
summary=summary,
|
|
75
|
+
suggested_actions=actions,
|
|
76
|
+
decisions_along_way=decisions,
|
|
77
|
+
considerations=considerations,
|
|
78
|
+
evidence=self._evidence(evidence_refs, request, why_now),
|
|
79
|
+
completion_checks=(completion,),
|
|
80
|
+
dependencies=dependencies,
|
|
81
|
+
warnings=self._warnings(request),
|
|
82
|
+
forbidden_actions=self._context_values(request, "forbidden"),
|
|
83
|
+
stop_conditions=(
|
|
84
|
+
"The completion checks pass.",
|
|
85
|
+
"Evidence contradicts the proposed action.",
|
|
86
|
+
"Required authority is missing.",
|
|
87
|
+
"Required prerequisites are missing.",
|
|
88
|
+
),
|
|
89
|
+
return_report=(
|
|
90
|
+
"Report actions taken, decisions made, evidence observed, completion checks, "
|
|
91
|
+
"and unresolved follow-ups to the calling agent."
|
|
92
|
+
),
|
|
93
|
+
execution_prompt="pending",
|
|
94
|
+
)
|
|
95
|
+
return handoff.model_copy(update={"execution_prompt": self.render_prompt(handoff)})
|
|
96
|
+
|
|
97
|
+
def render_prompt(self, handoff: SuggestionHandoff) -> str:
|
|
98
|
+
"""Render only validated fields in a stable order."""
|
|
99
|
+
lines = [
|
|
100
|
+
f"Goal: {handoff.original_goal}",
|
|
101
|
+
f"Suggested action: {handoff.title}",
|
|
102
|
+
f"Summary: {handoff.summary}",
|
|
103
|
+
"Actions:",
|
|
104
|
+
*self._lines(handoff.suggested_actions),
|
|
105
|
+
"Decisions along the way:",
|
|
106
|
+
*self._lines(handoff.decisions_along_way),
|
|
107
|
+
"Things to consider:",
|
|
108
|
+
*self._lines(handoff.considerations),
|
|
109
|
+
"Evidence:",
|
|
110
|
+
*self._evidence_lines(handoff.evidence),
|
|
111
|
+
"Completion checks:",
|
|
112
|
+
*self._lines(handoff.completion_checks),
|
|
113
|
+
"Dependencies:",
|
|
114
|
+
*self._lines(handoff.dependencies),
|
|
115
|
+
"Warnings:",
|
|
116
|
+
*self._lines(handoff.warnings),
|
|
117
|
+
"Forbidden actions:",
|
|
118
|
+
*self._lines(handoff.forbidden_actions),
|
|
119
|
+
"Stop when:",
|
|
120
|
+
*self._lines(handoff.stop_conditions),
|
|
121
|
+
f"Authority: {handoff.authority}",
|
|
122
|
+
f"Report: {handoff.return_report}",
|
|
123
|
+
]
|
|
124
|
+
return "\n".join(lines)
|
|
125
|
+
|
|
126
|
+
def _evidence(
|
|
127
|
+
self, refs: tuple[str, ...], request: SuggestionRequest, why_now: str
|
|
128
|
+
) -> tuple[SuggestionHandoffEvidence, ...]:
|
|
129
|
+
items_by_ref = {item.ref: item for item in request.context_items}
|
|
130
|
+
return tuple(
|
|
131
|
+
SuggestionHandoffEvidence(
|
|
132
|
+
ref=ref,
|
|
133
|
+
source=items_by_ref[ref].source,
|
|
134
|
+
content=items_by_ref[ref].content[:4096],
|
|
135
|
+
relevance=f"Supports why this action matters now: {why_now}",
|
|
136
|
+
)
|
|
137
|
+
for ref in refs
|
|
138
|
+
if ref in items_by_ref
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def _warnings(self, request: SuggestionRequest) -> tuple[str, ...]:
|
|
142
|
+
warnings = list(request.context_warnings)
|
|
143
|
+
for item in request.context_items:
|
|
144
|
+
if item.kind == "mistake":
|
|
145
|
+
parts = tuple(part.strip() for part in item.content.split("|"))
|
|
146
|
+
if len(parts) == 3 and all(parts):
|
|
147
|
+
warnings.append(
|
|
148
|
+
f"Past mistake lesson: {parts[2]} (failure: {parts[0]}; cause: {parts[1]})"
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
warnings.append(item.content)
|
|
152
|
+
elif item.kind == "risk" or (
|
|
153
|
+
item.kind == "hypothesis" and item.content.lower().startswith("low:")
|
|
154
|
+
):
|
|
155
|
+
warnings.append(item.content)
|
|
156
|
+
return tuple(dict.fromkeys(warnings))
|
|
157
|
+
|
|
158
|
+
def _context_values(self, request: SuggestionRequest, kind: str) -> tuple[str, ...]:
|
|
159
|
+
return tuple(item.content for item in request.context_items if item.kind == kind)
|
|
160
|
+
|
|
161
|
+
def _lines(self, values: tuple[str, ...]) -> list[str]:
|
|
162
|
+
return [f"- {value}" for value in values] or ["- none supplied"]
|
|
163
|
+
|
|
164
|
+
def _evidence_lines(self, evidence: tuple[SuggestionHandoffEvidence, ...]) -> list[str]:
|
|
165
|
+
if not evidence:
|
|
166
|
+
return ["- none cited"]
|
|
167
|
+
return [
|
|
168
|
+
f"- [{item.ref}] {item.content} (source: {item.source}; {item.relevance})"
|
|
169
|
+
for item in evidence
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
__all__ = ["SuggestionHandoffBuilder"]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Stop-and-message tools that let one suggestion agent end its turn with a message.
|
|
2
|
+
|
|
3
|
+
This module owns the CLI side of each tool: its model-facing text, the message
|
|
4
|
+
validation, and the stop signal the service waits on. It never imports the SDK;
|
|
5
|
+
`sdk.py` wraps each tool as an SDK `BaseTool` so Codex can call it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
from ...types.suggestions import MAX_AGENT_MESSAGE_CHARS
|
|
14
|
+
from .prompts.library import SuggestionPrompts
|
|
15
|
+
|
|
16
|
+
MessageToolName = Literal["message_parent", "message_generator"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SuggestionMessageTool:
|
|
20
|
+
"""One agent's route to another agent: the first valid message stops its turn."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, name: MessageToolName, prompts: SuggestionPrompts) -> None:
|
|
23
|
+
# One instance serves exactly one agent, so its message and signal are never shared.
|
|
24
|
+
self.name = name
|
|
25
|
+
self.description = prompts.message_tool(name)
|
|
26
|
+
self.message_description = prompts.message_argument(name)
|
|
27
|
+
self._receipt = prompts.message_receipt()
|
|
28
|
+
self.message: str | None = None
|
|
29
|
+
self.stopped = asyncio.Event()
|
|
30
|
+
|
|
31
|
+
def deliver(self, message: object) -> str:
|
|
32
|
+
"""Record the model's message, signal the stop, and return the tool receipt."""
|
|
33
|
+
if not isinstance(message, str) or not message.strip():
|
|
34
|
+
raise ValueError(f"{self.name} requires a non-empty message string.")
|
|
35
|
+
text = message.strip()
|
|
36
|
+
if len(text) > MAX_AGENT_MESSAGE_CHARS:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"{self.name} messages must be at most {MAX_AGENT_MESSAGE_CHARS} characters."
|
|
39
|
+
)
|
|
40
|
+
# A second call in the same turn cannot replace the message already being delivered.
|
|
41
|
+
if self.message is None:
|
|
42
|
+
self.message = text
|
|
43
|
+
self.stopped.set()
|
|
44
|
+
return self._receipt
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
__all__ = ["MessageToolName", "SuggestionMessageTool"]
|