symphony-code 0.0.1__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.
- coding_agent/__init__.py +20 -0
- coding_agent/_lazy.py +17 -0
- coding_agent/addons/__init__.py +35 -0
- coding_agent/addons/approvals.py +224 -0
- coding_agent/addons/compaction/__init__.py +36 -0
- coding_agent/addons/compaction/addon.py +88 -0
- coding_agent/addons/compaction/compactor.py +138 -0
- coding_agent/addons/compaction/prompts.py +20 -0
- coding_agent/addons/compaction/transcript.py +118 -0
- coding_agent/addons/evaluation/__init__.py +68 -0
- coding_agent/addons/evaluation/addon.py +199 -0
- coding_agent/addons/evaluation/evaluators.py +245 -0
- coding_agent/addons/evaluation/handlers.py +66 -0
- coding_agent/addons/evaluation/policy.py +277 -0
- coding_agent/addons/evaluation/protocol.py +192 -0
- coding_agent/addons/evaluation/state.py +284 -0
- coding_agent/addons/langfuse/__init__.py +24 -0
- coding_agent/addons/langfuse/addon.py +465 -0
- coding_agent/addons/langfuse/install.py +79 -0
- coding_agent/addons/langfuse/serialize.py +158 -0
- coding_agent/addons/learning/__init__.py +33 -0
- coding_agent/addons/learning/addon.py +181 -0
- coding_agent/addons/learning/loop.py +247 -0
- coding_agent/addons/learning/prompts.py +26 -0
- coding_agent/addons/learning/sanitize.py +64 -0
- coding_agent/addons/learning/store.py +522 -0
- coding_agent/addons/manager/__init__.py +18 -0
- coding_agent/addons/manager/bridge.py +74 -0
- coding_agent/addons/manager/discovery.py +34 -0
- coding_agent/addons/manager/report.py +33 -0
- coding_agent/addons/manager/runtime.py +76 -0
- coding_agent/addons/manager/tool_changes.py +16 -0
- coding_agent/addons/persistence/__init__.py +41 -0
- coding_agent/addons/persistence/active.py +132 -0
- coding_agent/addons/persistence/artifacts.py +110 -0
- coding_agent/addons/persistence/collection.py +166 -0
- coding_agent/addons/persistence/compatibility.py +30 -0
- coding_agent/addons/persistence/images.py +133 -0
- coding_agent/addons/persistence/jsonl.py +1195 -0
- coding_agent/addons/persistence/presentation.py +104 -0
- coding_agent/addons/plugins/__init__.py +19 -0
- coding_agent/addons/plugins/install.py +37 -0
- coding_agent/addons/plugins/manager.py +140 -0
- coding_agent/addons/plugins/models.py +50 -0
- coding_agent/addons/skills/__init__.py +28 -0
- coding_agent/addons/skills/addon.py +26 -0
- coding_agent/addons/skills/args.py +78 -0
- coding_agent/addons/skills/bundled_skills/addon-authoring/SKILL.md +77 -0
- coding_agent/addons/skills/bundled_skills/context-budget/SKILL.md +12 -0
- coding_agent/addons/skills/bundled_skills/orient-first/SKILL.md +16 -0
- coding_agent/addons/skills/bundled_skills/security-boundary/SKILL.md +16 -0
- coding_agent/addons/skills/bundled_skills/skill-discipline/SKILL.md +13 -0
- coding_agent/addons/skills/bundled_skills/surgical-edit/SKILL.md +11 -0
- coding_agent/addons/skills/bundled_skills/systematic-debug/SKILL.md +18 -0
- coding_agent/addons/skills/bundled_skills/verify-before-done/SKILL.md +11 -0
- coding_agent/addons/skills/frontmatter.py +57 -0
- coding_agent/addons/skills/models.py +57 -0
- coding_agent/addons/skills/registry.py +109 -0
- coding_agent/addons/skills/tool.py +54 -0
- coding_agent/addons/voice/__init__.py +5 -0
- coding_agent/addons/voice/addon.py +106 -0
- coding_agent/addons/voice/credentials.py +42 -0
- coding_agent/addons/voice/playback.py +42 -0
- coding_agent/addons/voice/speak.py +287 -0
- coding_agent/addons/voice/stream.py +651 -0
- coding_agent/addons/voice/summary.py +126 -0
- coding_agent/agent.py +523 -0
- coding_agent/bench/__init__.py +1 -0
- coding_agent/bench/cli.py +200 -0
- coding_agent/bench/patch.py +78 -0
- coding_agent/commands/__init__.py +5 -0
- coding_agent/commands/catalog.py +179 -0
- coding_agent/commands/jev_argument.py +40 -0
- coding_agent/config/__init__.py +51 -0
- coding_agent/config/config.py +376 -0
- coding_agent/config/credentials.py +153 -0
- coding_agent/config/personalities.json +34 -0
- coding_agent/config/personalities.py +170 -0
- coding_agent/config/prompts.py +67 -0
- coding_agent/protocols/__init__.py +5 -0
- coding_agent/protocols/acp/README.md +36 -0
- coding_agent/protocols/acp/__init__.py +27 -0
- coding_agent/protocols/acp/mapping.py +164 -0
- coding_agent/protocols/acp/server.py +261 -0
- coding_agent/protocols/acp/transport.py +72 -0
- coding_agent/protocols/stdio/README.md +36 -0
- coding_agent/protocols/stdio/__init__.py +37 -0
- coding_agent/protocols/stdio/catalog.py +49 -0
- coding_agent/protocols/stdio/commands.py +218 -0
- coding_agent/protocols/stdio/server.py +181 -0
- coding_agent/protocols/stdio/transport.py +71 -0
- coding_agent/py.typed +0 -0
- coding_agent/resources/__init__.py +5 -0
- coding_agent/resources/usage.py +264 -0
- coding_agent/run/__init__.py +1 -0
- coding_agent/run/cli.py +99 -0
- coding_agent/run/detach.py +37 -0
- coding_agent/run/interrupted.py +67 -0
- coding_agent/run/log_sink.py +46 -0
- coding_agent/tools/__init__.py +73 -0
- coding_agent/tools/ask_user.py +61 -0
- coding_agent/tools/base.py +136 -0
- coding_agent/tools/bash.py +288 -0
- coding_agent/tools/bash_jobs.py +116 -0
- coding_agent/tools/generate_image.py +123 -0
- coding_agent/tools/memory.py +100 -0
- coding_agent/tools/patch.py +218 -0
- coding_agent/tools/read_file.py +167 -0
- coding_agent/tools/search.py +433 -0
- coding_agent/tools/web_search.py +151 -0
- coding_agent/tools/write_file.py +40 -0
- coding_agent/tui/EFFICIENCY_PLAN.md +25 -0
- coding_agent/tui/__init__.py +22 -0
- coding_agent/tui/__main__.py +122 -0
- coding_agent/tui/app.py +456 -0
- coding_agent/tui/chrome/__init__.py +33 -0
- coding_agent/tui/chrome/footer.py +274 -0
- coding_agent/tui/chrome/topbar.py +259 -0
- coding_agent/tui/commands/__init__.py +45 -0
- coding_agent/tui/commands/catalog.py +35 -0
- coding_agent/tui/commands/manager.py +557 -0
- coding_agent/tui/composer/__init__.py +18 -0
- coding_agent/tui/composer/input.py +219 -0
- coding_agent/tui/composer/slash_menu.py +341 -0
- coding_agent/tui/composer/surface.py +330 -0
- coding_agent/tui/composer/voice.py +267 -0
- coding_agent/tui/composer/voice_mode.py +491 -0
- coding_agent/tui/runtime/__init__.py +23 -0
- coding_agent/tui/runtime/events.py +728 -0
- coding_agent/tui/runtime/sink.py +115 -0
- coding_agent/tui/runtime/state.py +145 -0
- coding_agent/tui/runtime/subagent.py +531 -0
- coding_agent/tui/runtime/turn.py +206 -0
- coding_agent/tui/screens/__init__.py +54 -0
- coding_agent/tui/screens/ask.py +69 -0
- coding_agent/tui/screens/context.py +195 -0
- coding_agent/tui/screens/dashboard.py +91 -0
- coding_agent/tui/screens/diff.py +302 -0
- coding_agent/tui/screens/extensions.py +68 -0
- coding_agent/tui/screens/file_selector.py +167 -0
- coding_agent/tui/screens/history.py +458 -0
- coding_agent/tui/screens/langfuse.py +142 -0
- coding_agent/tui/screens/learning.py +113 -0
- coding_agent/tui/screens/modal.py +131 -0
- coding_agent/tui/screens/onboard.py +455 -0
- coding_agent/tui/screens/resume.py +163 -0
- coding_agent/tui/theme/__init__.py +49 -0
- coding_agent/tui/theme/load.py +325 -0
- coding_agent/tui/theme/style.py +95 -0
- coding_agent/tui/theme/theme.toml +1687 -0
- coding_agent/tui/tools/__init__.py +53 -0
- coding_agent/tui/tools/activity.py +184 -0
- coding_agent/tui/tools/calls.py +560 -0
- coding_agent/tui/tools/code_view.py +52 -0
- coding_agent/tui/tools/images.py +250 -0
- coding_agent/tui/tools/labels.py +135 -0
- coding_agent/tui/tools/modal.py +137 -0
- coding_agent/tui/tools/partial_args.py +19 -0
- coding_agent/tui/tools/snapshots.py +382 -0
- coding_agent/tui/transcript/__init__.py +36 -0
- coding_agent/tui/transcript/messages.py +408 -0
- coding_agent/tui/transcript/process.py +504 -0
- coding_agent/tui/transcript/surface.py +345 -0
- coding_agent/tui/transcript/thinking_markdown.py +314 -0
- symphony_code-0.0.1.dist-info/METADATA +66 -0
- symphony_code-0.0.1.dist-info/RECORD +169 -0
- symphony_code-0.0.1.dist-info/WHEEL +4 -0
- symphony_code-0.0.1.dist-info/entry_points.txt +5 -0
- symphony_code-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"""Capped semantic run state for the critic. Never a raw transcript or JSONL dump."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Any, Iterable, Optional
|
|
9
|
+
|
|
10
|
+
from core_ai.content import text_from_content
|
|
11
|
+
from core_ai.types import Message
|
|
12
|
+
|
|
13
|
+
from coding_agent.config import EvaluationConfig
|
|
14
|
+
from coding_agent.addons.evaluation.protocol import EvaluationPhase
|
|
15
|
+
from coding_agent.addons.learning.sanitize import redact_secrets
|
|
16
|
+
|
|
17
|
+
WRITE_TOOLS = frozenset({"write_file", "patch"})
|
|
18
|
+
MAX_PLAN_ITEMS = 40
|
|
19
|
+
_CHECKBOX = re.compile(r"^\s*[-*]\s*\[([ xX])\]\s+(.*)$")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class PlanItem:
|
|
24
|
+
text: str
|
|
25
|
+
status: str
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class RunState:
|
|
30
|
+
"""Bounded snapshot sent to the evaluator."""
|
|
31
|
+
|
|
32
|
+
phase: EvaluationPhase
|
|
33
|
+
rule: str = ""
|
|
34
|
+
request: str = ""
|
|
35
|
+
brief: str = ""
|
|
36
|
+
plan: str = ""
|
|
37
|
+
plan_items: list[PlanItem] = field(default_factory=list)
|
|
38
|
+
observations: list[str] = field(default_factory=list)
|
|
39
|
+
files_modified: list[str] = field(default_factory=list)
|
|
40
|
+
last_tool_results: list[str] = field(default_factory=list)
|
|
41
|
+
final: str = ""
|
|
42
|
+
|
|
43
|
+
def as_eval_state(self) -> dict[str, Any]:
|
|
44
|
+
"""Compact, redacted payload for the evaluation-model ``state`` field."""
|
|
45
|
+
return redact_eval_value(
|
|
46
|
+
{
|
|
47
|
+
"request": self.request,
|
|
48
|
+
"rule": self.rule,
|
|
49
|
+
"brief": self.brief,
|
|
50
|
+
"plan": self.plan,
|
|
51
|
+
"plan_items": [
|
|
52
|
+
{"text": item.text, "status": item.status} for item in self.plan_items
|
|
53
|
+
],
|
|
54
|
+
"observations": self.observations,
|
|
55
|
+
"files_modified": self.files_modified,
|
|
56
|
+
"last_tool_results": self.last_tool_results,
|
|
57
|
+
"final": self.final,
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def redact_field(text: str) -> str:
|
|
63
|
+
"""Strip secrets from one outbound text field (same path as Langfuse)."""
|
|
64
|
+
return redact_secrets(text or "")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def redact_eval_value(value: Any) -> Any:
|
|
68
|
+
"""Walk a state payload and redact every string leaf."""
|
|
69
|
+
if isinstance(value, str):
|
|
70
|
+
return redact_field(value)
|
|
71
|
+
if isinstance(value, dict):
|
|
72
|
+
return {str(key): redact_eval_value(item) for key, item in value.items()}
|
|
73
|
+
if isinstance(value, list):
|
|
74
|
+
return [redact_eval_value(item) for item in value]
|
|
75
|
+
if isinstance(value, tuple):
|
|
76
|
+
return [redact_eval_value(item) for item in value]
|
|
77
|
+
return value
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def clip_text(text: str, limit: int) -> str:
|
|
81
|
+
value = text or ""
|
|
82
|
+
if limit <= 0 or len(value) <= limit:
|
|
83
|
+
return value
|
|
84
|
+
if limit == 1:
|
|
85
|
+
return "…"
|
|
86
|
+
return value[: limit - 1] + "…"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def bound_text(text: str, limit: int) -> str:
|
|
90
|
+
"""Redact secrets, then apply the field's character cap."""
|
|
91
|
+
return clip_text(redact_field(text), limit)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def bound_lines(lines: Iterable[str], max_chars: int) -> list[str]:
|
|
95
|
+
return clip_lines((redact_field(line) for line in lines), max_chars)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def clip_lines(lines: Iterable[str], max_chars: int) -> list[str]:
|
|
99
|
+
out: list[str] = []
|
|
100
|
+
used = 0
|
|
101
|
+
for line in lines:
|
|
102
|
+
remaining = max_chars - used
|
|
103
|
+
if remaining <= 0:
|
|
104
|
+
break
|
|
105
|
+
clipped = clip_text(line, remaining)
|
|
106
|
+
if not clipped:
|
|
107
|
+
continue
|
|
108
|
+
out.append(clipped)
|
|
109
|
+
used += len(clipped)
|
|
110
|
+
return out
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def first_line(text: str) -> str:
|
|
114
|
+
for line in (text or "").splitlines():
|
|
115
|
+
stripped = line.strip()
|
|
116
|
+
if stripped:
|
|
117
|
+
return stripped
|
|
118
|
+
return (text or "").strip()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def plan_items_from_markdown(text: str) -> list[PlanItem]:
|
|
122
|
+
items: list[PlanItem] = []
|
|
123
|
+
for line in (text or "").splitlines():
|
|
124
|
+
match = _CHECKBOX.match(line)
|
|
125
|
+
if match is None:
|
|
126
|
+
continue
|
|
127
|
+
status = "done" if match.group(1).strip() else "pending"
|
|
128
|
+
items.append(PlanItem(text=bound_text(match.group(2).strip(), 200), status=status))
|
|
129
|
+
return items
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def request_from_messages(messages: Iterable[Message]) -> str:
|
|
133
|
+
for message in reversed(list(messages)):
|
|
134
|
+
if isinstance(message, Message) and message.role == "user":
|
|
135
|
+
return text_from_content(message.content).strip()
|
|
136
|
+
return ""
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _tool_name(call: object) -> str:
|
|
140
|
+
if isinstance(call, dict):
|
|
141
|
+
function = call.get("function")
|
|
142
|
+
if isinstance(function, dict):
|
|
143
|
+
return str(function.get("name") or "").strip()
|
|
144
|
+
return str(call.get("name") or "").strip()
|
|
145
|
+
return str(getattr(call, "name", "") or "").strip()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _tool_args(call: object) -> dict[str, Any]:
|
|
149
|
+
raw: Any = None
|
|
150
|
+
if isinstance(call, dict):
|
|
151
|
+
function = call.get("function")
|
|
152
|
+
if isinstance(function, dict):
|
|
153
|
+
raw = function.get("arguments")
|
|
154
|
+
elif "arguments" in call:
|
|
155
|
+
raw = call.get("arguments")
|
|
156
|
+
else:
|
|
157
|
+
raw = getattr(call, "arguments", None)
|
|
158
|
+
if isinstance(raw, dict):
|
|
159
|
+
return raw
|
|
160
|
+
if isinstance(raw, str) and raw.strip():
|
|
161
|
+
try:
|
|
162
|
+
parsed = json.loads(raw)
|
|
163
|
+
except ValueError:
|
|
164
|
+
return {}
|
|
165
|
+
return parsed if isinstance(parsed, dict) else {}
|
|
166
|
+
return {}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def files_modified_from_calls(calls: Iterable[object], *, limit: int) -> list[str]:
|
|
170
|
+
paths: list[str] = []
|
|
171
|
+
seen: set[str] = set()
|
|
172
|
+
for call in calls:
|
|
173
|
+
if _tool_name(call) not in WRITE_TOOLS:
|
|
174
|
+
continue
|
|
175
|
+
path = str(_tool_args(call).get("path") or "").strip()
|
|
176
|
+
if not path or path in seen:
|
|
177
|
+
continue
|
|
178
|
+
seen.add(path)
|
|
179
|
+
paths.append(path)
|
|
180
|
+
if len(paths) >= limit:
|
|
181
|
+
break
|
|
182
|
+
return paths
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def files_modified_from_messages(messages: Iterable[Message], *, limit: int) -> list[str]:
|
|
186
|
+
calls: list[object] = []
|
|
187
|
+
for message in messages:
|
|
188
|
+
if isinstance(message, Message) and message.tool_calls:
|
|
189
|
+
calls.extend(message.tool_calls)
|
|
190
|
+
return files_modified_from_calls(calls, limit=limit)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def observations_from_messages(messages: Iterable[Message]) -> list[str]:
|
|
194
|
+
lines: list[str] = []
|
|
195
|
+
pending: dict[str, str] = {}
|
|
196
|
+
for message in messages:
|
|
197
|
+
if not isinstance(message, Message):
|
|
198
|
+
continue
|
|
199
|
+
if message.role == "assistant" and message.tool_calls:
|
|
200
|
+
for call in message.tool_calls:
|
|
201
|
+
name = _tool_name(call) or "tool"
|
|
202
|
+
args = _tool_args(call)
|
|
203
|
+
detail = str(args.get("path") or args.get("command") or "").strip()
|
|
204
|
+
summary = f"{name} {detail}".strip()
|
|
205
|
+
call_id = ""
|
|
206
|
+
if isinstance(call, dict):
|
|
207
|
+
call_id = str(call.get("id") or "")
|
|
208
|
+
lines.append(summary)
|
|
209
|
+
if call_id:
|
|
210
|
+
pending[call_id] = summary
|
|
211
|
+
continue
|
|
212
|
+
if message.role == "tool":
|
|
213
|
+
body = text_from_content(message.content).strip()
|
|
214
|
+
failed = body.startswith("[tool:")
|
|
215
|
+
status = "error" if failed else "ok"
|
|
216
|
+
key = message.tool_call_id or ""
|
|
217
|
+
prefix = pending.get(key) or "tool"
|
|
218
|
+
lines.append(f"{prefix} → {status}")
|
|
219
|
+
return lines
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def last_tool_results_from_messages(messages: Iterable[Message], *, max_items: int = 5) -> list[str]:
|
|
223
|
+
results: list[str] = []
|
|
224
|
+
for message in messages:
|
|
225
|
+
if isinstance(message, Message) and message.role == "tool":
|
|
226
|
+
results.append(text_from_content(message.content).strip())
|
|
227
|
+
return results[-max_items:]
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def build_run_state(
|
|
231
|
+
*,
|
|
232
|
+
config: EvaluationConfig,
|
|
233
|
+
phase: EvaluationPhase,
|
|
234
|
+
request: str = "",
|
|
235
|
+
messages: Optional[Iterable[Message]] = None,
|
|
236
|
+
plan_text: str = "",
|
|
237
|
+
final: str = "",
|
|
238
|
+
tool_calls: Optional[Iterable[object]] = None,
|
|
239
|
+
) -> RunState:
|
|
240
|
+
"""Assemble a capped, secret-redacted semantic snapshot."""
|
|
241
|
+
message_list = list(messages or [])
|
|
242
|
+
request_text = request.strip() or request_from_messages(message_list)
|
|
243
|
+
items = plan_items_from_markdown(plan_text)
|
|
244
|
+
files = files_modified_from_calls(tool_calls or (), limit=config.files_modified_max)
|
|
245
|
+
if not files:
|
|
246
|
+
files = files_modified_from_messages(message_list, limit=config.files_modified_max)
|
|
247
|
+
return RunState(
|
|
248
|
+
phase=phase,
|
|
249
|
+
rule=bound_text(config.rule, config.request_max_chars),
|
|
250
|
+
request=bound_text(request_text, config.request_max_chars),
|
|
251
|
+
brief=bound_text(first_line(request_text), config.brief_max_chars),
|
|
252
|
+
plan=bound_text(plan_text, config.plan_max_chars),
|
|
253
|
+
plan_items=items[:MAX_PLAN_ITEMS],
|
|
254
|
+
observations=bound_lines(
|
|
255
|
+
observations_from_messages(message_list), config.observations_max_chars
|
|
256
|
+
),
|
|
257
|
+
files_modified=[redact_field(path) for path in files],
|
|
258
|
+
last_tool_results=bound_lines(
|
|
259
|
+
last_tool_results_from_messages(message_list),
|
|
260
|
+
config.last_tool_results_max_chars,
|
|
261
|
+
),
|
|
262
|
+
final=bound_text(final, config.final_max_chars),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
__all__ = [
|
|
267
|
+
"PlanItem",
|
|
268
|
+
"RunState",
|
|
269
|
+
"WRITE_TOOLS",
|
|
270
|
+
"bound_lines",
|
|
271
|
+
"bound_text",
|
|
272
|
+
"build_run_state",
|
|
273
|
+
"clip_lines",
|
|
274
|
+
"clip_text",
|
|
275
|
+
"redact_eval_value",
|
|
276
|
+
"redact_field",
|
|
277
|
+
"files_modified_from_calls",
|
|
278
|
+
"files_modified_from_messages",
|
|
279
|
+
"first_line",
|
|
280
|
+
"last_tool_results_from_messages",
|
|
281
|
+
"observations_from_messages",
|
|
282
|
+
"plan_items_from_markdown",
|
|
283
|
+
"request_from_messages",
|
|
284
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Optional Langfuse telemetry for coding-agent runs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from coding_agent._lazy import resolve
|
|
8
|
+
from coding_agent.addons.langfuse.install import ensure_langfuse_installed, langfuse_available
|
|
9
|
+
|
|
10
|
+
_ADDON_EXPORTS = {
|
|
11
|
+
"LangfuseAddon": ("coding_agent.addons.langfuse.addon", "LangfuseAddon"),
|
|
12
|
+
"langfuse_from_config": ("coding_agent.addons.langfuse.addon", "langfuse_from_config"),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"LangfuseAddon",
|
|
17
|
+
"ensure_langfuse_installed",
|
|
18
|
+
"langfuse_available",
|
|
19
|
+
"langfuse_from_config",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def __getattr__(name: str) -> Any:
|
|
24
|
+
return resolve(globals(), name, _ADDON_EXPORTS)
|