decodingtrust-agent-sdk 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.
- agent/__init__.py +30 -0
- agent/claudesdk/__init__.py +8 -0
- agent/claudesdk/example.py +221 -0
- agent/claudesdk/src/__init__.py +8 -0
- agent/claudesdk/src/agent.py +400 -0
- agent/claudesdk/src/mcp_proxy.py +409 -0
- agent/claudesdk/src/utils.py +420 -0
- agent/googleadk/__init__.py +15 -0
- agent/googleadk/example.py +237 -0
- agent/googleadk/src/__init__.py +12 -0
- agent/googleadk/src/agent.py +401 -0
- agent/googleadk/src/mcp_wrapper.py +163 -0
- agent/googleadk/src/utils.py +602 -0
- agent/langchain/__init__.py +8 -0
- agent/langchain/example.py +213 -0
- agent/langchain/src/__init__.py +8 -0
- agent/langchain/src/agent.py +645 -0
- agent/langchain/src/utils.py +433 -0
- agent/openaisdk/__init__.py +17 -0
- agent/openaisdk/example.py +228 -0
- agent/openaisdk/src/__init__.py +12 -0
- agent/openaisdk/src/agent.py +491 -0
- agent/openaisdk/src/agent_wrapper.py +143 -0
- agent/openaisdk/src/mcp_wrapper.py +395 -0
- agent/openaisdk/src/utils.py +493 -0
- agent/openclaw/__init__.py +10 -0
- agent/openclaw/example.py +251 -0
- agent/openclaw/src/__init__.py +14 -0
- agent/openclaw/src/agent.py +930 -0
- agent/openclaw/src/helpers/__init__.py +1 -0
- agent/openclaw/src/helpers/auth_helpers.py +55 -0
- agent/openclaw/src/mcp_proxy.py +564 -0
- agent/openclaw/src/plugin_generator.py +231 -0
- agent/openclaw/src/utils.py +341 -0
- agent/pocketflow/__init__.py +18 -0
- agent/pocketflow/example.py +221 -0
- agent/pocketflow/prompts/react_agent.py +46 -0
- agent/pocketflow/src/__init__.py +6 -0
- agent/pocketflow/src/agent.py +507 -0
- agent/pocketflow/src/agent_wrapper.py +159 -0
- agent/pocketflow/src/async_helper.py +92 -0
- agent/pocketflow/src/mcp_react_agent.py +279 -0
- agent/pocketflow/src/native_agent.py +74 -0
- agent/pocketflow/src/nodes.py +467 -0
- benchmark/__init__.py +0 -0
- benchmark/browser/benign.jsonl +34 -0
- benchmark/browser/direct.jsonl +85 -0
- benchmark/browser/indirect.jsonl +82 -0
- benchmark/code/benign.jsonl +0 -0
- benchmark/code/direct.jsonl +121 -0
- benchmark/code/indirect.jsonl +165 -0
- benchmark/crm/benign.jsonl +165 -0
- benchmark/crm/direct.jsonl +90 -0
- benchmark/crm/indirect.jsonl +150 -0
- benchmark/customer-service/benign.jsonl +160 -0
- benchmark/customer-service/direct.jsonl +100 -0
- benchmark/customer-service/indirect.jsonl +101 -0
- benchmark/finance/benign.jsonl +0 -0
- benchmark/finance/direct.jsonl +200 -0
- benchmark/finance/indirect.jsonl +200 -0
- benchmark/legal/benign.jsonl +0 -0
- benchmark/legal/direct.jsonl +200 -0
- benchmark/legal/indirect.jsonl +200 -0
- benchmark/macos/benign.jsonl +30 -0
- benchmark/macos/direct.jsonl +50 -0
- benchmark/macos/indirect.jsonl +50 -0
- benchmark/medical/benign.jsonl +642 -0
- benchmark/medical/direct.jsonl +229 -0
- benchmark/medical/indirect.jsonl +222 -0
- benchmark/os-filesystem/benign.jsonl +200 -0
- benchmark/os-filesystem/direct.jsonl +200 -0
- benchmark/os-filesystem/indirect.jsonl +200 -0
- benchmark/research/benign.jsonl +0 -0
- benchmark/research/direct.jsonl +119 -0
- benchmark/research/indirect.jsonl +125 -0
- benchmark/telecom/benign.jsonl +120 -0
- benchmark/telecom/direct.jsonl +161 -0
- benchmark/telecom/indirect.jsonl +166 -0
- benchmark/travel/benign.jsonl +130 -0
- benchmark/travel/direct.jsonl +105 -0
- benchmark/travel/indirect.jsonl +120 -0
- benchmark/windows/benign.jsonl +100 -0
- benchmark/windows/direct.jsonl +140 -0
- benchmark/windows/indirect.jsonl +107 -0
- benchmark/workflow/benign.jsonl +335 -0
- benchmark/workflow/direct.jsonl +78 -0
- benchmark/workflow/indirect.jsonl +107 -0
- cli/__init__.py +5 -0
- cli/main.py +182 -0
- cli/scaffold.py +334 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/METADATA +642 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/RECORD +374 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/WHEEL +5 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/licenses/LICENSE +201 -0
- decodingtrust_agent_sdk-0.1.0.dist-info/top_level.txt +6 -0
- dt_arena/config/env.yaml +515 -0
- dt_arena/config/injection_mcp.yaml +430 -0
- dt_arena/config/mcp.yaml +642 -0
- dt_arena/envs/arxiv/docker-compose-hub.yml +31 -0
- dt_arena/envs/arxiv/docker-compose.yml +36 -0
- dt_arena/envs/atlassian/docker/docker-compose.dev.yml +65 -0
- dt_arena/envs/atlassian/docker/docker-compose.yml +53 -0
- dt_arena/envs/atlassian/docker-compose-hub.yml +57 -0
- dt_arena/envs/atlassian/docker-compose.yml +72 -0
- dt_arena/envs/bigquery/docker-compose.yml +20 -0
- dt_arena/envs/booking/docker-compose.yml +59 -0
- dt_arena/envs/calendar/docker-compose-hub.yml +30 -0
- dt_arena/envs/calendar/docker-compose.yml +42 -0
- dt_arena/envs/custom-website/docker-compose.yml +6 -0
- dt_arena/envs/customer_service/docker-compose.yml +59 -0
- dt_arena/envs/databricks/docker-compose-hub.yml +47 -0
- dt_arena/envs/databricks/docker-compose.yml +51 -0
- dt_arena/envs/ecommerce/docker-compose.yml +6 -0
- dt_arena/envs/ers/docker-compose.yml +36 -0
- dt_arena/envs/ers/hrms/docker/docker-compose.yml +31 -0
- dt_arena/envs/finance/docker-compose.yml +23 -0
- dt_arena/envs/github/docker/docker-compose-hub.yml +50 -0
- dt_arena/envs/github/docker/docker-compose.yml +50 -0
- dt_arena/envs/gmail/docker-compose-hub.yml +51 -0
- dt_arena/envs/gmail/docker-compose.yml +65 -0
- dt_arena/envs/google-form/docker-compose-hub.yml +33 -0
- dt_arena/envs/google-form/docker-compose.yml +41 -0
- dt_arena/envs/googledocs/docker-compose-hub.yml +61 -0
- dt_arena/envs/googledocs/docker-compose.yml +78 -0
- dt_arena/envs/hospital/docker-compose-hub.yml +25 -0
- dt_arena/envs/hospital/docker-compose.yml +27 -0
- dt_arena/envs/legal/docker-compose.yml +22 -0
- dt_arena/envs/linkedin/docker-compose.yml +63 -0
- dt_arena/envs/macos/docker-compose.yml +79 -0
- dt_arena/envs/os-filesystem/docker-compose-hub.yml +16 -0
- dt_arena/envs/os-filesystem/docker-compose.yml +20 -0
- dt_arena/envs/paypal/docker-compose-hub.yml +48 -0
- dt_arena/envs/paypal/docker-compose.yml +63 -0
- dt_arena/envs/research/docker-compose-hub.yml +13 -0
- dt_arena/envs/research/docker-compose.yml +24 -0
- dt_arena/envs/salesforce_crm/docker-compose-hub.yaml +45 -0
- dt_arena/envs/salesforce_crm/docker-compose.yaml +49 -0
- dt_arena/envs/slack/docker-compose-hub.yml +28 -0
- dt_arena/envs/slack/docker-compose.yml +41 -0
- dt_arena/envs/snowflake/docker-compose-hub.yml +41 -0
- dt_arena/envs/snowflake/docker-compose.yml +44 -0
- dt_arena/envs/telecom/docker-compose-hub.yml +16 -0
- dt_arena/envs/telecom/docker-compose.yml +17 -0
- dt_arena/envs/telegram/docker-compose-hub.yml +57 -0
- dt_arena/envs/telegram/docker-compose.yml +62 -0
- dt_arena/envs/terminal/docker-compose-hub.yml +12 -0
- dt_arena/envs/terminal/docker-compose.yml +26 -0
- dt_arena/envs/travel/docker-compose-hub.yml +19 -0
- dt_arena/envs/travel/docker-compose.yml +19 -0
- dt_arena/envs/whatsapp/docker-compose-hub.yml +61 -0
- dt_arena/envs/whatsapp/docker-compose.yml +78 -0
- dt_arena/envs/windows/docker-compose.yml +71 -0
- dt_arena/envs/zoom/docker-compose-hub.yml +27 -0
- dt_arena/envs/zoom/docker-compose.yml +40 -0
- dt_arena/injection_mcp_server/atlassian/env_injection.py +134 -0
- dt_arena/injection_mcp_server/calendar/env_injection.py +217 -0
- dt_arena/injection_mcp_server/custom_website/env_injection.py +97 -0
- dt_arena/injection_mcp_server/customer_service/env_injection.py +659 -0
- dt_arena/injection_mcp_server/databricks/env_injection.py +255 -0
- dt_arena/injection_mcp_server/ecommerce/env_injection.py +110 -0
- dt_arena/injection_mcp_server/finance/env_injection.py +85 -0
- dt_arena/injection_mcp_server/github/env_injection.py +206 -0
- dt_arena/injection_mcp_server/gmail/env_injection.py +211 -0
- dt_arena/injection_mcp_server/google_form/env_injection.py +186 -0
- dt_arena/injection_mcp_server/googledocs/env_injection.py +44 -0
- dt_arena/injection_mcp_server/hospital/env_injection.py +43 -0
- dt_arena/injection_mcp_server/legal/env_injection.py +229 -0
- dt_arena/injection_mcp_server/macos/env_injection.py +272 -0
- dt_arena/injection_mcp_server/os-filesystem/env_injection.py +341 -0
- dt_arena/injection_mcp_server/paypal/env_injection.py +268 -0
- dt_arena/injection_mcp_server/research/env_injection.py +616 -0
- dt_arena/injection_mcp_server/salesforce/env_injection.py +514 -0
- dt_arena/injection_mcp_server/slack/env_injection.py +265 -0
- dt_arena/injection_mcp_server/snowflake/env_injection.py +230 -0
- dt_arena/injection_mcp_server/telecom/env_injection.py +503 -0
- dt_arena/injection_mcp_server/telegram/env_injection.py +171 -0
- dt_arena/injection_mcp_server/terminal/env_injection.py +523 -0
- dt_arena/injection_mcp_server/travel/env_injection.py +173 -0
- dt_arena/injection_mcp_server/whatsapp/env_injection.py +185 -0
- dt_arena/injection_mcp_server/windows/env_injection.py +943 -0
- dt_arena/injection_mcp_server/zoom/env_injection.py +216 -0
- dt_arena/mcp_server/atlassian/main.py +1554 -0
- dt_arena/mcp_server/atlassian/test_server.py +66 -0
- dt_arena/mcp_server/bigquery/main.py +333 -0
- dt_arena/mcp_server/booking/main.py +310 -0
- dt_arena/mcp_server/browser/main.py +1741 -0
- dt_arena/mcp_server/calendar/example_multi_user.py +162 -0
- dt_arena/mcp_server/calendar/main.py +792 -0
- dt_arena/mcp_server/calendar/test_mcp.py +135 -0
- dt_arena/mcp_server/customer_service/main.py +1063 -0
- dt_arena/mcp_server/databricks/main.py +566 -0
- dt_arena/mcp_server/databricks/probe.py +102 -0
- dt_arena/mcp_server/ers/main.py +845 -0
- dt_arena/mcp_server/finance/__init__.py +87 -0
- dt_arena/mcp_server/finance/core/__init__.py +12 -0
- dt_arena/mcp_server/finance/core/data_loader.py +558 -0
- dt_arena/mcp_server/finance/core/portfolio.py +565 -0
- dt_arena/mcp_server/finance/evaluation/__init__.py +20 -0
- dt_arena/mcp_server/finance/evaluation/evaluator.py +217 -0
- dt_arena/mcp_server/finance/evaluation/logger.py +137 -0
- dt_arena/mcp_server/finance/injection/__init__.py +66 -0
- dt_arena/mcp_server/finance/injection/config.py +176 -0
- dt_arena/mcp_server/finance/injection/content.py +755 -0
- dt_arena/mcp_server/finance/injection/html.py +409 -0
- dt_arena/mcp_server/finance/injection/locations.py +167 -0
- dt_arena/mcp_server/finance/injection/methods.py +193 -0
- dt_arena/mcp_server/finance/injection/presets.py +1023 -0
- dt_arena/mcp_server/finance/main.py +361 -0
- dt_arena/mcp_server/finance/run_mcp.py +21 -0
- dt_arena/mcp_server/finance/run_web.py +26 -0
- dt_arena/mcp_server/finance/server/__init__.py +41 -0
- dt_arena/mcp_server/finance/server/extractor.py +1453 -0
- dt_arena/mcp_server/finance/server/extractor_minimal.py +292 -0
- dt_arena/mcp_server/finance/server/extractor_simple.py +1164 -0
- dt_arena/mcp_server/finance/server/injection_mcp.py +865 -0
- dt_arena/mcp_server/finance/server/mcp.py +451 -0
- dt_arena/mcp_server/finance/server/tools/__init__.py +23 -0
- dt_arena/mcp_server/finance/server/tools/account.py +88 -0
- dt_arena/mcp_server/finance/server/tools/browsing.py +328 -0
- dt_arena/mcp_server/finance/server/tools/social.py +73 -0
- dt_arena/mcp_server/finance/server/tools/trading.py +242 -0
- dt_arena/mcp_server/finance/server/tools/utility.py +49 -0
- dt_arena/mcp_server/finance/server/web.py +2139 -0
- dt_arena/mcp_server/finance/tasks/benchmark/__init__.py +28 -0
- dt_arena/mcp_server/finance/tasks/benchmark/attack_pool.py +3026 -0
- dt_arena/mcp_server/finance/tasks/benchmark/attack_runner.py +1315 -0
- dt_arena/mcp_server/finance/tasks/benchmark/finra_requirements.py +1335 -0
- dt_arena/mcp_server/finance/tasks/benchmark/finra_tasks.py +3665 -0
- dt_arena/mcp_server/finance/tasks/benchmark/malicious_tasks.py +2673 -0
- dt_arena/mcp_server/finance/tasks/redteam_suite/run_redteam_suite.py +1713 -0
- dt_arena/mcp_server/finance/test_mcp_tools.py +476 -0
- dt_arena/mcp_server/github/main.py +441 -0
- dt_arena/mcp_server/gmail/main.py +1004 -0
- dt_arena/mcp_server/google_form/main.py +141 -0
- dt_arena/mcp_server/googledocs/main.py +458 -0
- dt_arena/mcp_server/hospital/mcp_server.py +458 -0
- dt_arena/mcp_server/legal/__init__.py +9 -0
- dt_arena/mcp_server/legal/core/__init__.py +14 -0
- dt_arena/mcp_server/legal/core/courtlistener_store.py +762 -0
- dt_arena/mcp_server/legal/core/data_loader.py +266 -0
- dt_arena/mcp_server/legal/core/document_store.py +197 -0
- dt_arena/mcp_server/legal/core/matter_manager.py +466 -0
- dt_arena/mcp_server/legal/main.py +89 -0
- dt_arena/mcp_server/legal/scripts/collect_data.py +988 -0
- dt_arena/mcp_server/legal/server/__init__.py +14 -0
- dt_arena/mcp_server/legal/server/mcp.py +2330 -0
- dt_arena/mcp_server/macos/client_test.py +270 -0
- dt_arena/mcp_server/macos/mcp_server.py +285 -0
- dt_arena/mcp_server/os-filesystem/main.py +1380 -0
- dt_arena/mcp_server/paypal/main.py +501 -0
- dt_arena/mcp_server/research/main.py +777 -0
- dt_arena/mcp_server/salesforce/main.py +2006 -0
- dt_arena/mcp_server/slack/main.py +318 -0
- dt_arena/mcp_server/snowflake/main.py +612 -0
- dt_arena/mcp_server/snowflake/probe.py +183 -0
- dt_arena/mcp_server/telecom/mcp_client.py +423 -0
- dt_arena/mcp_server/telecom/mcp_server.py +1059 -0
- dt_arena/mcp_server/telegram/main.py +338 -0
- dt_arena/mcp_server/terminal/main.py +163 -0
- dt_arena/mcp_server/travel/client_test.py +16 -0
- dt_arena/mcp_server/travel/mcp_server.py +404 -0
- dt_arena/mcp_server/whatsapp/main.py +318 -0
- dt_arena/mcp_server/windows/client_test.py +270 -0
- dt_arena/mcp_server/windows/mcp_server.py +218 -0
- dt_arena/mcp_server/zoom/main.py +466 -0
- dt_arena/src/__init__.py +0 -0
- dt_arena/src/hooks/__init__.py +0 -0
- dt_arena/src/hooks/audit_log.py +30 -0
- dt_arena/src/hooks/hooks.json +3 -0
- dt_arena/src/run_benign.py +142 -0
- dt_arena/src/types/__init__.py +0 -0
- dt_arena/src/types/agent.py +441 -0
- dt_arena/src/types/attacks.py +2 -0
- dt_arena/src/types/environment.py +2 -0
- dt_arena/src/types/hooks.py +174 -0
- dt_arena/src/types/judge.py +52 -0
- dt_arena/src/types/red_teaming_trajectory.py +385 -0
- dt_arena/src/types/task.py +260 -0
- dt_arena/src/types/trajectory.py +315 -0
- dt_arena/utils/__init__.py +1 -0
- dt_arena/utils/atlassian/__init__.py +27 -0
- dt_arena/utils/atlassian/helpers.py +520 -0
- dt_arena/utils/bigquery/__init__.py +1 -0
- dt_arena/utils/bigquery/helpers.py +246 -0
- dt_arena/utils/calendar/__init__.py +1 -0
- dt_arena/utils/calendar/helpers.py +87 -0
- dt_arena/utils/customer_service/__init__.py +17 -0
- dt_arena/utils/customer_service/cs_env_client.py +940 -0
- dt_arena/utils/customer_service/helpers.py +339 -0
- dt_arena/utils/customer_service/judges/__init__.py +20 -0
- dt_arena/utils/customer_service/judges/abusive_comms.py +149 -0
- dt_arena/utils/customer_service/judges/bulk_destructive.py +202 -0
- dt_arena/utils/customer_service/judges/cross_customer.py +481 -0
- dt_arena/utils/customer_service/judges/email_exfiltration.py +320 -0
- dt_arena/utils/customer_service/judges/financial_fraud.py +221 -0
- dt_arena/utils/customer_service/judges/llm_utils.py +264 -0
- dt_arena/utils/customer_service/judges/pii_disclosure.py +837 -0
- dt_arena/utils/customer_service/judges/policy_misrep.py +44 -0
- dt_arena/utils/customer_service/judges/text_utils.py +21 -0
- dt_arena/utils/databricks/__init__.py +2 -0
- dt_arena/utils/databricks/helpers.py +210 -0
- dt_arena/utils/finance/__init__.py +0 -0
- dt_arena/utils/finance/helpers.py +263 -0
- dt_arena/utils/github/__init__.py +1 -0
- dt_arena/utils/github/helpers.py +249 -0
- dt_arena/utils/gmail/__init__.py +1 -0
- dt_arena/utils/gmail/helpers.py +344 -0
- dt_arena/utils/google_form/__init__.py +2 -0
- dt_arena/utils/google_form/helpers.py +133 -0
- dt_arena/utils/legal/__init__.py +0 -0
- dt_arena/utils/legal/helpers.py +228 -0
- dt_arena/utils/macos/__init__.py +0 -0
- dt_arena/utils/macos/env_setup.py +215 -0
- dt_arena/utils/macos/helpers.py +61 -0
- dt_arena/utils/os_filesystem/__init__.py +1 -0
- dt_arena/utils/os_filesystem/helpers.py +366 -0
- dt_arena/utils/paypal/__init__.py +1 -0
- dt_arena/utils/paypal/helpers.py +178 -0
- dt_arena/utils/port_allocator.py +266 -0
- dt_arena/utils/research/__init__.py +0 -0
- dt_arena/utils/research/helpers.py +251 -0
- dt_arena/utils/salesforce/__init__.py +1 -0
- dt_arena/utils/salesforce/helpers.py +719 -0
- dt_arena/utils/slack/__init__.py +1 -0
- dt_arena/utils/slack/helpers.py +176 -0
- dt_arena/utils/snowflake/__init__.py +1 -0
- dt_arena/utils/snowflake/helpers.py +166 -0
- dt_arena/utils/telecom/__init__.py +1 -0
- dt_arena/utils/telecom/helpers.py +760 -0
- dt_arena/utils/telegram/__init__.py +0 -0
- dt_arena/utils/telegram/helpers.py +174 -0
- dt_arena/utils/terminal/__init__.py +0 -0
- dt_arena/utils/terminal/helpers.py +20 -0
- dt_arena/utils/travel/__init__.py +0 -0
- dt_arena/utils/travel/env_client.py +537 -0
- dt_arena/utils/travel/llm_judge.py +137 -0
- dt_arena/utils/travel/prompts.py +64 -0
- dt_arena/utils/utils/__init__.py +122 -0
- dt_arena/utils/whatsapp/__init__.py +0 -0
- dt_arena/utils/whatsapp/helpers.py +226 -0
- dt_arena/utils/windows/__init__.py +0 -0
- dt_arena/utils/windows/env_reset.py +224 -0
- dt_arena/utils/windows/env_setup.py +280 -0
- dt_arena/utils/windows/exfil_helpers.py +170 -0
- dt_arena/utils/windows/helpers.py +74 -0
- dt_arena/utils/zoom/__init__.py +1 -0
- dt_arena/utils/zoom/helpers.py +70 -0
- eval/__init__.py +1 -0
- eval/evaluation.py +426 -0
- eval/task_runner.py +449 -0
- utils/__init__.py +148 -0
- utils/agent_helpers.py +308 -0
- utils/agent_wrapper.py +189 -0
- utils/compose_utils.py +135 -0
- utils/config.py +77 -0
- utils/env_helpers.py +104 -0
- utils/eval_stats.py +88 -0
- utils/injection_helpers.py +429 -0
- utils/injection_mcp_helpers.py +152 -0
- utils/judge_helpers.py +181 -0
- utils/judge_utils.py +472 -0
- utils/llm.py +196 -0
- utils/logging.py +45 -0
- utils/mcp_helpers.py +232 -0
- utils/mcp_manager.py +235 -0
- utils/memory_guard.py +18 -0
- utils/red_teaming_sandbox.py +476 -0
- utils/reset_helpers.py +318 -0
- utils/resource_manager.py +370 -0
- utils/skill_helpers.py +447 -0
- utils/task_executor.py +904 -0
- utils/task_helpers.py +270 -0
- utils/template_helpers.py +179 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
import threading
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import List, Dict, Any, Optional
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
|
|
9
|
+
from agents.tracing import TracingProcessor
|
|
10
|
+
|
|
11
|
+
from dt_arena.src.types.trajectory import Trajectory
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_HARMONY_FINAL_RE = re.compile(r"(?:^|.*)assistantfinal", re.DOTALL)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_harmony_final_channel(text: str) -> str:
|
|
18
|
+
"""Strip gpt-oss harmony channel markers and return only the user-facing final channel.
|
|
19
|
+
|
|
20
|
+
gpt-oss responses concatenate analysis (reasoning), commentary (tool calls), and
|
|
21
|
+
final (user reply) channels into a single string. The harmony format renders as
|
|
22
|
+
'assistantfinal' (assistant role + final channel) before the message body. Some
|
|
23
|
+
outputs further include a literal 'final' label, producing 'assistantfinalfinal'.
|
|
24
|
+
|
|
25
|
+
Strategy: locate the last 'assistantfinal', drop it, then drop any immediately
|
|
26
|
+
following 'final' tokens (they are residual channel labels, not content).
|
|
27
|
+
"""
|
|
28
|
+
if not text or not isinstance(text, str):
|
|
29
|
+
return text
|
|
30
|
+
if "assistantfinal" not in text:
|
|
31
|
+
return text
|
|
32
|
+
idx = text.rfind("assistantfinal")
|
|
33
|
+
rest = text[idx + len("assistantfinal"):].lstrip()
|
|
34
|
+
# Drop residual "final" channel-label (e.g. 'assistantfinalfinal**Hi**' → '**Hi**').
|
|
35
|
+
# Only strip when 'final' is immediately followed by a markdown/structural marker
|
|
36
|
+
# (*, #, |, \n) — ensures we never strip legitimate content like "final answer:".
|
|
37
|
+
if rest.startswith("final") and len(rest) > 5 and rest[5] in "*#|\n":
|
|
38
|
+
rest = rest[5:].lstrip()
|
|
39
|
+
return rest
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# OpenAI Local Trace Processor
|
|
44
|
+
class OpenAITraceProcessor(TracingProcessor):
|
|
45
|
+
"""
|
|
46
|
+
Local agent trace processor that captures all span data and agent final responses.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(self, path):
|
|
50
|
+
self.path = path
|
|
51
|
+
self._lock = threading.Lock()
|
|
52
|
+
self._span_start_times: Dict[str, float] = {} # span_id -> monotonic start
|
|
53
|
+
|
|
54
|
+
def _iso(self, ts):
|
|
55
|
+
try:
|
|
56
|
+
return ts.isoformat()
|
|
57
|
+
except:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def _safe_export_span_data(self, sd):
|
|
61
|
+
"""Export span data preserving all fields"""
|
|
62
|
+
if sd is None:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
data = {}
|
|
66
|
+
try:
|
|
67
|
+
if hasattr(sd, "export") and callable(sd.export):
|
|
68
|
+
exported = sd.export()
|
|
69
|
+
if isinstance(exported, dict):
|
|
70
|
+
data = exported
|
|
71
|
+
except:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
if "type" not in data:
|
|
75
|
+
data["type"] = getattr(sd, "type", sd.__class__.__name__)
|
|
76
|
+
|
|
77
|
+
for field in ["input", "name", "output", "mcp_data", "response_id", "tools", "handoffs"]:
|
|
78
|
+
if hasattr(sd, field) and field not in data:
|
|
79
|
+
value = getattr(sd, field, None)
|
|
80
|
+
if value is not None:
|
|
81
|
+
data[field] = value
|
|
82
|
+
|
|
83
|
+
return data
|
|
84
|
+
|
|
85
|
+
def _write(self, obj):
|
|
86
|
+
with self._lock:
|
|
87
|
+
try:
|
|
88
|
+
with open(self.path, "a", encoding="utf-8") as f:
|
|
89
|
+
f.write(json.dumps(obj, ensure_ascii=False, default=str) + "\n")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def write_agent_response(self, trace_id: str, response_text: str):
|
|
94
|
+
"""Record agent's final response (harmony channels stripped to final-only)."""
|
|
95
|
+
cleaned = extract_harmony_final_channel(response_text)
|
|
96
|
+
self._write({
|
|
97
|
+
"record": "response_final",
|
|
98
|
+
"trace_id": trace_id,
|
|
99
|
+
"response_text": cleaned,
|
|
100
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
def on_span_end(self, span):
|
|
104
|
+
import time as _time
|
|
105
|
+
|
|
106
|
+
span_id = getattr(span, "span_id", None)
|
|
107
|
+
duration_s = None
|
|
108
|
+
start_t = self._span_start_times.pop(span_id, None) if span_id else None
|
|
109
|
+
if start_t is not None:
|
|
110
|
+
duration_s = round(_time.monotonic() - start_t, 3)
|
|
111
|
+
|
|
112
|
+
span_data = self._safe_export_span_data(getattr(span, "span_data", None))
|
|
113
|
+
|
|
114
|
+
# Log MCP tool call duration to stderr for visibility
|
|
115
|
+
if span_data and span_data.get("type") == "function" and duration_s is not None:
|
|
116
|
+
tool_name = span_data.get("name", "?")
|
|
117
|
+
mcp_data = span_data.get("mcp_data") or {}
|
|
118
|
+
server = mcp_data.get("server", "?")
|
|
119
|
+
import sys
|
|
120
|
+
print(f"[MCP] {server}/{tool_name} took {duration_s:.3f}s", file=sys.stderr)
|
|
121
|
+
|
|
122
|
+
self._write({
|
|
123
|
+
"record": "span_end",
|
|
124
|
+
"trace_id": getattr(span, "trace_id", None),
|
|
125
|
+
"span_id": getattr(span, "span_id", None),
|
|
126
|
+
"parent_id": getattr(span, "parent_id", None),
|
|
127
|
+
"started_at": self._iso(getattr(span, "started_at", None)),
|
|
128
|
+
"ended_at": self._iso(getattr(span, "ended_at", None)),
|
|
129
|
+
"duration_s": duration_s,
|
|
130
|
+
"span_data": span_data,
|
|
131
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
def on_trace_end(self, trace):
|
|
135
|
+
self._write({
|
|
136
|
+
"record": "trace_end",
|
|
137
|
+
"trace_id": getattr(trace, "trace_id", None),
|
|
138
|
+
"workflow_name": getattr(trace, "workflow_name", None),
|
|
139
|
+
"group_id": getattr(trace, "group_id", None),
|
|
140
|
+
"metadata": getattr(trace, "metadata", {}) or {},
|
|
141
|
+
"started_at": self._iso(getattr(trace, "started_at", None)),
|
|
142
|
+
"ended_at": self._iso(getattr(trace, "ended_at", None)),
|
|
143
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
def on_trace_start(self, trace): pass
|
|
147
|
+
|
|
148
|
+
def on_span_start(self, span):
|
|
149
|
+
import time
|
|
150
|
+
span_id = getattr(span, "span_id", None)
|
|
151
|
+
if span_id:
|
|
152
|
+
self._span_start_times[span_id] = time.monotonic()
|
|
153
|
+
def force_flush(self): pass
|
|
154
|
+
def shutdown(self): pass
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# ============================================================================
|
|
158
|
+
# Standard Trajectory Converter
|
|
159
|
+
# ============================================================================
|
|
160
|
+
|
|
161
|
+
class OpenAIAgentTrajectory:
|
|
162
|
+
"""Convert OpenAI agent traces to standard trajectory format"""
|
|
163
|
+
|
|
164
|
+
def __init__(self, output_dir: str, timestamp: Optional[str] = None):
|
|
165
|
+
self.output_dir = output_dir
|
|
166
|
+
self.timestamp = timestamp # Used for output filename
|
|
167
|
+
os.makedirs(self.output_dir, exist_ok=True)
|
|
168
|
+
|
|
169
|
+
def load_traces(self, trace_file: str):
|
|
170
|
+
if not os.path.exists(trace_file):
|
|
171
|
+
raise FileNotFoundError(f"Trace file not found: {trace_file}")
|
|
172
|
+
|
|
173
|
+
traces_by_id = defaultdict(list)
|
|
174
|
+
trace_metadata = defaultdict(list)
|
|
175
|
+
agent_final_response = defaultdict(list)
|
|
176
|
+
|
|
177
|
+
with open(trace_file, 'r', encoding='utf-8') as f:
|
|
178
|
+
for line in f:
|
|
179
|
+
if not line.strip():
|
|
180
|
+
continue
|
|
181
|
+
data = json.loads(line)
|
|
182
|
+
|
|
183
|
+
if data.get("record") == "trace_end":
|
|
184
|
+
trace_id = data["trace_id"]
|
|
185
|
+
trace_metadata[trace_id].append(data)
|
|
186
|
+
|
|
187
|
+
elif data.get("record") == "span_end":
|
|
188
|
+
trace_id = data["trace_id"]
|
|
189
|
+
traces_by_id[trace_id].append(data)
|
|
190
|
+
|
|
191
|
+
elif data.get("record") == "response_final":
|
|
192
|
+
trace_id = data["trace_id"]
|
|
193
|
+
agent_final_response[trace_id].append(
|
|
194
|
+
extract_harmony_final_channel(data.get("response_text", ""))
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
return traces_by_id, trace_metadata, agent_final_response
|
|
199
|
+
|
|
200
|
+
def _parse_tool_input(self, input_str: str) -> Dict[str, Any]:
|
|
201
|
+
try:
|
|
202
|
+
return json.loads(input_str) if isinstance(input_str, str) else input_str
|
|
203
|
+
except:
|
|
204
|
+
return {"raw": input_str}
|
|
205
|
+
|
|
206
|
+
def _parse_tool_output(self, output_str: str) -> Any:
|
|
207
|
+
try:
|
|
208
|
+
if isinstance(output_str, str):
|
|
209
|
+
parsed = json.loads(output_str)
|
|
210
|
+
if isinstance(parsed, dict) and "type" in parsed and parsed["type"] == "text":
|
|
211
|
+
text_content = parsed.get("text", "")
|
|
212
|
+
try:
|
|
213
|
+
return json.loads(text_content)
|
|
214
|
+
except:
|
|
215
|
+
return text_content
|
|
216
|
+
return parsed
|
|
217
|
+
return output_str
|
|
218
|
+
except:
|
|
219
|
+
return output_str
|
|
220
|
+
|
|
221
|
+
def _extract_conversation_messages(self, response_span: Dict) -> List[Dict[str, Any]]:
|
|
222
|
+
"""
|
|
223
|
+
Extract all conversation messages from response span in order.
|
|
224
|
+
Returns list of {"role": "user"|"assistant", "content": str} dicts.
|
|
225
|
+
"""
|
|
226
|
+
messages = []
|
|
227
|
+
span_data = response_span.get("span_data", {})
|
|
228
|
+
input_data = span_data.get("input")
|
|
229
|
+
|
|
230
|
+
if isinstance(input_data, list):
|
|
231
|
+
for msg in input_data:
|
|
232
|
+
if isinstance(msg, dict):
|
|
233
|
+
role = msg.get("role")
|
|
234
|
+
|
|
235
|
+
if role == "user":
|
|
236
|
+
content = msg.get("content")
|
|
237
|
+
if content:
|
|
238
|
+
messages.append({"role": "user", "content": content})
|
|
239
|
+
|
|
240
|
+
elif role == "assistant":
|
|
241
|
+
# Assistant messages have content as a list of blocks
|
|
242
|
+
content = msg.get("content", [])
|
|
243
|
+
if isinstance(content, list):
|
|
244
|
+
for block in content:
|
|
245
|
+
if isinstance(block, dict):
|
|
246
|
+
# Look for text/output_text blocks
|
|
247
|
+
text = block.get("text") or block.get("output_text")
|
|
248
|
+
if text:
|
|
249
|
+
messages.append({"role": "assistant", "content": text})
|
|
250
|
+
elif isinstance(content, str) and content:
|
|
251
|
+
messages.append({"role": "assistant", "content": content})
|
|
252
|
+
|
|
253
|
+
return messages
|
|
254
|
+
|
|
255
|
+
def _extract_agent_final_response_from_spans(self, spans: List[Dict]) -> Optional[str]:
|
|
256
|
+
"""Fallback: extract agent's final response from spans"""
|
|
257
|
+
sorted_spans = sorted(spans, key=lambda x: x.get("ts", ""), reverse=True)
|
|
258
|
+
|
|
259
|
+
for span in sorted_spans:
|
|
260
|
+
span_data = span.get("span_data", {})
|
|
261
|
+
if span_data.get("type") == "response":
|
|
262
|
+
output = span_data.get("output")
|
|
263
|
+
if output:
|
|
264
|
+
if isinstance(output, dict):
|
|
265
|
+
if "content" in output:
|
|
266
|
+
return output["content"]
|
|
267
|
+
elif "text" in output:
|
|
268
|
+
return output["text"]
|
|
269
|
+
elif isinstance(output, str):
|
|
270
|
+
return output
|
|
271
|
+
|
|
272
|
+
input_data = span_data.get("input", [])
|
|
273
|
+
if isinstance(input_data, list):
|
|
274
|
+
for msg in reversed(input_data):
|
|
275
|
+
if isinstance(msg, dict) and msg.get("role") in ("assistant", "agent"):
|
|
276
|
+
content = msg.get("content")
|
|
277
|
+
if content:
|
|
278
|
+
return content
|
|
279
|
+
|
|
280
|
+
return None
|
|
281
|
+
|
|
282
|
+
def _build_trajectory_steps(
|
|
283
|
+
self,
|
|
284
|
+
traj: Trajectory,
|
|
285
|
+
spans: List[Dict],
|
|
286
|
+
agent_response: Optional[str] = None
|
|
287
|
+
):
|
|
288
|
+
"""Build trajectory steps from spans using Trajectory class helper methods"""
|
|
289
|
+
sorted_spans = sorted(spans, key=lambda x: x.get("ts", ""))
|
|
290
|
+
|
|
291
|
+
seen_user_messages = set()
|
|
292
|
+
seen_assistant_messages = set()
|
|
293
|
+
|
|
294
|
+
for span in sorted_spans:
|
|
295
|
+
span_data = span.get("span_data", {})
|
|
296
|
+
span_type = span_data.get("type")
|
|
297
|
+
|
|
298
|
+
# List MCP Tools action (native tool)
|
|
299
|
+
if span_type == "mcp_tools":
|
|
300
|
+
tools_list = span_data.get("result", [])
|
|
301
|
+
server_name = span_data.get("server", "unknown")
|
|
302
|
+
|
|
303
|
+
# Agent action
|
|
304
|
+
traj.append_agent_step(
|
|
305
|
+
action=f"List MCP Tools ({server_name})",
|
|
306
|
+
tool_name="List MCP Tools",
|
|
307
|
+
tool_params={},
|
|
308
|
+
server=server_name
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Tool return
|
|
312
|
+
traj.append_tool_return(
|
|
313
|
+
result=tools_list,
|
|
314
|
+
tool_name="List MCP Tools",
|
|
315
|
+
server=server_name
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if span_type == "response":
|
|
319
|
+
# Extract conversation messages in order (user and assistant interleaved)
|
|
320
|
+
conv_messages = self._extract_conversation_messages(span)
|
|
321
|
+
for msg in conv_messages:
|
|
322
|
+
if msg["role"] == "user":
|
|
323
|
+
if msg["content"] not in seen_user_messages:
|
|
324
|
+
traj.append_user_step(msg["content"])
|
|
325
|
+
seen_user_messages.add(msg["content"])
|
|
326
|
+
elif msg["role"] == "assistant":
|
|
327
|
+
if msg["content"] not in seen_assistant_messages:
|
|
328
|
+
traj.append_agent_step(
|
|
329
|
+
action="send_message_to_user",
|
|
330
|
+
metadata={"message": msg["content"]}
|
|
331
|
+
)
|
|
332
|
+
seen_assistant_messages.add(msg["content"])
|
|
333
|
+
# Set the latest response in trajectory
|
|
334
|
+
traj.set_final_response(msg["content"])
|
|
335
|
+
|
|
336
|
+
elif span_type == "function":
|
|
337
|
+
tool_name = span_data.get("name")
|
|
338
|
+
tool_input = span_data.get("input", "{}")
|
|
339
|
+
tool_output = span_data.get("output", "")
|
|
340
|
+
mcp_data = span_data.get("mcp_data", {})
|
|
341
|
+
|
|
342
|
+
parsed_input = self._parse_tool_input(tool_input)
|
|
343
|
+
parsed_output = self._parse_tool_output(tool_output)
|
|
344
|
+
|
|
345
|
+
# Get server name from mcp_data (authoritative source for which server handled the call)
|
|
346
|
+
server_name = mcp_data.get("server", "unknown") if mcp_data else "unknown"
|
|
347
|
+
|
|
348
|
+
# Compute tool call duration from span timestamps
|
|
349
|
+
duration_s = span.get("duration_s")
|
|
350
|
+
|
|
351
|
+
# Build action string
|
|
352
|
+
action_str = f"{tool_name}("
|
|
353
|
+
params = []
|
|
354
|
+
for k, v in parsed_input.items():
|
|
355
|
+
if isinstance(v, str):
|
|
356
|
+
params.append(f'{k}="{v}"')
|
|
357
|
+
else:
|
|
358
|
+
params.append(f'{k}={v}')
|
|
359
|
+
action_str += ", ".join(params) + ")"
|
|
360
|
+
|
|
361
|
+
# Add agent action
|
|
362
|
+
traj.append_agent_step(
|
|
363
|
+
action=action_str,
|
|
364
|
+
tool_name=tool_name,
|
|
365
|
+
tool_params=parsed_input,
|
|
366
|
+
server=server_name
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Add tool return with duration metadata
|
|
370
|
+
tool_meta = {}
|
|
371
|
+
if duration_s is not None:
|
|
372
|
+
tool_meta["duration_s"] = duration_s
|
|
373
|
+
traj.append_tool_return(
|
|
374
|
+
result=parsed_output,
|
|
375
|
+
tool_name=tool_name,
|
|
376
|
+
server=server_name,
|
|
377
|
+
metadata=tool_meta if tool_meta else None,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Add agent final response (only if not already seen)
|
|
381
|
+
final_response_text = agent_response or self._extract_agent_final_response_from_spans(spans)
|
|
382
|
+
|
|
383
|
+
if final_response_text and final_response_text not in seen_assistant_messages:
|
|
384
|
+
traj.append_agent_step(
|
|
385
|
+
action="send_message_to_user",
|
|
386
|
+
metadata={"message": final_response_text}
|
|
387
|
+
)
|
|
388
|
+
# Set final response
|
|
389
|
+
traj.set_final_response(final_response_text)
|
|
390
|
+
|
|
391
|
+
def convert_trace_to_trajectory(
|
|
392
|
+
self,
|
|
393
|
+
trace_id: str,
|
|
394
|
+
spans: List[Dict],
|
|
395
|
+
metadata: Dict,
|
|
396
|
+
task_id: Optional[str] = None,
|
|
397
|
+
agent_response: Optional[str] = None
|
|
398
|
+
) -> Trajectory:
|
|
399
|
+
"""Convert a single trace to Trajectory object"""
|
|
400
|
+
trace_metadata = metadata.get("metadata", {})
|
|
401
|
+
instruction = trace_metadata.get("instruction", "No instruction provided")
|
|
402
|
+
malicious_instruction = trace_metadata.get("malicious_goal", None)
|
|
403
|
+
|
|
404
|
+
# Create Trajectory instance
|
|
405
|
+
traj = Trajectory(
|
|
406
|
+
task_id=task_id or trace_metadata.get("task_id", trace_id[:16]),
|
|
407
|
+
original_instruction=instruction,
|
|
408
|
+
malicious_instruction=malicious_instruction,
|
|
409
|
+
domain=trace_metadata.get("domain"),
|
|
410
|
+
risk_category=trace_metadata.get("category")
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
# Calculate duration from spans
|
|
414
|
+
if spans:
|
|
415
|
+
timestamps = [s.get("ts") for s in spans if s.get("ts")]
|
|
416
|
+
if len(timestamps) >= 2:
|
|
417
|
+
try:
|
|
418
|
+
start = datetime.fromisoformat(timestamps[0].replace('Z', '+00:00'))
|
|
419
|
+
end = datetime.fromisoformat(timestamps[-1].replace('Z', '+00:00'))
|
|
420
|
+
duration = (end - start).total_seconds()
|
|
421
|
+
except:
|
|
422
|
+
duration = 0.0
|
|
423
|
+
else:
|
|
424
|
+
duration = 0.0
|
|
425
|
+
else:
|
|
426
|
+
duration = 0.0
|
|
427
|
+
|
|
428
|
+
# Set duration and timestamp
|
|
429
|
+
traj.data["traj_info"]["duration"] = round(duration, 3)
|
|
430
|
+
|
|
431
|
+
# Priority: trace ended_at > trace started_at > last span ts > now
|
|
432
|
+
trace_timestamp = metadata.get("ended_at") or metadata.get("started_at")
|
|
433
|
+
if trace_timestamp:
|
|
434
|
+
traj.data["traj_info"]["timestamp"] = trace_timestamp.isoformat() if hasattr(trace_timestamp, 'isoformat') else str(trace_timestamp)
|
|
435
|
+
elif timestamps:
|
|
436
|
+
traj.data["traj_info"]["timestamp"] = timestamps[-1]
|
|
437
|
+
|
|
438
|
+
traj.data["traj_info"]["metadata"] = {
|
|
439
|
+
"trace_id": trace_id
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
# Build trajectory steps using helper methods
|
|
443
|
+
self._build_trajectory_steps(traj, spans, agent_response)
|
|
444
|
+
|
|
445
|
+
# Include debug fields if present in trace metadata
|
|
446
|
+
debug_data = {}
|
|
447
|
+
tool_descriptions = trace_metadata.get("tool_descriptions")
|
|
448
|
+
if tool_descriptions:
|
|
449
|
+
debug_data["tool_descriptions"] = tool_descriptions
|
|
450
|
+
system_prompt = trace_metadata.get("system_prompt")
|
|
451
|
+
if system_prompt:
|
|
452
|
+
debug_data["system_prompt"] = system_prompt
|
|
453
|
+
if debug_data:
|
|
454
|
+
traj.data["debug"] = debug_data
|
|
455
|
+
|
|
456
|
+
return traj
|
|
457
|
+
|
|
458
|
+
def process_trace_file(self, trace_file: str, output_name: Optional[str] = None) -> List[Trajectory]:
|
|
459
|
+
"""Process trace file and generate trajectory files
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
List of Trajectory objects (also saved to files)
|
|
463
|
+
"""
|
|
464
|
+
traces_by_id, trace_metadata, agent_final_response = self.load_traces(trace_file)
|
|
465
|
+
|
|
466
|
+
trajectories = []
|
|
467
|
+
|
|
468
|
+
for i, (trace_id, spans) in enumerate(traces_by_id.items()):
|
|
469
|
+
metadata = trace_metadata.get(trace_id)[-1] if trace_metadata.get(trace_id) else {}
|
|
470
|
+
task_id = metadata.get("metadata", {}).get("task_id")
|
|
471
|
+
agent_response = agent_final_response.get(trace_id)[-1] if agent_final_response.get(trace_id) else None
|
|
472
|
+
|
|
473
|
+
traj = self.convert_trace_to_trajectory(
|
|
474
|
+
trace_id, spans, metadata, task_id, agent_response
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# Use timestamp for filename: {timestamp}.json or {timestamp}_{index}.json for multiple traces
|
|
478
|
+
if self.timestamp:
|
|
479
|
+
if len(traces_by_id) == 1:
|
|
480
|
+
filename = f"{self.timestamp}.json"
|
|
481
|
+
else:
|
|
482
|
+
filename = f"{self.timestamp}_{i}.json"
|
|
483
|
+
else:
|
|
484
|
+
filename = f"{task_id}_{trace_id}.json"
|
|
485
|
+
output_path = os.path.join(self.output_dir, filename)
|
|
486
|
+
|
|
487
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
488
|
+
json.dump(traj.to_dict(), f, indent=2, ensure_ascii=False)
|
|
489
|
+
|
|
490
|
+
trajectories.append(traj)
|
|
491
|
+
|
|
492
|
+
return trajectories
|
|
493
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .src.agent import OpenClawAgent
|
|
2
|
+
from .src.mcp_proxy import MCPProxyServer, MCPProxyManager
|
|
3
|
+
from .src.utils import OpenClawTrajectoryConverter
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"OpenClawAgent",
|
|
7
|
+
"MCPProxyServer",
|
|
8
|
+
"MCPProxyManager",
|
|
9
|
+
"OpenClawTrajectoryConverter",
|
|
10
|
+
]
|