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.
- traceforge/__init__.py +72 -0
- traceforge/__main__.py +5 -0
- traceforge/_generated.py +254 -0
- traceforge/adapters/__init__.py +12 -0
- traceforge/adapters/base.py +82 -0
- traceforge/adapters/genai_otel.py +164 -0
- traceforge/adapters/mapped_json.py +297 -0
- traceforge/adapters/otel.py +220 -0
- traceforge/boundary/__init__.py +46 -0
- traceforge/boundary/data/boundary-model.joblib +0 -0
- traceforge/boundary/decode.py +87 -0
- traceforge/boundary/features.py +137 -0
- traceforge/boundary/inference.py +267 -0
- traceforge/boundary/inferencer.py +146 -0
- traceforge/classify/__init__.py +95 -0
- traceforge/classify/cmd.py +109 -0
- traceforge/classify/coding.py +213 -0
- traceforge/classify/config.py +554 -0
- traceforge/classify/core.py +266 -0
- traceforge/classify/data/binary_info.yaml +358 -0
- traceforge/classify/data/canonical_tools.yaml +251 -0
- traceforge/classify/data/effect_overrides.yaml +480 -0
- traceforge/classify/data/mcp_profiles.yaml +711 -0
- traceforge/classify/data/recommendation_rules.yaml +111 -0
- traceforge/classify/data/risk.yaml +534 -0
- traceforge/classify/data/shell_defaults.yaml +95 -0
- traceforge/classify/data/shell_rules.yaml +1192 -0
- traceforge/classify/data/tool_classifications.yaml +215 -0
- traceforge/classify/data/verb_inference.yaml +218 -0
- traceforge/classify/mcp.py +181 -0
- traceforge/classify/phases.py +75 -0
- traceforge/classify/powershell.py +93 -0
- traceforge/classify/registry.py +138 -0
- traceforge/classify/risk.py +553 -0
- traceforge/classify/rules.py +215 -0
- traceforge/classify/schema.yaml +282 -0
- traceforge/classify/shell.py +503 -0
- traceforge/classify/tools.py +91 -0
- traceforge/classify/workflow.py +32 -0
- traceforge/cli/__init__.py +42 -0
- traceforge/cli/config_cmd.py +130 -0
- traceforge/cli/detect.py +46 -0
- traceforge/cli/download_cmd.py +74 -0
- traceforge/cli/factory.py +60 -0
- traceforge/cli/gate_cmd.py +30 -0
- traceforge/cli/init_cmd.py +86 -0
- traceforge/cli/replay.py +130 -0
- traceforge/cli/runner.py +181 -0
- traceforge/cli/score.py +217 -0
- traceforge/cli/status.py +65 -0
- traceforge/cli/watch.py +297 -0
- traceforge/config/__init__.py +80 -0
- traceforge/config/defaults.py +149 -0
- traceforge/config/loader.py +239 -0
- traceforge/config/mappings.py +104 -0
- traceforge/config/models.py +579 -0
- traceforge/enricher.py +576 -0
- traceforge/formatting/__init__.py +12 -0
- traceforge/formatting/budget.py +92 -0
- traceforge/formatting/density.py +154 -0
- traceforge/gate/__init__.py +17 -0
- traceforge/gate/client.py +145 -0
- traceforge/gate/external.py +305 -0
- traceforge/gate/registry.py +108 -0
- traceforge/gate/server.py +174 -0
- traceforge/gates/__init__.py +8 -0
- traceforge/gates/pii.py +352 -0
- traceforge/gates/pii_patterns.yaml +228 -0
- traceforge/governance/__init__.py +121 -0
- traceforge/governance/assessor.py +441 -0
- traceforge/governance/budget.py +59 -0
- traceforge/governance/canonical.py +56 -0
- traceforge/governance/codec.py +414 -0
- traceforge/governance/context.py +249 -0
- traceforge/governance/drift.py +196 -0
- traceforge/governance/emitter.py +234 -0
- traceforge/governance/envelope.py +138 -0
- traceforge/governance/ifc.py +364 -0
- traceforge/governance/integrity.py +162 -0
- traceforge/governance/labeler.py +250 -0
- traceforge/governance/mcp_drift.py +328 -0
- traceforge/governance/monitor.py +539 -0
- traceforge/governance/observer.py +276 -0
- traceforge/governance/persistence.py +401 -0
- traceforge/governance/phase1.py +77 -0
- traceforge/governance/pii.py +276 -0
- traceforge/governance/pipeline.py +840 -0
- traceforge/governance/registry.py +110 -0
- traceforge/governance/results.py +183 -0
- traceforge/governance/risk_wrapper.py +113 -0
- traceforge/governance/rules.py +368 -0
- traceforge/governance/scorer.py +262 -0
- traceforge/governance/shield.py +318 -0
- traceforge/governance/state.py +466 -0
- traceforge/governance/types.py +176 -0
- traceforge/mappings/__init__.py +1 -0
- traceforge/mappings/aider.yaml +216 -0
- traceforge/mappings/aider_markdown.yaml +113 -0
- traceforge/mappings/amazonq.yaml +56 -0
- traceforge/mappings/antigravity.yaml +88 -0
- traceforge/mappings/claude.yaml +93 -0
- traceforge/mappings/cline.yaml +286 -0
- traceforge/mappings/codex.yaml +158 -0
- traceforge/mappings/continue_dev.yaml +57 -0
- traceforge/mappings/copilot.yaml +266 -0
- traceforge/mappings/copilot_markdown.yaml +72 -0
- traceforge/mappings/copilot_vscode.yaml +181 -0
- traceforge/mappings/crewai.yaml +592 -0
- traceforge/mappings/goose.yaml +128 -0
- traceforge/mappings/langgraph.yaml +165 -0
- traceforge/mappings/maf.yaml +90 -0
- traceforge/mappings/maf_transcript.yaml +99 -0
- traceforge/mappings/openai_agents.yaml +144 -0
- traceforge/mappings/opencode.yaml +473 -0
- traceforge/mappings/openhands.yaml +320 -0
- traceforge/mappings/pydantic_ai.yaml +183 -0
- traceforge/mappings/smolagents.yaml +89 -0
- traceforge/mappings/sweagent.yaml +82 -0
- traceforge/migrations/__init__.py +1 -0
- traceforge/migrations/env.py +60 -0
- traceforge/migrations/models.py +145 -0
- traceforge/migrations/runner.py +44 -0
- traceforge/migrations/script.py.mako +25 -0
- traceforge/migrations/versions/0001_initial.py +176 -0
- traceforge/migrations/versions/__init__.py +4 -0
- traceforge/models.py +29 -0
- traceforge/parsers/__init__.py +21 -0
- traceforge/parsers/aider.py +416 -0
- traceforge/parsers/base.py +226 -0
- traceforge/parsers/copilot.py +314 -0
- traceforge/phase/__init__.py +50 -0
- traceforge/phase/data/phase-model.joblib +0 -0
- traceforge/phase/data/potion-base-8M/README.md +99 -0
- traceforge/phase/data/potion-base-8M/config.json +13 -0
- traceforge/phase/data/potion-base-8M/model.safetensors +0 -0
- traceforge/phase/data/potion-base-8M/modules.json +14 -0
- traceforge/phase/data/potion-base-8M/tokenizer.json +1 -0
- traceforge/phase/event_rows.py +107 -0
- traceforge/phase/features.py +468 -0
- traceforge/phase/inference.py +279 -0
- traceforge/phase/inferencer.py +171 -0
- traceforge/phase/segmentation.py +258 -0
- traceforge/pipeline.py +891 -0
- traceforge/preprocessors/__init__.py +34 -0
- traceforge/preprocessors/amazonq.py +224 -0
- traceforge/preprocessors/antigravity.py +116 -0
- traceforge/preprocessors/claude.py +95 -0
- traceforge/preprocessors/cline.py +36 -0
- traceforge/preprocessors/codex.py +311 -0
- traceforge/preprocessors/continue_dev.py +119 -0
- traceforge/preprocessors/copilot_vscode.py +171 -0
- traceforge/preprocessors/goose.py +156 -0
- traceforge/preprocessors/maf_transcript.py +84 -0
- traceforge/preprocessors/openai_agents.py +86 -0
- traceforge/preprocessors/opencode.py +85 -0
- traceforge/preprocessors/openhands.py +36 -0
- traceforge/preprocessors/pydantic_ai.py +62 -0
- traceforge/preprocessors/registry.py +24 -0
- traceforge/preprocessors/smolagents.py +90 -0
- traceforge/py.typed +0 -0
- traceforge/sdk/__init__.py +59 -0
- traceforge/sdk/gate_policy.py +63 -0
- traceforge/sdk/gate_types.py +140 -0
- traceforge/sdk/pipeline.py +265 -0
- traceforge/sdk/verdict.py +81 -0
- traceforge/sinks/__init__.py +23 -0
- traceforge/sinks/base.py +95 -0
- traceforge/sinks/callback.py +132 -0
- traceforge/sinks/console.py +112 -0
- traceforge/sinks/factory.py +94 -0
- traceforge/sinks/jsonl.py +125 -0
- traceforge/sinks/otel_exporter.py +212 -0
- traceforge/sinks/parquet.py +260 -0
- traceforge/sinks/s3.py +206 -0
- traceforge/sinks/sqlite_output.py +234 -0
- traceforge/sinks/webhook.py +136 -0
- traceforge/sources/__init__.py +18 -0
- traceforge/sources/auto_detect.py +173 -0
- traceforge/sources/base.py +45 -0
- traceforge/sources/file_poll.py +136 -0
- traceforge/sources/file_watch.py +221 -0
- traceforge/sources/http_poll.py +141 -0
- traceforge/sources/replay.py +63 -0
- traceforge/sources/sqlite.py +187 -0
- traceforge/sources/sse.py +198 -0
- traceforge/telemetry/__init__.py +192 -0
- traceforge/title/__init__.py +23 -0
- traceforge/title/_resolve.py +79 -0
- traceforge/title/context.py +295 -0
- traceforge/title/data/boilerplate_files.json +14 -0
- traceforge/title/heuristics.py +429 -0
- traceforge/title/hygiene.py +90 -0
- traceforge/title/inference.py +314 -0
- traceforge/title/inferencer.py +477 -0
- traceforge/title/naming.py +398 -0
- traceforge/trace.py +291 -0
- traceforge/tracking/__init__.py +30 -0
- traceforge/tracking/models.py +115 -0
- traceforge/tracking/phase_tracker.py +288 -0
- traceforge/types.py +315 -0
- traceforge_toolkit-0.1.0.dist-info/METADATA +188 -0
- traceforge_toolkit-0.1.0.dist-info/RECORD +205 -0
- traceforge_toolkit-0.1.0.dist-info/WHEEL +4 -0
- traceforge_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
- 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
traceforge/_generated.py
ADDED
|
@@ -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 {}
|