traceforge-toolkit 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 (205) hide show
  1. traceforge/__init__.py +72 -0
  2. traceforge/__main__.py +5 -0
  3. traceforge/_generated.py +254 -0
  4. traceforge/adapters/__init__.py +12 -0
  5. traceforge/adapters/base.py +82 -0
  6. traceforge/adapters/genai_otel.py +164 -0
  7. traceforge/adapters/mapped_json.py +297 -0
  8. traceforge/adapters/otel.py +220 -0
  9. traceforge/boundary/__init__.py +46 -0
  10. traceforge/boundary/data/boundary-model.joblib +0 -0
  11. traceforge/boundary/decode.py +87 -0
  12. traceforge/boundary/features.py +137 -0
  13. traceforge/boundary/inference.py +267 -0
  14. traceforge/boundary/inferencer.py +146 -0
  15. traceforge/classify/__init__.py +95 -0
  16. traceforge/classify/cmd.py +109 -0
  17. traceforge/classify/coding.py +213 -0
  18. traceforge/classify/config.py +554 -0
  19. traceforge/classify/core.py +266 -0
  20. traceforge/classify/data/binary_info.yaml +358 -0
  21. traceforge/classify/data/canonical_tools.yaml +251 -0
  22. traceforge/classify/data/effect_overrides.yaml +480 -0
  23. traceforge/classify/data/mcp_profiles.yaml +711 -0
  24. traceforge/classify/data/recommendation_rules.yaml +111 -0
  25. traceforge/classify/data/risk.yaml +534 -0
  26. traceforge/classify/data/shell_defaults.yaml +95 -0
  27. traceforge/classify/data/shell_rules.yaml +1192 -0
  28. traceforge/classify/data/tool_classifications.yaml +215 -0
  29. traceforge/classify/data/verb_inference.yaml +218 -0
  30. traceforge/classify/mcp.py +181 -0
  31. traceforge/classify/phases.py +75 -0
  32. traceforge/classify/powershell.py +93 -0
  33. traceforge/classify/registry.py +138 -0
  34. traceforge/classify/risk.py +553 -0
  35. traceforge/classify/rules.py +215 -0
  36. traceforge/classify/schema.yaml +282 -0
  37. traceforge/classify/shell.py +503 -0
  38. traceforge/classify/tools.py +91 -0
  39. traceforge/classify/workflow.py +32 -0
  40. traceforge/cli/__init__.py +42 -0
  41. traceforge/cli/config_cmd.py +130 -0
  42. traceforge/cli/detect.py +46 -0
  43. traceforge/cli/download_cmd.py +74 -0
  44. traceforge/cli/factory.py +60 -0
  45. traceforge/cli/gate_cmd.py +30 -0
  46. traceforge/cli/init_cmd.py +86 -0
  47. traceforge/cli/replay.py +130 -0
  48. traceforge/cli/runner.py +181 -0
  49. traceforge/cli/score.py +217 -0
  50. traceforge/cli/status.py +65 -0
  51. traceforge/cli/watch.py +297 -0
  52. traceforge/config/__init__.py +80 -0
  53. traceforge/config/defaults.py +149 -0
  54. traceforge/config/loader.py +239 -0
  55. traceforge/config/mappings.py +104 -0
  56. traceforge/config/models.py +579 -0
  57. traceforge/enricher.py +576 -0
  58. traceforge/formatting/__init__.py +12 -0
  59. traceforge/formatting/budget.py +92 -0
  60. traceforge/formatting/density.py +154 -0
  61. traceforge/gate/__init__.py +17 -0
  62. traceforge/gate/client.py +145 -0
  63. traceforge/gate/external.py +305 -0
  64. traceforge/gate/registry.py +108 -0
  65. traceforge/gate/server.py +174 -0
  66. traceforge/gates/__init__.py +8 -0
  67. traceforge/gates/pii.py +352 -0
  68. traceforge/gates/pii_patterns.yaml +228 -0
  69. traceforge/governance/__init__.py +121 -0
  70. traceforge/governance/assessor.py +441 -0
  71. traceforge/governance/budget.py +59 -0
  72. traceforge/governance/canonical.py +56 -0
  73. traceforge/governance/codec.py +414 -0
  74. traceforge/governance/context.py +249 -0
  75. traceforge/governance/drift.py +196 -0
  76. traceforge/governance/emitter.py +234 -0
  77. traceforge/governance/envelope.py +138 -0
  78. traceforge/governance/ifc.py +364 -0
  79. traceforge/governance/integrity.py +162 -0
  80. traceforge/governance/labeler.py +250 -0
  81. traceforge/governance/mcp_drift.py +328 -0
  82. traceforge/governance/monitor.py +539 -0
  83. traceforge/governance/observer.py +276 -0
  84. traceforge/governance/persistence.py +401 -0
  85. traceforge/governance/phase1.py +77 -0
  86. traceforge/governance/pii.py +276 -0
  87. traceforge/governance/pipeline.py +840 -0
  88. traceforge/governance/registry.py +110 -0
  89. traceforge/governance/results.py +183 -0
  90. traceforge/governance/risk_wrapper.py +113 -0
  91. traceforge/governance/rules.py +368 -0
  92. traceforge/governance/scorer.py +262 -0
  93. traceforge/governance/shield.py +318 -0
  94. traceforge/governance/state.py +466 -0
  95. traceforge/governance/types.py +176 -0
  96. traceforge/mappings/__init__.py +1 -0
  97. traceforge/mappings/aider.yaml +216 -0
  98. traceforge/mappings/aider_markdown.yaml +113 -0
  99. traceforge/mappings/amazonq.yaml +56 -0
  100. traceforge/mappings/antigravity.yaml +88 -0
  101. traceforge/mappings/claude.yaml +93 -0
  102. traceforge/mappings/cline.yaml +286 -0
  103. traceforge/mappings/codex.yaml +158 -0
  104. traceforge/mappings/continue_dev.yaml +57 -0
  105. traceforge/mappings/copilot.yaml +266 -0
  106. traceforge/mappings/copilot_markdown.yaml +72 -0
  107. traceforge/mappings/copilot_vscode.yaml +181 -0
  108. traceforge/mappings/crewai.yaml +592 -0
  109. traceforge/mappings/goose.yaml +128 -0
  110. traceforge/mappings/langgraph.yaml +165 -0
  111. traceforge/mappings/maf.yaml +90 -0
  112. traceforge/mappings/maf_transcript.yaml +99 -0
  113. traceforge/mappings/openai_agents.yaml +144 -0
  114. traceforge/mappings/opencode.yaml +473 -0
  115. traceforge/mappings/openhands.yaml +320 -0
  116. traceforge/mappings/pydantic_ai.yaml +183 -0
  117. traceforge/mappings/smolagents.yaml +89 -0
  118. traceforge/mappings/sweagent.yaml +82 -0
  119. traceforge/migrations/__init__.py +1 -0
  120. traceforge/migrations/env.py +60 -0
  121. traceforge/migrations/models.py +145 -0
  122. traceforge/migrations/runner.py +44 -0
  123. traceforge/migrations/script.py.mako +25 -0
  124. traceforge/migrations/versions/0001_initial.py +176 -0
  125. traceforge/migrations/versions/__init__.py +4 -0
  126. traceforge/models.py +29 -0
  127. traceforge/parsers/__init__.py +21 -0
  128. traceforge/parsers/aider.py +416 -0
  129. traceforge/parsers/base.py +226 -0
  130. traceforge/parsers/copilot.py +314 -0
  131. traceforge/phase/__init__.py +50 -0
  132. traceforge/phase/data/phase-model.joblib +0 -0
  133. traceforge/phase/data/potion-base-8M/README.md +99 -0
  134. traceforge/phase/data/potion-base-8M/config.json +13 -0
  135. traceforge/phase/data/potion-base-8M/model.safetensors +0 -0
  136. traceforge/phase/data/potion-base-8M/modules.json +14 -0
  137. traceforge/phase/data/potion-base-8M/tokenizer.json +1 -0
  138. traceforge/phase/event_rows.py +107 -0
  139. traceforge/phase/features.py +468 -0
  140. traceforge/phase/inference.py +279 -0
  141. traceforge/phase/inferencer.py +171 -0
  142. traceforge/phase/segmentation.py +258 -0
  143. traceforge/pipeline.py +891 -0
  144. traceforge/preprocessors/__init__.py +34 -0
  145. traceforge/preprocessors/amazonq.py +224 -0
  146. traceforge/preprocessors/antigravity.py +116 -0
  147. traceforge/preprocessors/claude.py +95 -0
  148. traceforge/preprocessors/cline.py +36 -0
  149. traceforge/preprocessors/codex.py +311 -0
  150. traceforge/preprocessors/continue_dev.py +119 -0
  151. traceforge/preprocessors/copilot_vscode.py +171 -0
  152. traceforge/preprocessors/goose.py +156 -0
  153. traceforge/preprocessors/maf_transcript.py +84 -0
  154. traceforge/preprocessors/openai_agents.py +86 -0
  155. traceforge/preprocessors/opencode.py +85 -0
  156. traceforge/preprocessors/openhands.py +36 -0
  157. traceforge/preprocessors/pydantic_ai.py +62 -0
  158. traceforge/preprocessors/registry.py +24 -0
  159. traceforge/preprocessors/smolagents.py +90 -0
  160. traceforge/py.typed +0 -0
  161. traceforge/sdk/__init__.py +59 -0
  162. traceforge/sdk/gate_policy.py +63 -0
  163. traceforge/sdk/gate_types.py +140 -0
  164. traceforge/sdk/pipeline.py +265 -0
  165. traceforge/sdk/verdict.py +81 -0
  166. traceforge/sinks/__init__.py +23 -0
  167. traceforge/sinks/base.py +95 -0
  168. traceforge/sinks/callback.py +132 -0
  169. traceforge/sinks/console.py +112 -0
  170. traceforge/sinks/factory.py +94 -0
  171. traceforge/sinks/jsonl.py +125 -0
  172. traceforge/sinks/otel_exporter.py +212 -0
  173. traceforge/sinks/parquet.py +260 -0
  174. traceforge/sinks/s3.py +206 -0
  175. traceforge/sinks/sqlite_output.py +234 -0
  176. traceforge/sinks/webhook.py +136 -0
  177. traceforge/sources/__init__.py +18 -0
  178. traceforge/sources/auto_detect.py +173 -0
  179. traceforge/sources/base.py +45 -0
  180. traceforge/sources/file_poll.py +136 -0
  181. traceforge/sources/file_watch.py +221 -0
  182. traceforge/sources/http_poll.py +141 -0
  183. traceforge/sources/replay.py +63 -0
  184. traceforge/sources/sqlite.py +187 -0
  185. traceforge/sources/sse.py +198 -0
  186. traceforge/telemetry/__init__.py +192 -0
  187. traceforge/title/__init__.py +23 -0
  188. traceforge/title/_resolve.py +79 -0
  189. traceforge/title/context.py +295 -0
  190. traceforge/title/data/boilerplate_files.json +14 -0
  191. traceforge/title/heuristics.py +429 -0
  192. traceforge/title/hygiene.py +90 -0
  193. traceforge/title/inference.py +314 -0
  194. traceforge/title/inferencer.py +477 -0
  195. traceforge/title/naming.py +398 -0
  196. traceforge/trace.py +291 -0
  197. traceforge/tracking/__init__.py +30 -0
  198. traceforge/tracking/models.py +115 -0
  199. traceforge/tracking/phase_tracker.py +288 -0
  200. traceforge/types.py +315 -0
  201. traceforge_toolkit-0.1.0.dist-info/METADATA +188 -0
  202. traceforge_toolkit-0.1.0.dist-info/RECORD +205 -0
  203. traceforge_toolkit-0.1.0.dist-info/WHEEL +4 -0
  204. traceforge_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
  205. traceforge_toolkit-0.1.0.dist-info/licenses/LICENSE +21 -0
