zeno-cli 0.3.4__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.
- zeno_adapters/__init__.py +17 -0
- zeno_adapters/_common.py +38 -0
- zeno_adapters/anthropic.py +68 -0
- zeno_adapters/claude_code.py +101 -0
- zeno_adapters/crewai.py +92 -0
- zeno_adapters/langgraph.py +49 -0
- zeno_adapters/openai.py +108 -0
- zeno_cli/__init__.py +1 -0
- zeno_cli/_hooks/cc_bridge.py +1016 -0
- zeno_cli/doctor.py +535 -0
- zeno_cli/hook_install.py +269 -0
- zeno_cli/hud/__init__.py +1 -0
- zeno_cli/hud/hud_install.py +652 -0
- zeno_cli/hud/zeno_attention.py +288 -0
- zeno_cli/hud/zeno_cognition.py +457 -0
- zeno_cli/hud/zeno_hud.py +496 -0
- zeno_cli/interview_invites.py +342 -0
- zeno_cli/login.py +241 -0
- zeno_cli/main.py +2534 -0
- zeno_cli/onboard.py +206 -0
- zeno_cli/outreach.py +456 -0
- zeno_cli/version.py +67 -0
- zeno_cli-0.3.4.dist-info/METADATA +161 -0
- zeno_cli-0.3.4.dist-info/RECORD +69 -0
- zeno_cli-0.3.4.dist-info/WHEEL +4 -0
- zeno_cli-0.3.4.dist-info/entry_points.txt +4 -0
- zeno_core/__init__.py +67 -0
- zeno_core/analytics.py +193 -0
- zeno_core/rtlx_s.py +460 -0
- zeno_core/streak.py +178 -0
- zeno_core/tlx_s.py +192 -0
- zeno_sdk/__init__.py +6 -0
- zeno_sdk/_generated/__init__.py +6 -0
- zeno_sdk/_generated/client.py +819 -0
- zeno_sdk/_migrations/alembic/env.py +33 -0
- zeno_sdk/_migrations/alembic/script.py.mako +18 -0
- zeno_sdk/_migrations/alembic/versions/0001_initial.py +79 -0
- zeno_sdk/_migrations/alembic/versions/0002_cognition_samples.py +53 -0
- zeno_sdk/_migrations/alembic/versions/0003_cognition_drivers.py +41 -0
- zeno_sdk/_migrations/alembic/versions/0004_transcript_intelligence.py +248 -0
- zeno_sdk/_migrations/alembic.ini +35 -0
- zeno_sdk/_runtime.py +12 -0
- zeno_sdk/adapters/__init__.py +15 -0
- zeno_sdk/adapters/anthropic.py +5 -0
- zeno_sdk/adapters/claude_code.py +5 -0
- zeno_sdk/adapters/crewai.py +5 -0
- zeno_sdk/adapters/langgraph.py +5 -0
- zeno_sdk/adapters/openai.py +5 -0
- zeno_sdk/auth.py +25 -0
- zeno_sdk/client.py +87 -0
- zeno_sdk/config.py +61 -0
- zeno_sdk/daemon.py +72 -0
- zeno_sdk/privacy.py +46 -0
- zeno_sdk/session.py +179 -0
- zeno_sdk/storage.py +487 -0
- zeno_sdk/types/__init__.py +121 -0
- zeno_session_intel/__init__.py +19 -0
- zeno_session_intel/analytics.py +588 -0
- zeno_session_intel/compression.py +123 -0
- zeno_session_intel/ingest.py +376 -0
- zeno_session_intel/model.py +129 -0
- zeno_session_intel/parsers/__init__.py +31 -0
- zeno_session_intel/parsers/claude_code.py +169 -0
- zeno_session_intel/parsers/codex.py +265 -0
- zeno_session_intel/parsers/cursor.py +198 -0
- zeno_session_intel/prices.py +281 -0
- zeno_session_intel/schema.py +277 -0
- zeno_session_intel/signals.py +319 -0
- zeno_session_intel/taxonomy.py +71 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from alembic import context
|
|
4
|
+
from sqlalchemy import engine_from_config, pool
|
|
5
|
+
|
|
6
|
+
config = context.config
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run_migrations_offline() -> None:
|
|
10
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
11
|
+
context.configure(url=url, literal_binds=True, compare_type=True)
|
|
12
|
+
|
|
13
|
+
with context.begin_transaction():
|
|
14
|
+
context.run_migrations()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_migrations_online() -> None:
|
|
18
|
+
connectable = engine_from_config(
|
|
19
|
+
config.get_section(config.config_ini_section) or {},
|
|
20
|
+
prefix="sqlalchemy.",
|
|
21
|
+
poolclass=pool.NullPool,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
with connectable.connect() as connection:
|
|
25
|
+
context.configure(connection=connection, compare_type=True)
|
|
26
|
+
with context.begin_transaction():
|
|
27
|
+
context.run_migrations()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if context.is_offline_mode():
|
|
31
|
+
run_migrations_offline()
|
|
32
|
+
else:
|
|
33
|
+
run_migrations_online()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
"""
|
|
7
|
+
from alembic import op
|
|
8
|
+
import sqlalchemy as sa
|
|
9
|
+
|
|
10
|
+
${imports if imports else ""}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def upgrade() -> None:
|
|
14
|
+
${upgrades if upgrades else "pass"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def downgrade() -> None:
|
|
18
|
+
${downgrades if downgrades else "pass"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""initial schema
|
|
2
|
+
|
|
3
|
+
Revision ID: 0001_initial
|
|
4
|
+
Revises:
|
|
5
|
+
Create Date: 2026-04-30
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sqlalchemy as sa
|
|
11
|
+
from alembic import op
|
|
12
|
+
|
|
13
|
+
# revision identifiers, used by Alembic.
|
|
14
|
+
revision = "0001_initial"
|
|
15
|
+
down_revision = None
|
|
16
|
+
branch_labels = None
|
|
17
|
+
depends_on = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
op.create_table(
|
|
22
|
+
"sessions",
|
|
23
|
+
sa.Column("id", sa.String(), primary_key=True),
|
|
24
|
+
sa.Column("start_at", sa.String(), nullable=False),
|
|
25
|
+
sa.Column("end_at", sa.String(), nullable=True),
|
|
26
|
+
sa.Column("agent_count_max", sa.Integer(), nullable=False),
|
|
27
|
+
sa.Column("harness", sa.String(), nullable=False),
|
|
28
|
+
sa.Column("project_id", sa.String(), nullable=False),
|
|
29
|
+
sa.Column("synced_at", sa.String(), nullable=True),
|
|
30
|
+
)
|
|
31
|
+
op.create_table(
|
|
32
|
+
"agent_runs",
|
|
33
|
+
sa.Column("id", sa.String(), primary_key=True),
|
|
34
|
+
sa.Column("session_id", sa.String(), nullable=False, index=True),
|
|
35
|
+
sa.Column("harness", sa.String(), nullable=False),
|
|
36
|
+
sa.Column("model", sa.String(), nullable=False),
|
|
37
|
+
sa.Column("started_at", sa.String(), nullable=False),
|
|
38
|
+
sa.Column("ended_at", sa.String(), nullable=True),
|
|
39
|
+
sa.Column("outcome", sa.String(), nullable=False),
|
|
40
|
+
sa.Column("synced_at", sa.String(), nullable=True),
|
|
41
|
+
)
|
|
42
|
+
op.create_table(
|
|
43
|
+
"supervision_events",
|
|
44
|
+
sa.Column("id", sa.String(), primary_key=True),
|
|
45
|
+
sa.Column("session_id", sa.String(), nullable=False, index=True),
|
|
46
|
+
sa.Column("agent_run_id", sa.String(), nullable=True, index=True),
|
|
47
|
+
sa.Column("type", sa.String(), nullable=False),
|
|
48
|
+
sa.Column("timestamp", sa.String(), nullable=False),
|
|
49
|
+
sa.Column("latency_ms_to_decide", sa.Integer(), nullable=True),
|
|
50
|
+
sa.Column("metadata_json", sa.Text(), nullable=False, server_default="{}"),
|
|
51
|
+
sa.Column("synced_at", sa.String(), nullable=True),
|
|
52
|
+
)
|
|
53
|
+
op.create_table(
|
|
54
|
+
"load_probes",
|
|
55
|
+
sa.Column("id", sa.String(), primary_key=True),
|
|
56
|
+
sa.Column("session_id", sa.String(), nullable=False, index=True),
|
|
57
|
+
sa.Column("prompted_at", sa.String(), nullable=False),
|
|
58
|
+
sa.Column("responded_at", sa.String(), nullable=True),
|
|
59
|
+
sa.Column("skipped", sa.Integer(), nullable=False),
|
|
60
|
+
sa.Column("subscales_json", sa.Text(), nullable=True),
|
|
61
|
+
sa.Column("synced_at", sa.String(), nullable=True),
|
|
62
|
+
)
|
|
63
|
+
op.create_table(
|
|
64
|
+
"babysitting_tax_points",
|
|
65
|
+
sa.Column("id", sa.String(), primary_key=True),
|
|
66
|
+
sa.Column("session_id", sa.String(), nullable=False, index=True),
|
|
67
|
+
sa.Column("n_agents_active", sa.Integer(), nullable=False),
|
|
68
|
+
sa.Column("composite_load", sa.Float(), nullable=False),
|
|
69
|
+
sa.Column("output_quality", sa.Float(), nullable=True),
|
|
70
|
+
sa.Column("synced_at", sa.String(), nullable=True),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def downgrade() -> None:
|
|
75
|
+
op.drop_table("babysitting_tax_points")
|
|
76
|
+
op.drop_table("load_probes")
|
|
77
|
+
op.drop_table("supervision_events")
|
|
78
|
+
op.drop_table("agent_runs")
|
|
79
|
+
op.drop_table("sessions")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""cognition_samples: per-turn cognition stream (tokens + context + attention)
|
|
2
|
+
|
|
3
|
+
Revision ID: 0002_cognition_samples
|
|
4
|
+
Revises: 0001_initial
|
|
5
|
+
Create Date: 2026-06-16
|
|
6
|
+
|
|
7
|
+
One row per assistant turn, written by the HUD statusline (the read+write
|
|
8
|
+
surface) so the in-terminal HUD and the tailnet dashboard share the exact
|
|
9
|
+
same attention + token signal. See docs/COGNITION_UNIFIED_PLAN.md.
|
|
10
|
+
|
|
11
|
+
Kept deliberately schema-compatible with the self-create block in
|
|
12
|
+
integrations/zeno-hud/zeno_hud.py and dotfiles/claude/hooks/zeno-cc-bridge.py:
|
|
13
|
+
if you add a column here, mirror it in those stdlib writers (they never
|
|
14
|
+
import this package, to keep hook/statusline startup sub-100ms).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import sqlalchemy as sa
|
|
20
|
+
from alembic import op
|
|
21
|
+
|
|
22
|
+
# revision identifiers, used by Alembic.
|
|
23
|
+
revision = "0002_cognition_samples"
|
|
24
|
+
down_revision = "0001_initial"
|
|
25
|
+
branch_labels = None
|
|
26
|
+
depends_on = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def upgrade() -> None:
|
|
30
|
+
op.create_table(
|
|
31
|
+
"cognition_samples",
|
|
32
|
+
sa.Column("id", sa.String(), primary_key=True),
|
|
33
|
+
sa.Column("session_id", sa.String(), nullable=False, index=True),
|
|
34
|
+
sa.Column("ts", sa.String(), nullable=False),
|
|
35
|
+
# tokens + context captured from the statusline stdin
|
|
36
|
+
sa.Column("context_pct", sa.Float(), nullable=True),
|
|
37
|
+
sa.Column("input_tokens", sa.Integer(), nullable=True),
|
|
38
|
+
sa.Column("output_tokens", sa.Integer(), nullable=True),
|
|
39
|
+
sa.Column("cache_read_tokens", sa.Integer(), nullable=True),
|
|
40
|
+
sa.Column("cache_creation_tokens", sa.Integer(), nullable=True),
|
|
41
|
+
sa.Column("total_tokens", sa.Integer(), nullable=True),
|
|
42
|
+
# attention signal (score 0..100 + its components) from the shared model
|
|
43
|
+
sa.Column("attention_score", sa.Integer(), nullable=True),
|
|
44
|
+
sa.Column("attention_effort", sa.Float(), nullable=True),
|
|
45
|
+
sa.Column("attention_deliberation", sa.Float(), nullable=True),
|
|
46
|
+
sa.Column("attention_trend", sa.Float(), nullable=True),
|
|
47
|
+
sa.Column("model", sa.String(), nullable=True),
|
|
48
|
+
sa.Column("harness", sa.String(), nullable=True),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def downgrade() -> None:
|
|
53
|
+
op.drop_table("cognition_samples")
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""cognition_samples v2: multi-dimensional driver columns
|
|
2
|
+
|
|
3
|
+
Revision ID: 0003_cognition_drivers
|
|
4
|
+
Revises: 0002_cognition_samples
|
|
5
|
+
Create Date: 2026-06-16
|
|
6
|
+
|
|
7
|
+
HUD v2 turns the single attention score into a composite of five named drivers.
|
|
8
|
+
This adds the four new driver columns (attention_effort already exists from 0002).
|
|
9
|
+
|
|
10
|
+
Kept in lockstep with the canonical schema in
|
|
11
|
+
integrations/zeno-hud/zeno_cognition.py (SAMPLE_COLUMNS) and the stdlib writers
|
|
12
|
+
(zeno_hud.py + dotfiles zeno-cc-bridge.py call zeno_cognition.ensure_schema, which
|
|
13
|
+
ADDs these columns on existing DBs). The schema-drift test guards the two lists.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import sqlalchemy as sa
|
|
19
|
+
from alembic import op
|
|
20
|
+
|
|
21
|
+
revision = "0003_cognition_drivers"
|
|
22
|
+
down_revision = "0002_cognition_samples"
|
|
23
|
+
branch_labels = None
|
|
24
|
+
depends_on = None
|
|
25
|
+
|
|
26
|
+
_DRIVER_COLUMNS = (
|
|
27
|
+
"attention_autonomy",
|
|
28
|
+
"attention_verification",
|
|
29
|
+
"attention_fatigue",
|
|
30
|
+
"attention_flow",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def upgrade() -> None:
|
|
35
|
+
for col in _DRIVER_COLUMNS:
|
|
36
|
+
op.add_column("cognition_samples", sa.Column(col, sa.Float(), nullable=True))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def downgrade() -> None:
|
|
40
|
+
for col in reversed(_DRIVER_COLUMNS):
|
|
41
|
+
op.drop_column("cognition_samples", col)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""transcript intelligence: multi-tool session capture + cache-aware token/cost ledger
|
|
2
|
+
|
|
3
|
+
Revision ID: 0004_transcript_intelligence
|
|
4
|
+
Revises: 0003_cognition_drivers
|
|
5
|
+
Create Date: 2026-06-17
|
|
6
|
+
|
|
7
|
+
Additive session-intelligence tables, adopted from agentsview (MIT, (c) 2026 Kenn
|
|
8
|
+
Software LLC; see THIRD_PARTY_LICENSES.md). These are a derived cache of coding-agent
|
|
9
|
+
transcripts (Claude Code, Codex, Cursor) scoring *agent* behavior - token/cost, tool
|
|
10
|
+
health, outcomes - ALONGSIDE zeno's primary, untouched human-cognition signal in
|
|
11
|
+
``cognition_samples``. agentsview's ``sessions``/``messages`` collide with zeno's
|
|
12
|
+
existing tables, so they are namespaced ``transcript_*``.
|
|
13
|
+
|
|
14
|
+
Kept in lockstep with the canonical stdlib schema in
|
|
15
|
+
``packages/session-intel/src/zeno_session_intel/schema.py`` (the ingester writes via
|
|
16
|
+
stdlib sqlite3 and calls ``ensure_schema``; this migration is the SDK-managed-DB path).
|
|
17
|
+
The schema-drift test (``tests/test_session_intel_schema.py``) guards the two lists.
|
|
18
|
+
|
|
19
|
+
FTS5 is created via raw SQL (alembic does not model virtual tables) and is guarded:
|
|
20
|
+
on a SQLite build without the fts5 module it is skipped, search degrades, ingest still
|
|
21
|
+
works. ``transcript_messages.id`` is a synthetic INTEGER rowid - it is the FTS5
|
|
22
|
+
``content_rowid``; do not make it a composite PK.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import sqlalchemy as sa
|
|
28
|
+
from alembic import op
|
|
29
|
+
|
|
30
|
+
revision = "0004_transcript_intelligence"
|
|
31
|
+
down_revision = "0003_cognition_drivers"
|
|
32
|
+
branch_labels = None
|
|
33
|
+
depends_on = None
|
|
34
|
+
|
|
35
|
+
_NOW = sa.text("(strftime('%Y-%m-%dT%H:%M:%fZ','now'))")
|
|
36
|
+
|
|
37
|
+
_FTS_SQL = """
|
|
38
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS transcript_messages_fts USING fts5(
|
|
39
|
+
content,
|
|
40
|
+
content='transcript_messages',
|
|
41
|
+
content_rowid='id',
|
|
42
|
+
tokenize='porter unicode61'
|
|
43
|
+
);
|
|
44
|
+
CREATE TRIGGER IF NOT EXISTS transcript_messages_ai
|
|
45
|
+
AFTER INSERT ON transcript_messages BEGIN
|
|
46
|
+
INSERT INTO transcript_messages_fts(rowid, content) VALUES (new.id, new.content);
|
|
47
|
+
END;
|
|
48
|
+
CREATE TRIGGER IF NOT EXISTS transcript_messages_ad
|
|
49
|
+
AFTER DELETE ON transcript_messages BEGIN
|
|
50
|
+
INSERT INTO transcript_messages_fts(transcript_messages_fts, rowid, content)
|
|
51
|
+
VALUES ('delete', old.id, old.content);
|
|
52
|
+
END;
|
|
53
|
+
CREATE TRIGGER IF NOT EXISTS transcript_messages_au
|
|
54
|
+
AFTER UPDATE ON transcript_messages BEGIN
|
|
55
|
+
INSERT INTO transcript_messages_fts(transcript_messages_fts, rowid, content)
|
|
56
|
+
VALUES ('delete', old.id, old.content);
|
|
57
|
+
INSERT INTO transcript_messages_fts(rowid, content) VALUES (new.id, new.content);
|
|
58
|
+
END;
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def upgrade() -> None:
|
|
63
|
+
op.create_table(
|
|
64
|
+
"transcript_sessions",
|
|
65
|
+
sa.Column("id", sa.String(), primary_key=True),
|
|
66
|
+
sa.Column("zeno_session_id", sa.String(), nullable=True),
|
|
67
|
+
sa.Column("agent", sa.String(), nullable=False, server_default="claude"),
|
|
68
|
+
sa.Column("project", sa.String(), nullable=False, server_default=""),
|
|
69
|
+
sa.Column("machine", sa.String(), nullable=False, server_default="local"),
|
|
70
|
+
sa.Column("first_message", sa.Text(), nullable=True),
|
|
71
|
+
sa.Column("display_name", sa.String(), nullable=True),
|
|
72
|
+
sa.Column("started_at", sa.String(), nullable=True),
|
|
73
|
+
sa.Column("ended_at", sa.String(), nullable=True),
|
|
74
|
+
sa.Column("message_count", sa.Integer(), nullable=False, server_default="0"),
|
|
75
|
+
sa.Column("user_message_count", sa.Integer(), nullable=False, server_default="0"),
|
|
76
|
+
sa.Column("total_output_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
77
|
+
sa.Column("peak_context_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
78
|
+
sa.Column("is_automated", sa.Integer(), nullable=False, server_default="0"),
|
|
79
|
+
sa.Column("tool_failure_signal_count", sa.Integer(), nullable=False, server_default="0"),
|
|
80
|
+
sa.Column("tool_retry_count", sa.Integer(), nullable=False, server_default="0"),
|
|
81
|
+
sa.Column("edit_churn_count", sa.Integer(), nullable=False, server_default="0"),
|
|
82
|
+
sa.Column("consecutive_failure_max", sa.Integer(), nullable=False, server_default="0"),
|
|
83
|
+
sa.Column("outcome", sa.String(), nullable=False, server_default="unknown"),
|
|
84
|
+
sa.Column("outcome_confidence", sa.String(), nullable=False, server_default="low"),
|
|
85
|
+
sa.Column("ended_with_role", sa.String(), nullable=False, server_default=""),
|
|
86
|
+
sa.Column("final_failure_streak", sa.Integer(), nullable=False, server_default="0"),
|
|
87
|
+
sa.Column("compaction_count", sa.Integer(), nullable=False, server_default="0"),
|
|
88
|
+
sa.Column("mid_task_compaction_count", sa.Integer(), nullable=False, server_default="0"),
|
|
89
|
+
sa.Column("context_pressure_max", sa.Float(), nullable=True),
|
|
90
|
+
sa.Column("health_score", sa.Integer(), nullable=True),
|
|
91
|
+
sa.Column("health_grade", sa.String(), nullable=True),
|
|
92
|
+
sa.Column("has_tool_calls", sa.Integer(), nullable=False, server_default="0"),
|
|
93
|
+
sa.Column("has_context_data", sa.Integer(), nullable=False, server_default="0"),
|
|
94
|
+
sa.Column("cwd", sa.String(), nullable=False, server_default=""),
|
|
95
|
+
sa.Column("git_branch", sa.String(), nullable=False, server_default=""),
|
|
96
|
+
sa.Column("file_path", sa.String(), nullable=True),
|
|
97
|
+
sa.Column("file_mtime", sa.Integer(), nullable=True),
|
|
98
|
+
sa.Column("content_hash", sa.String(), nullable=True),
|
|
99
|
+
sa.Column("created_at", sa.String(), nullable=False, server_default=_NOW),
|
|
100
|
+
)
|
|
101
|
+
op.create_index(
|
|
102
|
+
"idx_transcript_sessions_ended",
|
|
103
|
+
"transcript_sessions",
|
|
104
|
+
[sa.text("ended_at DESC"), "id"],
|
|
105
|
+
)
|
|
106
|
+
op.create_index("idx_transcript_sessions_agent", "transcript_sessions", ["agent"])
|
|
107
|
+
op.create_index("idx_transcript_sessions_project", "transcript_sessions", ["project"])
|
|
108
|
+
op.create_index("idx_transcript_sessions_zeno", "transcript_sessions", ["zeno_session_id"])
|
|
109
|
+
|
|
110
|
+
op.create_table(
|
|
111
|
+
"transcript_messages",
|
|
112
|
+
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
|
113
|
+
sa.Column(
|
|
114
|
+
"session_id",
|
|
115
|
+
sa.String(),
|
|
116
|
+
sa.ForeignKey("transcript_sessions.id", ondelete="CASCADE"),
|
|
117
|
+
nullable=False,
|
|
118
|
+
),
|
|
119
|
+
sa.Column("ordinal", sa.Integer(), nullable=False),
|
|
120
|
+
sa.Column("role", sa.String(), nullable=False),
|
|
121
|
+
sa.Column("content", sa.Text(), nullable=False),
|
|
122
|
+
sa.Column("thinking_text", sa.Text(), nullable=False, server_default=""),
|
|
123
|
+
sa.Column("timestamp", sa.String(), nullable=True),
|
|
124
|
+
sa.Column("has_thinking", sa.Integer(), nullable=False, server_default="0"),
|
|
125
|
+
sa.Column("has_tool_use", sa.Integer(), nullable=False, server_default="0"),
|
|
126
|
+
sa.Column("content_length", sa.Integer(), nullable=False, server_default="0"),
|
|
127
|
+
sa.Column("model", sa.String(), nullable=False, server_default=""),
|
|
128
|
+
sa.Column("token_usage", sa.Text(), nullable=False, server_default=""),
|
|
129
|
+
sa.Column("context_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
130
|
+
sa.Column("output_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
131
|
+
sa.Column("source_uuid", sa.String(), nullable=False, server_default=""),
|
|
132
|
+
sa.Column("source_parent_uuid", sa.String(), nullable=False, server_default=""),
|
|
133
|
+
sa.Column("is_sidechain", sa.Integer(), nullable=False, server_default="0"),
|
|
134
|
+
sa.Column("is_compact_boundary", sa.Integer(), nullable=False, server_default="0"),
|
|
135
|
+
)
|
|
136
|
+
op.create_index(
|
|
137
|
+
"idx_transcript_messages_session_ordinal",
|
|
138
|
+
"transcript_messages",
|
|
139
|
+
["session_id", "ordinal"],
|
|
140
|
+
unique=True,
|
|
141
|
+
)
|
|
142
|
+
op.create_index(
|
|
143
|
+
"idx_transcript_messages_session_role",
|
|
144
|
+
"transcript_messages",
|
|
145
|
+
["session_id", "role"],
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
op.create_table(
|
|
149
|
+
"transcript_tool_calls",
|
|
150
|
+
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
|
151
|
+
sa.Column(
|
|
152
|
+
"session_id",
|
|
153
|
+
sa.String(),
|
|
154
|
+
sa.ForeignKey("transcript_sessions.id", ondelete="CASCADE"),
|
|
155
|
+
nullable=False,
|
|
156
|
+
),
|
|
157
|
+
sa.Column("message_ordinal", sa.Integer(), nullable=False, server_default="0"),
|
|
158
|
+
sa.Column("ordinal_in_message", sa.Integer(), nullable=False, server_default="0"),
|
|
159
|
+
sa.Column("tool_name", sa.String(), nullable=False, server_default=""),
|
|
160
|
+
sa.Column("category", sa.String(), nullable=False, server_default="Other"),
|
|
161
|
+
sa.Column("is_error", sa.Integer(), nullable=False, server_default="0"),
|
|
162
|
+
sa.Column("source", sa.String(), nullable=False, server_default=""),
|
|
163
|
+
)
|
|
164
|
+
op.create_index("idx_transcript_tool_calls_session", "transcript_tool_calls", ["session_id"])
|
|
165
|
+
op.create_index("idx_transcript_tool_calls_category", "transcript_tool_calls", ["category"])
|
|
166
|
+
|
|
167
|
+
op.create_table(
|
|
168
|
+
"token_usage_events",
|
|
169
|
+
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
|
170
|
+
sa.Column(
|
|
171
|
+
"session_id",
|
|
172
|
+
sa.String(),
|
|
173
|
+
sa.ForeignKey("transcript_sessions.id", ondelete="CASCADE"),
|
|
174
|
+
nullable=False,
|
|
175
|
+
),
|
|
176
|
+
sa.Column("message_ordinal", sa.Integer(), nullable=True),
|
|
177
|
+
sa.Column("source", sa.String(), nullable=False),
|
|
178
|
+
sa.Column("model", sa.String(), nullable=False),
|
|
179
|
+
sa.Column("input_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
180
|
+
sa.Column("output_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
181
|
+
sa.Column("cache_creation_input_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
182
|
+
sa.Column("cache_read_input_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
183
|
+
sa.Column("reasoning_tokens", sa.Integer(), nullable=False, server_default="0"),
|
|
184
|
+
sa.Column("cost_usd", sa.Float(), nullable=True),
|
|
185
|
+
sa.Column("cost_status", sa.String(), nullable=False, server_default=""),
|
|
186
|
+
sa.Column("cost_source", sa.String(), nullable=False, server_default=""),
|
|
187
|
+
sa.Column("occurred_at", sa.String(), nullable=True),
|
|
188
|
+
sa.Column("dedup_key", sa.String(), nullable=False, server_default=""),
|
|
189
|
+
)
|
|
190
|
+
op.create_index(
|
|
191
|
+
"idx_token_usage_events_dedup",
|
|
192
|
+
"token_usage_events",
|
|
193
|
+
["session_id", "source", "dedup_key"],
|
|
194
|
+
unique=True,
|
|
195
|
+
# the partial predicate must survive on BOTH dialects, else Postgres builds a
|
|
196
|
+
# FULL unique index and id-less (empty dedup_key) session events collide there.
|
|
197
|
+
sqlite_where=sa.text("dedup_key != ''"),
|
|
198
|
+
postgresql_where=sa.text("dedup_key != ''"),
|
|
199
|
+
)
|
|
200
|
+
op.create_index("idx_token_usage_events_session", "token_usage_events", ["session_id"])
|
|
201
|
+
op.create_index("idx_token_usage_events_occurred", "token_usage_events", ["occurred_at"])
|
|
202
|
+
|
|
203
|
+
op.create_table(
|
|
204
|
+
"model_pricing",
|
|
205
|
+
sa.Column("model_pattern", sa.String(), primary_key=True),
|
|
206
|
+
sa.Column("input_per_mtok", sa.Float(), nullable=False, server_default="0"),
|
|
207
|
+
sa.Column("output_per_mtok", sa.Float(), nullable=False, server_default="0"),
|
|
208
|
+
sa.Column("cache_creation_per_mtok", sa.Float(), nullable=False, server_default="0"),
|
|
209
|
+
sa.Column("cache_read_per_mtok", sa.Float(), nullable=False, server_default="0"),
|
|
210
|
+
sa.Column("context_window", sa.Integer(), nullable=True),
|
|
211
|
+
sa.Column("updated_at", sa.String(), nullable=False, server_default=_NOW),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# FTS5 virtual table + sync triggers. SQLite-only (Postgres has no fts5; the
|
|
215
|
+
# local-first ingester never targets prod Postgres) and guarded for SQLite builds
|
|
216
|
+
# compiled without the fts5 module. Uses the raw driver connection's executescript
|
|
217
|
+
# because the trigger bodies contain semicolons that a naive split would break.
|
|
218
|
+
bind = op.get_bind()
|
|
219
|
+
if bind.dialect.name == "sqlite":
|
|
220
|
+
raw = bind.connection.driver_connection # the underlying sqlite3.Connection
|
|
221
|
+
try:
|
|
222
|
+
raw.execute("CREATE VIRTUAL TABLE temp._zeno_fts5_probe USING fts5(x)")
|
|
223
|
+
raw.execute("DROP TABLE temp._zeno_fts5_probe")
|
|
224
|
+
raw.executescript(_FTS_SQL)
|
|
225
|
+
except Exception:
|
|
226
|
+
# fts5 module unavailable in this SQLite build; search degrades, ingest works.
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def downgrade() -> None:
|
|
231
|
+
bind = op.get_bind()
|
|
232
|
+
if bind.dialect.name == "sqlite":
|
|
233
|
+
raw = bind.connection.driver_connection
|
|
234
|
+
for stmt in (
|
|
235
|
+
"DROP TRIGGER IF EXISTS transcript_messages_au",
|
|
236
|
+
"DROP TRIGGER IF EXISTS transcript_messages_ad",
|
|
237
|
+
"DROP TRIGGER IF EXISTS transcript_messages_ai",
|
|
238
|
+
"DROP TABLE IF EXISTS transcript_messages_fts",
|
|
239
|
+
):
|
|
240
|
+
try:
|
|
241
|
+
raw.execute(stmt)
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
op.drop_table("model_pricing")
|
|
245
|
+
op.drop_table("token_usage_events")
|
|
246
|
+
op.drop_table("transcript_tool_calls")
|
|
247
|
+
op.drop_table("transcript_messages")
|
|
248
|
+
op.drop_table("transcript_sessions")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[alembic]
|
|
2
|
+
script_location = alembic
|
|
3
|
+
sqlalchemy.url = sqlite:///placeholder.db
|
|
4
|
+
|
|
5
|
+
[loggers]
|
|
6
|
+
keys = root,sqlalchemy,alembic
|
|
7
|
+
|
|
8
|
+
[handlers]
|
|
9
|
+
keys = console
|
|
10
|
+
|
|
11
|
+
[formatters]
|
|
12
|
+
keys = generic
|
|
13
|
+
|
|
14
|
+
[logger_root]
|
|
15
|
+
level = WARN
|
|
16
|
+
handlers = console
|
|
17
|
+
|
|
18
|
+
[logger_sqlalchemy]
|
|
19
|
+
level = WARN
|
|
20
|
+
handlers =
|
|
21
|
+
qualname = sqlalchemy.engine
|
|
22
|
+
|
|
23
|
+
[logger_alembic]
|
|
24
|
+
level = INFO
|
|
25
|
+
handlers =
|
|
26
|
+
qualname = alembic
|
|
27
|
+
|
|
28
|
+
[handler_console]
|
|
29
|
+
class = StreamHandler
|
|
30
|
+
args = (sys.stderr,)
|
|
31
|
+
level = NOTSET
|
|
32
|
+
formatter = generic
|
|
33
|
+
|
|
34
|
+
[formatter_generic]
|
|
35
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
zeno_sdk/_runtime.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections.abc import Coroutine
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def run_coro[T](coro: Coroutine[object, object, T]) -> T:
|
|
8
|
+
try:
|
|
9
|
+
asyncio.get_running_loop()
|
|
10
|
+
except RuntimeError:
|
|
11
|
+
return asyncio.run(coro)
|
|
12
|
+
raise RuntimeError("Zeno sync API called inside running event loop; use async entrypoints.")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .anthropic import instrument as instrument_anthropic
|
|
2
|
+
from .claude_code import instrument as instrument_claude_code
|
|
3
|
+
from .crewai import instrument_agent as instrument_crewai_agent
|
|
4
|
+
from .crewai import instrument_crew as instrument_crewai_crew
|
|
5
|
+
from .langgraph import instrument as instrument_langgraph
|
|
6
|
+
from .openai import instrument as instrument_openai
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"instrument_anthropic",
|
|
10
|
+
"instrument_claude_code",
|
|
11
|
+
"instrument_crewai_agent",
|
|
12
|
+
"instrument_crewai_crew",
|
|
13
|
+
"instrument_langgraph",
|
|
14
|
+
"instrument_openai",
|
|
15
|
+
]
|
zeno_sdk/auth.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import keyring
|
|
4
|
+
from keyring.errors import KeyringError
|
|
5
|
+
|
|
6
|
+
SERVICE = "zeno-sdk"
|
|
7
|
+
TOKEN_KEY = "clerk_jwt"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_token() -> str | None:
|
|
11
|
+
try:
|
|
12
|
+
return keyring.get_password(SERVICE, TOKEN_KEY)
|
|
13
|
+
except KeyringError:
|
|
14
|
+
return None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def set_token(token: str) -> None:
|
|
18
|
+
keyring.set_password(SERVICE, TOKEN_KEY, token)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def clear_token() -> None:
|
|
22
|
+
try:
|
|
23
|
+
keyring.delete_password(SERVICE, TOKEN_KEY)
|
|
24
|
+
except KeyringError:
|
|
25
|
+
return
|