trulens-apps-opencode 2.14.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.
@@ -0,0 +1,5 @@
1
+ """OpenCode hook instrumentation plugin."""
2
+
3
+ from trulens.apps.opencode.client import client_spec
4
+
5
+ __all__ = ["client_spec"]
@@ -0,0 +1,238 @@
1
+ """Minimal OpenCode-native plugin specification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ import shutil
8
+ import subprocess
9
+ from typing import Any, Mapping
10
+
11
+ from trulens.core.otel.client_hooks.clients import ClientSpec
12
+ from trulens.core.otel.client_hooks.clients import FieldAliases
13
+
14
+ _HOOK_EVENTS = (
15
+ "chat.message",
16
+ "tool.execute.before",
17
+ "tool.execute.after",
18
+ "experimental.text.complete",
19
+ "session.idle",
20
+ "session.error",
21
+ "file.edited",
22
+ )
23
+
24
+
25
+ def _first(payload: Mapping[str, Any], *keys: str) -> Any:
26
+ for key in keys:
27
+ value = payload.get(key)
28
+ if value is not None:
29
+ return value
30
+ return None
31
+
32
+
33
+ def _nested(payload: Mapping[str, Any], *path: str) -> Any:
34
+ current: Any = payload
35
+ for key in path:
36
+ if not isinstance(current, Mapping):
37
+ return None
38
+ current = current.get(key)
39
+ return current
40
+
41
+
42
+ def _prompt_from_parts(parts: Any) -> Any:
43
+ if not isinstance(parts, list):
44
+ return None
45
+ texts = [
46
+ part.get("text")
47
+ for part in parts
48
+ if isinstance(part, Mapping) and part.get("text")
49
+ ]
50
+ return "\n".join(texts) or None
51
+
52
+
53
+ def extract_overrides(payload: Mapping[str, Any]) -> Mapping[str, Any]:
54
+ """Flatten OpenCode camelCase and nested event payloads."""
55
+
56
+ event = payload.get("event")
57
+ properties = event.get("properties") if isinstance(event, Mapping) else {}
58
+ if not isinstance(properties, Mapping):
59
+ properties = {}
60
+ event_name = payload.get("hook_event_name")
61
+ if event_name is None and isinstance(event, Mapping):
62
+ event_name = event.get("type")
63
+ model = _first(payload, "model")
64
+ if isinstance(model, Mapping):
65
+ model = model.get("modelID") or model.get("model_id")
66
+ prompt = _first(payload, "prompt") or _prompt_from_parts(
67
+ payload.get("parts")
68
+ )
69
+ if prompt is None:
70
+ message = payload.get("message")
71
+ if isinstance(message, Mapping):
72
+ prompt = message.get("content") or _prompt_from_parts(
73
+ message.get("parts")
74
+ )
75
+ overrides = {
76
+ "conversation_id": _first(
77
+ payload,
78
+ "session_id",
79
+ "sessionID",
80
+ )
81
+ or properties.get("sessionID")
82
+ or _nested(payload, "event", "properties", "sessionID"),
83
+ "turn_id": _first(payload, "message_id", "messageID"),
84
+ "operation_id": _first(payload, "call_id", "callID", "tool_call_id"),
85
+ "event_name": event_name,
86
+ "tool_name": _first(payload, "tool_name", "tool"),
87
+ "prompt": prompt,
88
+ "response": _first(payload, "response", "text")
89
+ or _nested(payload, "output", "text"),
90
+ "tool_input": _first(payload, "tool_input", "args")
91
+ or _nested(payload, "output", "args"),
92
+ "tool_output": _first(payload, "tool_output")
93
+ or _nested(payload, "output", "output"),
94
+ }
95
+ if model:
96
+ overrides["model"] = model
97
+ return {key: value for key, value in overrides.items() if value is not None}
98
+
99
+
100
+ def _detect_version() -> str | None:
101
+ executable = shutil.which("opencode")
102
+ if executable is None:
103
+ return None
104
+ try:
105
+ result = subprocess.run(
106
+ [executable, "--version"],
107
+ capture_output=True,
108
+ check=True,
109
+ text=True,
110
+ timeout=5,
111
+ )
112
+ except (OSError, subprocess.SubprocessError):
113
+ return None
114
+ return result.stdout.strip() or None
115
+
116
+
117
+ def _plugin(command: str, client_version: str | None = None) -> str:
118
+ encoded = json.dumps(command)
119
+ encoded_version = json.dumps(client_version)
120
+ return f"""// managed_by: trulens-client-hooks
121
+ const COMMAND = {encoded}
122
+ const VERSION = {encoded_version}
123
+ async function ingest(payload) {{
124
+ try {{
125
+ const proc = Bun.spawn(["sh", "-c", COMMAND], {{
126
+ stdin: "pipe",
127
+ stdout: "ignore",
128
+ stderr: "pipe",
129
+ }})
130
+ proc.stdin.write(JSON.stringify(payload))
131
+ proc.stdin.end()
132
+ await proc.exited
133
+ }} catch (_error) {{
134
+ // Fail open: telemetry must never block OpenCode.
135
+ }}
136
+ }}
137
+
138
+ function textFromParts(parts) {{
139
+ if (!Array.isArray(parts)) {{
140
+ return undefined
141
+ }}
142
+ return parts
143
+ .map((part) => (part && part.text) || "")
144
+ .filter(Boolean)
145
+ .join("\\n") || undefined
146
+ }}
147
+
148
+ export const TruLensClientHooks = async ({{ directory }}) => {{
149
+ let lastSessionId
150
+ const send = async (payload) => {{
151
+ if (payload.session_id) {{
152
+ lastSessionId = payload.session_id
153
+ }}
154
+ await ingest({{ cwd: directory, client_version: VERSION, ...payload }})
155
+ }}
156
+ return {{
157
+ "chat.message": async (input, output) => {{
158
+ await send({{
159
+ session_id: input.sessionID,
160
+ message_id: input.messageID,
161
+ hook_event_name: "chat.message",
162
+ model: input.model && input.model.modelID,
163
+ prompt: textFromParts(output && output.parts),
164
+ }})
165
+ }},
166
+ "tool.execute.before": async (input, output) => {{
167
+ await send({{
168
+ session_id: input.sessionID,
169
+ hook_event_name: "tool.execute.before",
170
+ tool_name: input.tool,
171
+ tool_call_id: input.callID,
172
+ tool_input: output && output.args,
173
+ }})
174
+ }},
175
+ "tool.execute.after": async (input, output) => {{
176
+ await send({{
177
+ session_id: input.sessionID,
178
+ hook_event_name: "tool.execute.after",
179
+ tool_name: input.tool,
180
+ tool_call_id: input.callID,
181
+ tool_input: input.args,
182
+ tool_output: output && output.output,
183
+ }})
184
+ }},
185
+ "experimental.text.complete": async (input, output) => {{
186
+ await send({{
187
+ session_id: input.sessionID,
188
+ message_id: input.messageID,
189
+ response_message_id: input.messageID,
190
+ hook_event_name: "experimental.text.complete",
191
+ text: output && output.text,
192
+ }})
193
+ }},
194
+ event: async ({{ event }}) => {{
195
+ const type = event && event.type
196
+ const properties = (event && event.properties) || {{}}
197
+ const sessionId =
198
+ properties.sessionID || event.sessionID || lastSessionId
199
+ if (type === "session.idle" || type === "session.error") {{
200
+ await send({{
201
+ session_id: sessionId,
202
+ hook_event_name: type,
203
+ status: type === "session.error" ? "error" : "completed",
204
+ error:
205
+ type === "session.error"
206
+ ? properties.error || event.error
207
+ : undefined,
208
+ }})
209
+ }}
210
+ if (type === "file.edited") {{
211
+ await send({{
212
+ session_id: sessionId,
213
+ hook_event_name: "file.edited",
214
+ file_path: properties.file || event.path,
215
+ }})
216
+ }}
217
+ }},
218
+ }}
219
+ }}
220
+ """
221
+
222
+
223
+ client_spec = ClientSpec(
224
+ name="opencode",
225
+ aliases=("open-code",),
226
+ user_config_path=Path("~/.config/opencode/plugins/trulens-client-hooks.js"),
227
+ project_config_path=Path(".opencode/plugins/trulens-client-hooks.js"),
228
+ hook_events=_HOOK_EVENTS,
229
+ field_aliases=FieldAliases(
230
+ conversation=("session_id", "sessionID"),
231
+ turn=("message_id", "messageID"),
232
+ operation=("call_id", "callID", "tool_call_id"),
233
+ response=("text", "response", "output"),
234
+ ),
235
+ plugin_builder=_plugin,
236
+ version_detector=_detect_version,
237
+ extract_overrides=extract_overrides,
238
+ )
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: trulens-apps-opencode
3
+ Version: 2.14.0
4
+ Summary: OpenCode hook instrumentation plugin for TruLens.
5
+ License: MIT
6
+ Author: Snowflake Inc.
7
+ Author-email: ml-observability-wg-dl@snowflake.com
8
+ Requires-Python: >=3.9,<4.0
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Dist: trulens-core (>=2.0.0,<3.0.0)
18
+ Project-URL: Homepage, https://trulens.org/
19
+ Project-URL: Repository, https://github.com/truera/trulens
20
+ Description-Content-Type: text/markdown
21
+
22
+ # trulens-apps-opencode
23
+
24
+ OpenCode plugin configuration and payload mapping for TruLens client-hook
25
+ instrumentation.
26
+
@@ -0,0 +1,6 @@
1
+ trulens/apps/opencode/__init__.py,sha256=q-zuIAeYbz02ytBB2moUEjQgU-WOnE5UHTGUOhUL2WU,125
2
+ trulens/apps/opencode/client.py,sha256=_Ewwry8hZIjTlUN2oVoflAiduhkifd0NFqmdCxbayHo,7262
3
+ trulens_apps_opencode-2.14.0.dist-info/METADATA,sha256=miDHAmi4fvQYOb2Kpq9trxXVyHyJUsUsj12trnKMqS0,950
4
+ trulens_apps_opencode-2.14.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
5
+ trulens_apps_opencode-2.14.0.dist-info/entry_points.txt,sha256=W37Nf2GsO37hyyVKlXNhMr6zruQVIIFxxhfn78PzGz4,67
6
+ trulens_apps_opencode-2.14.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.3.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [trulens.client_hooks]
2
+ opencode=trulens.apps.opencode:client_spec
3
+