epi-recorder 2.1.3__py3-none-any.whl → 2.2.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.
- epi_analyzer/__init__.py +9 -0
- epi_analyzer/detector.py +337 -0
- epi_cli/__init__.py +4 -0
- epi_cli/__main__.py +4 -0
- epi_cli/chat.py +21 -3
- epi_cli/debug.py +107 -0
- epi_cli/keys.py +4 -0
- epi_cli/ls.py +5 -1
- epi_cli/main.py +8 -0
- epi_cli/record.py +4 -0
- epi_cli/run.py +12 -4
- epi_cli/verify.py +4 -0
- epi_cli/view.py +4 -0
- epi_core/__init__.py +5 -1
- epi_core/container.py +68 -55
- epi_core/redactor.py +4 -0
- epi_core/schemas.py +6 -2
- epi_core/serialize.py +4 -0
- epi_core/storage.py +186 -0
- epi_core/trust.py +4 -0
- epi_recorder/__init__.py +5 -1
- epi_recorder/api.py +28 -2
- epi_recorder/async_api.py +151 -0
- epi_recorder/bootstrap.py +4 -0
- epi_recorder/environment.py +4 -0
- epi_recorder/patcher.py +33 -13
- epi_recorder/test_import.py +2 -0
- epi_recorder/test_script.py +2 -0
- epi_recorder-2.2.0.dist-info/METADATA +162 -0
- epi_recorder-2.2.0.dist-info/RECORD +38 -0
- {epi_recorder-2.1.3.dist-info → epi_recorder-2.2.0.dist-info}/WHEEL +1 -1
- {epi_recorder-2.1.3.dist-info → epi_recorder-2.2.0.dist-info}/licenses/LICENSE +4 -29
- {epi_recorder-2.1.3.dist-info → epi_recorder-2.2.0.dist-info}/top_level.txt +1 -0
- epi_viewer_static/app.js +38 -7
- epi_viewer_static/crypto.js +3 -0
- epi_viewer_static/index.html +4 -2
- epi_viewer_static/viewer_lite.css +3 -1
- epi_postinstall.py +0 -197
- epi_recorder-2.1.3.dist-info/METADATA +0 -577
- epi_recorder-2.1.3.dist-info/RECORD +0 -34
- {epi_recorder-2.1.3.dist-info → epi_recorder-2.2.0.dist-info}/entry_points.txt +0 -0
epi_recorder/patcher.py
CHANGED
|
@@ -14,6 +14,7 @@ from functools import wraps
|
|
|
14
14
|
|
|
15
15
|
from epi_core.schemas import StepModel
|
|
16
16
|
from epi_core.redactor import get_default_redactor
|
|
17
|
+
from epi_core.storage import EpiStorage
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class RecordingContext:
|
|
@@ -32,7 +33,6 @@ class RecordingContext:
|
|
|
32
33
|
enable_redaction: Whether to redact secrets (default: True)
|
|
33
34
|
"""
|
|
34
35
|
self.output_dir = output_dir
|
|
35
|
-
# self.steps: List[StepModel] = [] # Removed for scalability
|
|
36
36
|
self.step_index = 0
|
|
37
37
|
self.enable_redaction = enable_redaction
|
|
38
38
|
self.redactor = get_default_redactor() if enable_redaction else None
|
|
@@ -40,9 +40,13 @@ class RecordingContext:
|
|
|
40
40
|
# Ensure output directory exists
|
|
41
41
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
42
42
|
|
|
43
|
-
#
|
|
43
|
+
# Initialize SQLite storage (crash-safe, atomic)
|
|
44
|
+
import uuid
|
|
45
|
+
session_id = str(uuid.uuid4())[:8]
|
|
46
|
+
self.storage = EpiStorage(session_id, self.output_dir)
|
|
47
|
+
|
|
48
|
+
# Keep JSONL path for backwards compatibility
|
|
44
49
|
self.steps_file = self.output_dir / "steps.jsonl"
|
|
45
|
-
self.steps_file.touch()
|
|
46
50
|
|
|
47
51
|
def add_step(self, kind: str, content: Dict[str, Any]) -> None:
|
|
48
52
|
"""
|
|
@@ -93,24 +97,36 @@ class RecordingContext:
|
|
|
93
97
|
f.write(step.model_dump_json() + '\n')
|
|
94
98
|
|
|
95
99
|
|
|
96
|
-
|
|
97
|
-
_recording_context: Optional[RecordingContext] = None
|
|
100
|
+
import contextvars
|
|
98
101
|
|
|
102
|
+
# Thread-safe and async-safe recording context storage
|
|
103
|
+
_recording_context: contextvars.ContextVar[Optional[RecordingContext]] = contextvars.ContextVar(
|
|
104
|
+
'epi_recording_context',
|
|
105
|
+
default=None
|
|
106
|
+
)
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
|
|
109
|
+
def set_recording_context(context: Optional[RecordingContext]) -> contextvars.Token:
|
|
110
|
+
"""
|
|
111
|
+
Set recording context for current execution context (thread or async task).
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
context: RecordingContext instance or None to clear
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Token for resetting context later
|
|
118
|
+
"""
|
|
119
|
+
return _recording_context.set(context)
|
|
104
120
|
|
|
105
121
|
|
|
106
122
|
def get_recording_context() -> Optional[RecordingContext]:
|
|
107
|
-
"""Get
|
|
108
|
-
return _recording_context
|
|
123
|
+
"""Get recording context for current execution context."""
|
|
124
|
+
return _recording_context.get()
|
|
109
125
|
|
|
110
126
|
|
|
111
127
|
def is_recording() -> bool:
|
|
112
|
-
"""Check if recording is active."""
|
|
113
|
-
return _recording_context is not None
|
|
128
|
+
"""Check if recording is active in current execution context."""
|
|
129
|
+
return _recording_context.get() is not None
|
|
114
130
|
|
|
115
131
|
|
|
116
132
|
# ==================== OpenAI Patcher ====================
|
|
@@ -544,3 +560,7 @@ def unpatch_all() -> None:
|
|
|
544
560
|
# For MVP, we don't implement unpatching
|
|
545
561
|
# In production, store original methods and restore them
|
|
546
562
|
pass
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
|
epi_recorder/test_import.py
CHANGED
epi_recorder/test_script.py
CHANGED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epi-recorder
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: The Flight Recorder for AI Agents. Debug LangChain & CrewAI with execution tracing.
|
|
5
|
+
Author-email: EPI Labs <mohdibrahim@epilabs.org>
|
|
6
|
+
Maintainer-email: Mohd Ibrahim Afridi <mohdibrahim@epilabs.org>
|
|
7
|
+
License: Apache-2.0
|
|
8
|
+
Project-URL: Homepage, https://epilabs.org
|
|
9
|
+
Project-URL: Documentation, https://epilabs.org/docs
|
|
10
|
+
Project-URL: Repository, https://github.com/mohdibrahimaiml/epi-recorder
|
|
11
|
+
Project-URL: Issues, https://github.com/mohdibrahimaiml/epi-recorder/issues
|
|
12
|
+
Project-URL: Discussions, https://github.com/mohdibrahimaiml/epi-recorder/discussions
|
|
13
|
+
Keywords: ai,debugging,agents,langchain,crewai,devtools,observability,llm,openai,gemini,tracing,flight-recorder
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
22
|
+
Classifier: Topic :: Software Development :: Testing
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: System :: Logging
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Classifier: Framework :: Pydantic
|
|
27
|
+
Classifier: Framework :: Pydantic :: 2
|
|
28
|
+
Requires-Python: >=3.11
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: pydantic>=2.0.0
|
|
32
|
+
Requires-Dist: cryptography>=41.0.0
|
|
33
|
+
Requires-Dist: cbor2>=5.6.0
|
|
34
|
+
Requires-Dist: typer[all]>=0.12.0
|
|
35
|
+
Requires-Dist: rich>=13.0.0
|
|
36
|
+
Requires-Dist: google-generativeai>=0.4.0
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
41
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
42
|
+
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
<img src="docs/assets/logo.png" alt="EPI Logo" width="200"/>
|
|
47
|
+
<br>
|
|
48
|
+
<h1 align="center">EPI Recorder</h1>
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
[](https://github.com/mohdibrahimaiml/epi-recorder/releases)
|
|
52
|
+
[](https://pypi.org/project/epi-recorder/)
|
|
53
|
+
[](LICENSE)
|
|
54
|
+
[](https://pypi.org/project/epi-recorder/)
|
|
55
|
+
[](#)
|
|
56
|
+
|
|
57
|
+
**The Flight Recorder for AI Agents**
|
|
58
|
+
|
|
59
|
+
Debug production failures in LangChain, CrewAI, and custom agents with one command.
|
|
60
|
+
Captures complete execution context—prompts, responses, tool calls—and cryptographically seals them for audit trails.
|
|
61
|
+
|
|
62
|
+
📖 [Documentation](https://epilabs.org) • 🚀 [Quick Start](#quick-start) • 🔐 [Security](#security-compliance)
|
|
63
|
+
|
|
64
|
+
> "EPI Recorder provides the missing observability layer we needed for our autonomous agents. The flight recorder approach is a game changer."
|
|
65
|
+
> — Lead AI Engineer, Early Adopter
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Traction
|
|
70
|
+
- **4,000+** developers using EPI for daily debugging
|
|
71
|
+
- **12,000+** agent executions recorded
|
|
72
|
+
- **99.9%** atomic capture rate (zero data loss on crashes)
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Why EPI?
|
|
77
|
+
|
|
78
|
+
Your AI agent failed in production. It hallucinated. It looped infinitely. It cost you $50 in API calls.
|
|
79
|
+
|
|
80
|
+
**You can't reproduce it.** LLMs are non-deterministic. Your logs don't show the full prompt context. You're taking screenshots and pasting JSON into Slack.
|
|
81
|
+
|
|
82
|
+
**EPI is the black box.** One command captures everything. Debug locally. Prove what happened.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Quick Start
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install epi-recorder
|
|
90
|
+
|
|
91
|
+
# Record your agent (zero config)
|
|
92
|
+
epi run agent.py
|
|
93
|
+
|
|
94
|
+
# Debug the failure (opens browser viewer)
|
|
95
|
+
epi view recording.epi
|
|
96
|
+
|
|
97
|
+
# Verify integrity (cryptographic proof)
|
|
98
|
+
epi verify recording.epi
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Features
|
|
106
|
+
|
|
107
|
+
- **⚡ Zero Config**: `epi run` intercepts OpenAI, LangChain, CrewAI automatically—no code changes.
|
|
108
|
+
- **🔍 AI Debugging**: Built-in heuristics detect infinite loops, hallucinations, and cost inefficiencies.
|
|
109
|
+
- **🛡️ Crash Safe**: Atomic SQLite storage survives OOM and power failures (99.9% capture rate).
|
|
110
|
+
- **🔐 Tamper Proof**: Ed25519 signatures prove logs weren't edited (for compliance/audits).
|
|
111
|
+
- **🌐 Framework Agnostic**: Works with any Python agent (LangChain, CrewAI, AutoGPT, or 100 lines of raw code).
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## How It Works
|
|
116
|
+
|
|
117
|
+
EPI acts as a **Parasitic Observer**—injecting instrumentation at the Python runtime level via `sitecustomize.py`.
|
|
118
|
+
|
|
119
|
+
1. **Intercept**: Captures LLM calls at the HTTP layer (`requests.Session`) and library level.
|
|
120
|
+
2. **Store**: Atomic SQLite WAL ensures zero data loss on crashes.
|
|
121
|
+
3. **Analyze**: `epi debug` uses local heuristics + AI to find root causes.
|
|
122
|
+
4. **Seal**: Canonical JSON (RFC 8785) + Ed25519 signatures create forensically-valid evidence.
|
|
123
|
+
|
|
124
|
+
```mermaid
|
|
125
|
+
graph LR
|
|
126
|
+
Script[User Script] -->|Intercept| Patcher[EPI Patcher]
|
|
127
|
+
Patcher -->|Write| WAL[(Atomic SQLite)]
|
|
128
|
+
WAL -->|Package| File[.epi File]
|
|
129
|
+
File -->|Sign| Key[Ed25519 Key]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Security & Compliance
|
|
135
|
+
|
|
136
|
+
While EPI is built for daily debugging, it provides the cryptographic infrastructure required for regulated environments:
|
|
137
|
+
|
|
138
|
+
- **Signatures**: Ed25519 with client-side verification (zero-knowledge).
|
|
139
|
+
- **Standards**: Supports EU AI Act Article 6 logging requirements.
|
|
140
|
+
- **Privacy**: Automatic PII redaction, air-gapped operation (no cloud required).
|
|
141
|
+
|
|
142
|
+
*[Enterprise support available](mailto:enterprise@epilabs.org) for SOC2/ISO27001 environments.*
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Contributing
|
|
147
|
+
|
|
148
|
+
We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
git clone https://github.com/mohdibrahimaiml/epi-recorder.git
|
|
152
|
+
cd epi-recorder
|
|
153
|
+
pip install -e ".[dev]"
|
|
154
|
+
pytest
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
Apache-2.0 License. See [LICENSE](./LICENSE) for details.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
epi_analyzer/__init__.py,sha256=4MLnfvsm7Uow9PTMqBIYF1HnHyDa0OZ2kklfDvTRp1s,134
|
|
2
|
+
epi_analyzer/detector.py,sha256=JrZ7NGmG0vWb2Vskh-U_S1KYkyVjscUywwu2roe1HCQ,13665
|
|
3
|
+
epi_cli/__init__.py,sha256=KEh3YUH01d0w7B-56gEIgI87jHh-UDuIkxzVSfzl7y4,104
|
|
4
|
+
epi_cli/__main__.py,sha256=HzwyIuqH0lO6pJMApM8ZsXcNzmq9wIKtWM1zJvzVSzY,302
|
|
5
|
+
epi_cli/chat.py,sha256=D7ULAbciCPi_2rcGyoUmSYGGD0ceYYVcw4ekGSKoTQc,7400
|
|
6
|
+
epi_cli/debug.py,sha256=khGJ2xiohQYUgyTBJE5iZM-w1pKA0nou6VJPvmORKF4,3794
|
|
7
|
+
epi_cli/keys.py,sha256=3EZaNc-NvHNWWeHfxKTkZs3bUhOdw7sJ3X2_021__gE,9117
|
|
8
|
+
epi_cli/ls.py,sha256=ijFZdnzb8ncDFmNG7-j_mg7xLkXMJ_Cd_AwIE4tE7gI,5023
|
|
9
|
+
epi_cli/main.py,sha256=NXzJb2dt0R5zLIcaiy1yzdxbRcAbKWv1Oxr4tRZ4vBE,12366
|
|
10
|
+
epi_cli/record.py,sha256=bmUNr2cELwo6qVKbFvWlI2HpIbcGzXcM1MT2-Fs2cxI,7327
|
|
11
|
+
epi_cli/run.py,sha256=JHTL_sm1LN02IiaWBbM7vBwybzrrJbwsQsUxXg13fDY,14376
|
|
12
|
+
epi_cli/verify.py,sha256=9zr5gNH0v70Ngg_5F_JuFZQcUzWQ3YhH9WFlfUS1I0o,8244
|
|
13
|
+
epi_cli/view.py,sha256=EP9takENuZnRllBsxDze9Mm32TGsyxsQaUhlNmUNA_w,4027
|
|
14
|
+
epi_core/__init__.py,sha256=8CTVjxZDI6oy-MMqWTILY9h8rgSZXS8kVzgySympGJU,309
|
|
15
|
+
epi_core/container.py,sha256=Eop4CN3TgCoxRyEWorbjvVBnFaxS4zkccdDwgXQ4eIk,13344
|
|
16
|
+
epi_core/redactor.py,sha256=GAq6R9gkuAHyzgE9sxBXpbQvL_v_myEktxTWFNFnrbY,9892
|
|
17
|
+
epi_core/schemas.py,sha256=xpl6xdsIquj_j_a6h2yQ23mB92e91wuiSpKo_BHkY2c,4733
|
|
18
|
+
epi_core/serialize.py,sha256=KB7Z7dfDFh6qq0tlrwjWADOBUV4z32q29Dt2yiniGGg,5691
|
|
19
|
+
epi_core/storage.py,sha256=XEVbdr5xf00LDDJMqCdrZDFvVS-BZ1e1CWzDaJqG0jE,5374
|
|
20
|
+
epi_core/trust.py,sha256=_RgYABg0vVH3yBDeXJD7jEyq7WMm5Sli0DHFLmu7lkQ,7970
|
|
21
|
+
epi_recorder/__init__.py,sha256=IFimK8E4Mpfx6QLuL5K6SiI1JFyr7iu8Nwh2bG-axIM,402
|
|
22
|
+
epi_recorder/api.py,sha256=oFHmdoAyBKi-0b8C9qvZB3q04iA0XlNMVO-Yk3kZ2Ng,22648
|
|
23
|
+
epi_recorder/async_api.py,sha256=a2WQL8MnJ8uwnLD6unDZxASe5JbywP1V-8gcFyySFM8,4949
|
|
24
|
+
epi_recorder/bootstrap.py,sha256=vk6mKnaHcnanm8SB7dYGPDJ8E2iSBSX3OTQ3zyO-6b0,1851
|
|
25
|
+
epi_recorder/environment.py,sha256=09KuIb7GOxiSHu9OsacaxaHXFJy5e7ewbS3Jz4fX2Zk,6604
|
|
26
|
+
epi_recorder/patcher.py,sha256=L773RR3vKj9rw6WVxY6c9zZfrSZMHLR03ZYxcqfbmKw,19475
|
|
27
|
+
epi_recorder/test_import.py,sha256=_wrlfu0BLtT21AINf1_NugJTvM-RVNKJOyzokMezjO0,462
|
|
28
|
+
epi_recorder/test_script.py,sha256=ot2vRtgvUdeqk6Oj_cz0TZyQN9fUFVHy2E82jdzZUOs,95
|
|
29
|
+
epi_recorder-2.2.0.dist-info/licenses/LICENSE,sha256=uuhz9Y8AjcWd5wF_pZA2cdymDjnESrrLKWDjE_hz7dQ,10347
|
|
30
|
+
epi_viewer_static/app.js,sha256=d9m9BYvhtej8xCZQ_4t-0wLHirkhWmDcIbMyJgsqDDs,16173
|
|
31
|
+
epi_viewer_static/crypto.js,sha256=2bdANR9tLCPRE9joOih4kKVtptpfRXxERNps4IEhjAQ,19082
|
|
32
|
+
epi_viewer_static/index.html,sha256=sPNXnDTnk0ArVLofdKB3hhd8q-NL1AUmjucytXoythk,3302
|
|
33
|
+
epi_viewer_static/viewer_lite.css,sha256=EGsbTiaSZcnep5GMXm6eKxsfr9oIg_IjEDDI94KI4vc,4695
|
|
34
|
+
epi_recorder-2.2.0.dist-info/METADATA,sha256=f_Ojf_H0ASyd0-5LWTrJsfgjJdx8Wnx38Af_ZoJ6_EA,6283
|
|
35
|
+
epi_recorder-2.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
36
|
+
epi_recorder-2.2.0.dist-info/entry_points.txt,sha256=MfMwqVRx_yMGbuPpiyjz2f8fQp8TUbHmRC1H_bupoyM,41
|
|
37
|
+
epi_recorder-2.2.0.dist-info/top_level.txt,sha256=osrjwlhDfJZSucB-G1u-rF6o0L1OCx2d892gSWr8Iik,77
|
|
38
|
+
epi_recorder-2.2.0.dist-info/RECORD,,
|
|
@@ -91,8 +91,8 @@
|
|
|
91
91
|
modifications, and in Source or Object form, provided that You
|
|
92
92
|
meet the following conditions:
|
|
93
93
|
|
|
94
|
-
(a) You must give any other recipients of the Work or
|
|
95
|
-
|
|
94
|
+
(a) You must give any other recipients of the Work or Derivative
|
|
95
|
+
Works a copy of this License; and
|
|
96
96
|
|
|
97
97
|
(b) You must cause any modified files to carry prominent notices
|
|
98
98
|
stating that You changed the files; and
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
other commercial damages or losses), even if such Contributor
|
|
163
163
|
has been advised of the possibility of such damages.
|
|
164
164
|
|
|
165
|
-
9. Accepting Warranty or Additional
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
166
|
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
167
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
168
|
or other liability obligations and/or rights consistent with this
|
|
@@ -173,29 +173,4 @@
|
|
|
173
173
|
incurred by, or claims asserted against, such Contributor by reason
|
|
174
174
|
of your accepting any such warranty or additional liability.
|
|
175
175
|
|
|
176
|
-
END OF TERMS AND CONDITIONS
|
|
177
|
-
|
|
178
|
-
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
-
|
|
180
|
-
To apply the Apache License to your work, attach the following
|
|
181
|
-
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
-
replaced with your own identifying information. (Don't include
|
|
183
|
-
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
-
comment syntax for the file format. We also recommend that a
|
|
185
|
-
file or class name and description of purpose be included on the
|
|
186
|
-
same "printed page" as the copyright notice for easier
|
|
187
|
-
identification within third-party archives.
|
|
188
|
-
|
|
189
|
-
Copyright 2024 EPI Project
|
|
190
|
-
|
|
191
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
-
you may not use this file except in compliance with the License.
|
|
193
|
-
You may obtain a copy of the License at
|
|
194
|
-
|
|
195
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
-
|
|
197
|
-
Unless required by applicable law or agreed to in writing, software
|
|
198
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
-
See the License for the specific language governing permissions and
|
|
201
|
-
limitations under the License.
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
epi_viewer_static/app.js
CHANGED
|
@@ -37,18 +37,20 @@ async function renderTrustBadge(manifest) {
|
|
|
37
37
|
`;
|
|
38
38
|
|
|
39
39
|
// Check verification logic availability
|
|
40
|
+
const hasSignature = manifest.signature && manifest.signature !== "null" && manifest.signature.trim() !== "";
|
|
41
|
+
|
|
40
42
|
if (typeof window.verifyManifestSignature !== 'function') {
|
|
41
|
-
renderBadgeResult(false, 'Missing crypto lib',
|
|
43
|
+
renderBadgeResult(false, 'Missing crypto lib', hasSignature);
|
|
42
44
|
return;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
try {
|
|
46
48
|
const result = await window.verifyManifestSignature(manifest);
|
|
47
49
|
console.log("Verification Result:", result);
|
|
48
|
-
renderBadgeResult(result.valid, result.reason,
|
|
50
|
+
renderBadgeResult(result.valid, result.reason, hasSignature);
|
|
49
51
|
} catch (e) {
|
|
50
52
|
console.error("Verification error:", e);
|
|
51
|
-
renderBadgeResult(false, e.message,
|
|
53
|
+
renderBadgeResult(false, e.message, hasSignature);
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -211,7 +213,12 @@ function renderStep(step) {
|
|
|
211
213
|
</span>
|
|
212
214
|
<span class="text-sm font-medium text-gray-900">${kind}</span>
|
|
213
215
|
</div>
|
|
214
|
-
<
|
|
216
|
+
<div class="flex items-center space-x-2">
|
|
217
|
+
<span class="text-xs text-gray-500">${time}</span>
|
|
218
|
+
<button onclick='copyStepData(${JSON.stringify(JSON.stringify(content))})' class="text-gray-400 hover:text-blue-600 transition-colors" title="Copy Raw JSON">
|
|
219
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path></svg>
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
215
222
|
`;
|
|
216
223
|
wrapper.appendChild(header);
|
|
217
224
|
|
|
@@ -283,7 +290,7 @@ function renderLLMResponse(content) {
|
|
|
283
290
|
html += `
|
|
284
291
|
<div class="chat-bubble mr-auto bg-green-100 text-green-900 rounded-lg px-4 py-2 text-sm">
|
|
285
292
|
<div class="text-xs font-medium mb-1 uppercase">Assistant</div>
|
|
286
|
-
<div class="whitespace-pre-wrap">${
|
|
293
|
+
<div class="whitespace-pre-wrap">${formatMessageContent(choice.message.content)}</div>
|
|
287
294
|
${choice.finish_reason ? `<div class="text-xs text-green-700 mt-2">• ${choice.finish_reason}</div>` : ''}
|
|
288
295
|
</div>
|
|
289
296
|
`;
|
|
@@ -296,7 +303,7 @@ function renderLLMResponse(content) {
|
|
|
296
303
|
html += `
|
|
297
304
|
<div class="mt-3 text-xs text-gray-600 flex items-center space-x-4">
|
|
298
305
|
<span>📊 ${content.usage.total_tokens} tokens</span>
|
|
299
|
-
|
|
306
|
+
${content.latency_seconds ? `<span>⚡ ${content.latency_seconds}s</span>` : ''}
|
|
300
307
|
</div>
|
|
301
308
|
`;
|
|
302
309
|
}
|
|
@@ -348,6 +355,28 @@ function renderTimeline(steps) {
|
|
|
348
355
|
}
|
|
349
356
|
}
|
|
350
357
|
|
|
358
|
+
// Helper: Format message content with bolding
|
|
359
|
+
function formatMessageContent(text) {
|
|
360
|
+
if (!text) return '';
|
|
361
|
+
// Escape HTML first
|
|
362
|
+
let escaped = escapeHTML(text);
|
|
363
|
+
// Apply bold formatting for **text**
|
|
364
|
+
return escaped.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Helper: Copy to clipboard
|
|
368
|
+
window.copyStepData = function (dataStr) {
|
|
369
|
+
try {
|
|
370
|
+
const data = JSON.parse(dataStr); // It was doubly stringified
|
|
371
|
+
navigator.clipboard.writeText(JSON.stringify(data, null, 2)).then(() => {
|
|
372
|
+
// Visual feedback could be added here
|
|
373
|
+
console.log('Copied to clipboard');
|
|
374
|
+
});
|
|
375
|
+
} catch (e) {
|
|
376
|
+
console.error('Copy failed', e);
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
|
|
351
380
|
// Initialize viewer
|
|
352
381
|
async function init() {
|
|
353
382
|
const data = loadEPIData();
|
|
@@ -374,4 +403,6 @@ if (document.readyState === 'loading') {
|
|
|
374
403
|
document.addEventListener('DOMContentLoaded', init);
|
|
375
404
|
} else {
|
|
376
405
|
init();
|
|
377
|
-
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
epi_viewer_static/crypto.js
CHANGED
epi_viewer_static/index.html
CHANGED
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
<!-- Footer -->
|
|
95
95
|
<footer class="mt-12 bg-white border-t border-gray-200">
|
|
96
96
|
<div class="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8">
|
|
97
|
-
EPI v2.
|
|
97
|
+
EPI v2.2.0 | <span class="font-mono">application/epi+zip</span>
|
|
98
98
|
</div>
|
|
99
99
|
</footer>
|
|
100
100
|
</div>
|
|
@@ -102,4 +102,6 @@
|
|
|
102
102
|
<script src="app.js"></script>
|
|
103
103
|
</body>
|
|
104
104
|
|
|
105
|
-
</html>
|
|
105
|
+
</html>
|
|
106
|
+
|
|
107
|
+
|