traceforge/__init__.py ADDED
@@ -0,0 +1,72 @@
1
+ """traceforge — Agent event observation pipeline with pluggable storage backends."""
2
+
3
+ from traceforge.adapters.base import Adapter, JsonLineAdapter
4
+ from traceforge.adapters.mapped_json import MappedJsonAdapter
5
+ from traceforge.adapters.otel import OtelSpanAdapter
6
+ from traceforge.classify import (
7
+ ClassificationEngine,
8
+ Classification,
9
+ ClassifyConfig,
10
+ Phase,
11
+ Visibility,
12
+ classify_cmd_command,
13
+ classify_powershell_command,
14
+ classify_shell,
15
+ classify_tool,
16
+ get_default_registry,
17
+ load_config,
18
+ normalize_tool_name,
19
+ )
20
+ from traceforge.enricher import Enricher
21
+ from traceforge.parsers.aider import AiderPreParser
22
+ from traceforge.pipeline import EventPipeline
23
+ from traceforge.sinks.base import StorageSink
24
+ from traceforge.sinks.callback import CallbackSink
25
+ from traceforge.trace import EventTrace
26
+ from traceforge.types import (
27
+ KNOWN_KINDS,
28
+ EventKind,
29
+ EventMetadata,
30
+ IngestionMode,
31
+ SessionEvent,
32
+ TelemetrySpan,
33
+ TitleUpdate,
34
+ UsageRecord,
35
+ is_known_kind,
36
+ )
37
+
38
+ __all__ = [
39
+ # Adapters
40
+ "Adapter",
41
+ "JsonLineAdapter",
42
+ "MappedJsonAdapter",
43
+ "AiderPreParser",
44
+ "OtelSpanAdapter",
45
+ # Classification
46
+ "CallbackSink",
47
+ "Classification",
48
+ "ClassificationEngine",
49
+ "ClassifyConfig",
50
+ "Enricher",
51
+ "EventKind",
52
+ "EventMetadata",
53
+ "EventPipeline",
54
+ "IngestionMode",
55
+ "KNOWN_KINDS",
56
+ "Phase",
57
+ "SessionEvent",
58
+ "StorageSink",
59
+ "TelemetrySpan",
60
+ "EventTrace",
61
+ "TitleUpdate",
62
+ "UsageRecord",
63
+ "Visibility",
64
+ "classify_cmd_command",
65
+ "classify_powershell_command",
66
+ "classify_shell",
67
+ "classify_tool",
68
+ "get_default_registry",
69
+ "is_known_kind",
70
+ "load_config",
71
+ "normalize_tool_name",
72
+ ]
traceforge/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Allow `python -m traceforge` to invoke the CLI."""
2
+
3
+ from traceforge.cli import main
4
+
5
+ main()
@@ -0,0 +1,254 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: schema.yaml
3
+ # timestamp: 2026-06-14T10:43:42+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import StrEnum
8
+
9
+
10
+ class EventKind(StrEnum):
11
+ agent_completed = "agent.completed"
12
+ agent_failed = "agent.failed"
13
+ agent_handoff = "agent.handoff"
14
+ agent_spawned = "agent.spawned"
15
+ browser_action = "browser.action"
16
+ browser_launched = "browser.launched"
17
+ browser_result = "browser.result"
18
+ checkpoint_created = "checkpoint.created"
19
+ checkpoint_restored = "checkpoint.restored"
20
+ command_completed = "command.completed"
21
+ command_failed = "command.failed"
22
+ command_output = "command.output"
23
+ command_started = "command.started"
24
+ file_created = "file.created"
25
+ file_deleted = "file.deleted"
26
+ file_edited = "file.edited"
27
+ file_read = "file.read"
28
+ guardrail_failed = "guardrail.failed"
29
+ guardrail_passed = "guardrail.passed"
30
+ guardrail_started = "guardrail.started"
31
+ hook_completed = "hook.completed"
32
+ hook_failed = "hook.failed"
33
+ hook_started = "hook.started"
34
+ input_received = "input.received"
35
+ input_requested = "input.requested"
36
+ knowledge_query_completed = "knowledge.query.completed"
37
+ knowledge_query_started = "knowledge.query.started"
38
+ llm_call_completed = "llm.call.completed"
39
+ llm_call_failed = "llm.call.failed"
40
+ llm_call_started = "llm.call.started"
41
+ llm_output_chunk = "llm.output.chunk"
42
+ llm_thinking_chunk = "llm.thinking.chunk"
43
+ mcp_connection_completed = "mcp.connection.completed"
44
+ mcp_connection_failed = "mcp.connection.failed"
45
+ mcp_connection_started = "mcp.connection.started"
46
+ memory_query_completed = "memory.query.completed"
47
+ memory_query_started = "memory.query.started"
48
+ memory_save_completed = "memory.save.completed"
49
+ memory_save_started = "memory.save.started"
50
+ message_assistant = "message.assistant"
51
+ message_assistant_chunk = "message.assistant.chunk"
52
+ message_system = "message.system"
53
+ message_user = "message.user"
54
+ permission_denied = "permission.denied"
55
+ permission_granted = "permission.granted"
56
+ permission_requested = "permission.requested"
57
+ planning_completed = "planning.completed"
58
+ planning_failed = "planning.failed"
59
+ planning_started = "planning.started"
60
+ raw = "raw"
61
+ reasoning_completed = "reasoning.completed"
62
+ reasoning_started = "reasoning.started"
63
+ session_abort = "session.abort"
64
+ session_ended = "session.ended"
65
+ session_error = "session.error"
66
+ session_idle = "session.idle"
67
+ session_info = "session.info"
68
+ session_paused = "session.paused"
69
+ session_resumed = "session.resumed"
70
+ session_started = "session.started"
71
+ session_warning = "session.warning"
72
+ skill_invoked = "skill.invoked"
73
+ task_completed = "task.completed"
74
+ task_failed = "task.failed"
75
+ task_started = "task.started"
76
+ telemetry_usage = "telemetry.usage"
77
+ tool_call_completed = "tool.call.completed"
78
+ tool_call_failed = "tool.call.failed"
79
+ tool_call_started = "tool.call.started"
80
+ tool_output = "tool.output"
81
+ tool_progress = "tool.progress"
82
+ tool_result_chunk = "tool.result.chunk"
83
+ tool_validation_failed = "tool.validation.failed"
84
+ turn_ended = "turn.ended"
85
+ turn_skipped = "turn.skipped"
86
+ turn_started = "turn.started"
87
+ workflow_completed = "workflow.completed"
88
+ workflow_failed = "workflow.failed"
89
+ workflow_started = "workflow.started"
90
+
91
+
92
+ class Mechanism(StrEnum):
93
+ communication_system = "communication.system"
94
+ communication_user = "communication.user"
95
+ database = "database"
96
+ database_nosql = "database.nosql"
97
+ database_sql = "database.sql"
98
+ delegation_agent = "delegation.agent"
99
+ filesystem = "filesystem"
100
+ network_http = "network.http"
101
+ process = "process"
102
+ process_shell = "process.shell"
103
+ unknown = "unknown"
104
+
105
+
106
+ class Effect(StrEnum):
107
+ destructive = "destructive"
108
+ mutating = "mutating"
109
+ read_only = "read_only"
110
+
111
+
112
+ class Scope(StrEnum):
113
+ artifact_build_output = "artifact.build_output"
114
+ artifact_config = "artifact.config"
115
+ artifact_container_image = "artifact.container_image"
116
+ artifact_dependency = "artifact.dependency"
117
+ artifact_documentation = "artifact.documentation"
118
+ artifact_repository = "artifact.repository"
119
+ artifact_source_code = "artifact.source_code"
120
+ artifact_test_code = "artifact.test_code"
121
+ configuration_ci_cd = "configuration.ci_cd"
122
+ configuration_dependency = "configuration.dependency"
123
+ configuration_infrastructure = "configuration.infrastructure"
124
+ external_api = "external.api"
125
+ external_service = "external.service"
126
+ state_deployment = "state.deployment"
127
+ state_repository = "state.repository"
128
+ system_network = "system.network"
129
+ system_os = "system.os"
130
+ system_secrets = "system.secrets"
131
+
132
+
133
+ class Role(StrEnum):
134
+ communicator_system_reporter = "communicator.system_reporter"
135
+ communicator_user_prompt = "communicator.user_prompt"
136
+ executor_container_runtime = "executor.container_runtime"
137
+ executor_script_runner = "executor.script_runner"
138
+ modifier_file_editor = "modifier.file_editor"
139
+ orchestrator_ci_cd = "orchestrator.ci_cd"
140
+ orchestrator_package_manager = "orchestrator.package_manager"
141
+ orchestrator_task_runner = "orchestrator.task_runner"
142
+ persistence_cache = "persistence.cache"
143
+ persistence_database = "persistence.database"
144
+ persistence_version_control = "persistence.version_control"
145
+ retriever_api_client = "retriever.api_client"
146
+ retriever_file_browser = "retriever.file_browser"
147
+ retriever_search_index = "retriever.search_index"
148
+ retriever_web_scraper = "retriever.web_scraper"
149
+ validator_security_scanner = "validator.security_scanner"
150
+ validator_test_runner = "validator.test_runner"
151
+
152
+
153
+ class Action(StrEnum):
154
+ analyze = "analyze"
155
+ analyze_diff = "analyze.diff"
156
+ analyze_measure = "analyze.measure"
157
+ analyze_profile = "analyze.profile"
158
+ configure = "configure"
159
+ configure_install = "configure.install"
160
+ configure_provision = "configure.provision"
161
+ configure_setup = "configure.setup"
162
+ deliver = "deliver"
163
+ deliver_deploy = "deliver.deploy"
164
+ deliver_publish = "deliver.publish"
165
+ deliver_push = "deliver.push"
166
+ deliver_release = "deliver.release"
167
+ execute = "execute"
168
+ execute_repl = "execute.repl"
169
+ execute_run_script = "execute.run_script"
170
+ execute_script = "execute.script"
171
+ execute_service = "execute.service"
172
+ generate = "generate"
173
+ generate_code = "generate.code"
174
+ generate_documentation = "generate.documentation"
175
+ generate_scaffold = "generate.scaffold"
176
+ modify = "modify"
177
+ modify_edit = "modify.edit"
178
+ modify_merge = "modify.merge"
179
+ modify_rebase = "modify.rebase"
180
+ persist = "persist"
181
+ persist_commit = "persist.commit"
182
+ persist_install = "persist.install"
183
+ persist_stage = "persist.stage"
184
+ persist_write = "persist.write"
185
+ remove = "remove"
186
+ remove_clean = "remove.clean"
187
+ remove_delete = "remove.delete"
188
+ remove_teardown = "remove.teardown"
189
+ remove_uninstall = "remove.uninstall"
190
+ retrieve = "retrieve"
191
+ retrieve_browse = "retrieve.browse"
192
+ retrieve_diff = "retrieve.diff"
193
+ retrieve_query = "retrieve.query"
194
+ retrieve_read = "retrieve.read"
195
+ retrieve_search = "retrieve.search"
196
+ transform = "transform"
197
+ transform_bundle = "transform.bundle"
198
+ transform_compile = "transform.compile"
199
+ transform_format = "transform.format"
200
+ transform_refactor = "transform.refactor"
201
+ transform_transpile = "transform.transpile"
202
+ validate = "validate"
203
+ validate_build_check = "validate.build_check"
204
+ validate_lint = "validate.lint"
205
+ validate_security_scan = "validate.security_scan"
206
+ validate_test = "validate.test"
207
+ validate_typecheck = "validate.typecheck"
208
+
209
+
210
+ class Capability(StrEnum):
211
+ budget_pressure = "budget_pressure"
212
+ credential_exposure = "credential_exposure"
213
+ elevated_privilege = "elevated_privilege"
214
+ filesystem_read = "filesystem_read"
215
+ filesystem_write = "filesystem_write"
216
+ human_interaction = "human_interaction"
217
+ integrity_unverified = "integrity_unverified"
218
+ mcp_drift = "mcp_drift"
219
+ network_inbound = "network_inbound"
220
+ network_outbound = "network_outbound"
221
+ pii_exposure = "pii_exposure"
222
+ subprocess = "subprocess"
223
+ uses_credentials = "uses_credentials"
224
+
225
+
226
+ class Structure(StrEnum):
227
+ conditional = "conditional"
228
+ ifc_violation = "ifc_violation"
229
+ interactive = "interactive"
230
+ parallel = "parallel"
231
+ phase_anomaly = "phase_anomaly"
232
+ piped = "piped"
233
+ sequential = "sequential"
234
+ tainted_flow = "tainted_flow"
235
+
236
+
237
+ class RiskBand(StrEnum):
238
+ safe = "safe"
239
+ caution = "caution"
240
+ danger = "danger"
241
+ critical = "critical"
242
+ unknown = "unknown"
243
+
244
+
245
+ class Recommendation(StrEnum):
246
+ allow = "allow"
247
+ deny = "deny"
248
+ escalate = "escalate"
249
+ warn = "warn"
250
+
251
+
252
+ class Decision(StrEnum):
253
+ allow = "allow"
254
+ deny = "deny"
@@ -0,0 +1,12 @@
1
+ """Adapters for parsing raw agent output into SessionEvents."""
2
+
3
+ from traceforge.adapters.base import Adapter, JsonLineAdapter
4
+ from traceforge.adapters.mapped_json import MappedJsonAdapter
5
+ from traceforge.adapters.otel import OtelSpanAdapter
6
+
7
+ __all__ = [
8
+ "Adapter",
9
+ "JsonLineAdapter",
10
+ "MappedJsonAdapter",
11
+ "OtelSpanAdapter",
12
+ ]
@@ -0,0 +1,82 @@
1
+ """Base adapter interfaces for parsing raw input into SessionEvents."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from abc import ABC, abstractmethod
8
+ from collections.abc import Iterator
9
+ from typing import Any
10
+
11
+ from traceforge.types import SessionEvent
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class Adapter(ABC):
17
+ """Root interface: parse raw bytes/str into SessionEvents."""
18
+
19
+ @abstractmethod
20
+ def parse(self, raw: bytes | str) -> Iterator[SessionEvent]:
21
+ """Parse raw input and yield zero or more SessionEvents.
22
+
23
+ Must never raise — log warnings for unparseable input and continue.
24
+ """
25
+ ...
26
+
27
+
28
+ class JsonLineAdapter(Adapter):
29
+ """Base for adapters that consume one JSON object per line.
30
+
31
+ Handles the common boilerplate:
32
+ - bytes → str decoding
33
+ - JSON parsing with error logging
34
+ - dict validation
35
+ - Delegates to ``parse_dict()`` which subclasses implement
36
+
37
+ The parsed dict is automatically attached as ``raw_event`` on every
38
+ SessionEvent yielded by ``parse_dict()``.
39
+ """
40
+
41
+ @abstractmethod
42
+ def parse_dict(self, obj: dict[str, Any]) -> Iterator[SessionEvent]:
43
+ """Convert a parsed JSON dict into SessionEvents.
44
+
45
+ Subclasses implement framework-specific deserialization here.
46
+ The ``raw_event`` field is set automatically by the base class.
47
+ """
48
+ ...
49
+
50
+ def parse(self, raw: bytes | str) -> Iterator[SessionEvent]:
51
+ if isinstance(raw, bytes):
52
+ try:
53
+ text = raw.decode("utf-8")
54
+ except (UnicodeDecodeError, ValueError):
55
+ logger.warning("%s: failed to decode bytes as UTF-8", self.__class__.__name__)
56
+ return
57
+ else:
58
+ text = raw
59
+ text = text.strip()
60
+ if not text:
61
+ return
62
+
63
+ try:
64
+ obj = json.loads(text)
65
+ except (json.JSONDecodeError, ValueError):
66
+ logger.warning("%s: failed to parse JSON line", self.__class__.__name__)
67
+ return
68
+
69
+ if not isinstance(obj, dict):
70
+ logger.warning(
71
+ "%s: expected JSON object, got %s", self.__class__.__name__, type(obj).__name__
72
+ )
73
+ return
74
+
75
+ try:
76
+ for event in self.parse_dict(obj):
77
+ if event.raw_event is None:
78
+ yield event.model_copy(update={"raw_event": obj})
79
+ else:
80
+ yield event
81
+ except Exception as exc:
82
+ logger.warning("%s: parse_dict raised: %s", self.__class__.__name__, exc)
@@ -0,0 +1,164 @@
1
+ """Generic GenAI OTel adapter — universal receiver for gen_ai.* semantic conventions.
2
+
3
+ Handles any framework following OpenTelemetry GenAI semantic conventions:
4
+ - AutoGen, Semantic Kernel, Vercel AI SDK, Mastra, Agno, Codex CLI (OTel mode)
5
+
6
+ When tool arguments are absent (most frameworks gate them as "sensitive"),
7
+ the adapter still emits events but flags governance confidence as LOW.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import logging
13
+ from collections.abc import Iterator
14
+ from datetime import datetime, timezone
15
+ from typing import Any
16
+
17
+ from traceforge.adapters.base import Adapter
18
+ from traceforge.types import EventKind, EventMetadata, SessionEvent
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+ # GenAI semantic convention attribute keys
23
+ _TOOL_NAME = "gen_ai.tool.name"
24
+ _TOOL_CALL_ID = "gen_ai.tool.call.id"
25
+ _TOOL_ARGUMENTS = "gen_ai.tool.call.arguments"
26
+ _TOOL_RESULT = "gen_ai.tool.call.result"
27
+ _TOOL_DESCRIPTION = "gen_ai.tool.description"
28
+ _OPERATION = "gen_ai.operation.name"
29
+ _SYSTEM = "gen_ai.system"
30
+ _MODEL = "gen_ai.request.model"
31
+ _INPUT_TOKENS = "gen_ai.usage.input_tokens"
32
+ _OUTPUT_TOKENS = "gen_ai.usage.output_tokens"
33
+
34
+
35
+ class GenAIOtelAdapter(Adapter):
36
+ """Ingests OTel spans following GenAI semantic conventions.
37
+
38
+ Works with any framework that emits gen_ai.* attributes on spans.
39
+ Extracts tool calls, model invocations, and agent operations.
40
+ """
41
+
42
+ def __init__(self, session_id: str | None = None) -> None:
43
+ self._session_id = session_id or "otel-session"
44
+
45
+ def parse(self, raw: str | bytes) -> Iterator[SessionEvent]:
46
+ """Parse a JSON string containing one or more OTel spans."""
47
+ import json
48
+
49
+ if isinstance(raw, bytes):
50
+ try:
51
+ raw = raw.decode("utf-8")
52
+ except (UnicodeDecodeError, ValueError):
53
+ return
54
+
55
+ try:
56
+ data = json.loads(raw)
57
+ except (json.JSONDecodeError, TypeError):
58
+ return
59
+
60
+ if isinstance(data, dict):
61
+ yield from self._parse_span(data)
62
+ elif isinstance(data, list):
63
+ for item in data:
64
+ if isinstance(item, dict):
65
+ yield from self._parse_span(item)
66
+
67
+ def _parse_span(self, span: dict[str, Any]) -> Iterator[SessionEvent]:
68
+ """Convert a single OTel span dict to SessionEvent(s)."""
69
+ attributes = _normalize_attributes(span.get("attributes", {}))
70
+ operation = attributes.get(_OPERATION, "")
71
+ tool_name = attributes.get(_TOOL_NAME)
72
+
73
+ # Determine event kind
74
+ if tool_name or operation == "execute_tool":
75
+ kind = EventKind.TOOL_CALL
76
+ elif operation in ("chat", "text_completion", "generate"):
77
+ kind = EventKind.LLM_CALL
78
+ else:
79
+ kind = EventKind.RAW
80
+
81
+ # Extract timestamp
82
+ start_ns = span.get("startTimeUnixNano") or span.get("start_time_unix_nano")
83
+ if start_ns:
84
+ timestamp = datetime.fromtimestamp(int(start_ns) / 1_000_000_000, tz=timezone.utc)
85
+ else:
86
+ timestamp = datetime.now(timezone.utc)
87
+
88
+ # Build payload
89
+ payload: dict[str, Any] = {}
90
+ if tool_name:
91
+ payload["tool_name"] = tool_name
92
+
93
+ tool_args = attributes.get(_TOOL_ARGUMENTS)
94
+ if tool_args:
95
+ payload["arguments"] = tool_args
96
+
97
+ tool_result = attributes.get(_TOOL_RESULT)
98
+ if tool_result:
99
+ payload["result"] = tool_result
100
+
101
+ tool_call_id = attributes.get(_TOOL_CALL_ID)
102
+ if tool_call_id:
103
+ payload["tool_call_id"] = tool_call_id
104
+
105
+ system = attributes.get(_SYSTEM)
106
+ if system:
107
+ payload["system"] = system
108
+
109
+ model = attributes.get(_MODEL)
110
+ if model:
111
+ payload["model"] = model
112
+
113
+ # Flag confidence based on data completeness
114
+ has_args = bool(tool_args)
115
+ payload["governance_confidence"] = "high" if has_args else "low"
116
+ if not has_args and tool_name:
117
+ payload["governance_note"] = (
118
+ "Tool arguments not included in span — governance classification is best-effort. "
119
+ "Enable sensitive content export in your framework for full governance."
120
+ )
121
+
122
+ # Session ID from span attributes or default
123
+ session_id = (
124
+ attributes.get("session.id")
125
+ or attributes.get("gen_ai.session.id")
126
+ or span.get("traceId", "")[:16]
127
+ or self._session_id
128
+ )
129
+
130
+ metadata = EventMetadata(
131
+ source_framework=system or "genai-otel",
132
+ ingestion_mode="stream",
133
+ raw_kind=span.get("name", ""),
134
+ span_id=span.get("spanId") or span.get("span_id"),
135
+ parent_id=span.get("parentSpanId") or span.get("parent_span_id"),
136
+ )
137
+
138
+ yield SessionEvent(
139
+ kind=kind,
140
+ session_id=session_id,
141
+ timestamp=timestamp,
142
+ payload=payload,
143
+ metadata=metadata,
144
+ raw_event=span,
145
+ )
146
+
147
+
148
+ def _normalize_attributes(attrs: Any) -> dict[str, Any]:
149
+ """Normalize OTel attributes from either list-of-dicts or flat dict format."""
150
+ if isinstance(attrs, dict):
151
+ return attrs
152
+ if isinstance(attrs, list):
153
+ result: dict[str, Any] = {}
154
+ for item in attrs:
155
+ if isinstance(item, dict) and "key" in item:
156
+ val = item.get("value", {})
157
+ if isinstance(val, dict):
158
+ for v in val.values():
159
+ result[item["key"]] = v
160
+ break
161
+ else:
162
+ result[item["key"]] = val
163
+ return result
164
+ return {}