somm-core 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. somm_core-0.6.1/.gitignore +38 -0
  2. somm_core-0.6.1/PKG-INFO +31 -0
  3. somm_core-0.6.1/README.md +13 -0
  4. somm_core-0.6.1/pyproject.toml +31 -0
  5. somm_core-0.6.1/src/somm_core/__init__.py +82 -0
  6. somm_core-0.6.1/src/somm_core/config.py +145 -0
  7. somm_core-0.6.1/src/somm_core/data/plan_catalog.toml +98 -0
  8. somm_core-0.6.1/src/somm_core/data/pricing_bundle.json +3472 -0
  9. somm_core-0.6.1/src/somm_core/migrations/0001_initial.sql +148 -0
  10. somm_core-0.6.1/src/somm_core/migrations/0002_workload_shadow.sql +26 -0
  11. somm_core-0.6.1/src/somm_core/migrations/0003_workload_capabilities.sql +6 -0
  12. somm_core-0.6.1/src/somm_core/migrations/0004_decisions.sql +32 -0
  13. somm_core-0.6.1/src/somm_core/migrations/0005_error_detail.sql +6 -0
  14. somm_core-0.6.1/src/somm_core/migrations/0006_workload_constraints.sql +47 -0
  15. somm_core-0.6.1/src/somm_core/migrations/0007_commission_and_call_params.sql +21 -0
  16. somm_core-0.6.1/src/somm_core/migrations/0008_learned_param_overrides.sql +31 -0
  17. somm_core-0.6.1/src/somm_core/migrations/0009_correlation_id.sql +16 -0
  18. somm_core-0.6.1/src/somm_core/migrations/0010_drop_commission_id.sql +13 -0
  19. somm_core-0.6.1/src/somm_core/models.py +275 -0
  20. somm_core-0.6.1/src/somm_core/parse.py +339 -0
  21. somm_core-0.6.1/src/somm_core/plans.py +614 -0
  22. somm_core-0.6.1/src/somm_core/pricing.py +365 -0
  23. somm_core-0.6.1/src/somm_core/py.typed +0 -0
  24. somm_core-0.6.1/src/somm_core/registry.py +72 -0
  25. somm_core-0.6.1/src/somm_core/repository.py +778 -0
  26. somm_core-0.6.1/src/somm_core/schema.py +93 -0
  27. somm_core-0.6.1/src/somm_core/version.py +7 -0
@@ -0,0 +1,38 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ *.egg
7
+ build/
8
+ dist/
9
+
10
+ # Environments
11
+ .venv/
12
+ .env
13
+ .env.*
14
+
15
+ # Tooling caches
16
+ .pytest_cache/
17
+ .mypy_cache/
18
+ .ruff_cache/
19
+
20
+ # Local Claude session id log (per-machine, not source of truth)
21
+ sessions.txt
22
+
23
+ # Local data (never commit telemetry)
24
+ .somm/
25
+ *.sqlite
26
+ *.sqlite-wal
27
+ *.sqlite-shm
28
+
29
+ # Author-local notes not for open source
30
+ notes/
31
+ .claude/
32
+
33
+ # Editor
34
+ .vscode/
35
+ .idea/
36
+ *.swp
37
+
38
+ # Archived internal design/process docs (see docs/BLUEPRINT.md for the public design doc)
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.4
2
+ Name: somm-core
3
+ Version: 0.6.1
4
+ Summary: somm shared core — schema, repository, config, parse, version
5
+ Project-URL: Homepage, https://github.com/lavallee/somm
6
+ Project-URL: Repository, https://github.com/lavallee/somm
7
+ Project-URL: Issues, https://github.com/lavallee/somm/issues
8
+ Project-URL: Changelog, https://github.com/lavallee/somm/blob/main/CHANGELOG.md
9
+ Author: Marc Lavallee
10
+ License: MIT
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.12
17
+ Description-Content-Type: text/markdown
18
+
19
+ # somm-core
20
+
21
+ Shared foundation for [somm](https://github.com/lavallee/somm) — the
22
+ self-hosted LLM telemetry, routing, and intelligence loop.
23
+
24
+ Contains the SQLite schema + migrations, the repository layer, config
25
+ loading, parse helpers (multimodal content blocks, capability
26
+ inference, JSON extraction), pricing (including the bundled offline
27
+ pricing snapshot), and the version constants every somm package
28
+ imports.
29
+
30
+ You usually want to install [`somm`](https://pypi.org/project/somm/)
31
+ (which depends on this package) rather than somm-core directly.
@@ -0,0 +1,13 @@
1
+ # somm-core
2
+
3
+ Shared foundation for [somm](https://github.com/lavallee/somm) — the
4
+ self-hosted LLM telemetry, routing, and intelligence loop.
5
+
6
+ Contains the SQLite schema + migrations, the repository layer, config
7
+ loading, parse helpers (multimodal content blocks, capability
8
+ inference, JSON extraction), pricing (including the bundled offline
9
+ pricing snapshot), and the version constants every somm package
10
+ imports.
11
+
12
+ You usually want to install [`somm`](https://pypi.org/project/somm/)
13
+ (which depends on this package) rather than somm-core directly.
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "somm-core"
3
+ version = "0.6.1"
4
+ description = "somm shared core — schema, repository, config, parse, version"
5
+ requires-python = ">=3.12"
6
+ license = { text = "MIT" }
7
+ readme = "README.md"
8
+ authors = [{ name = "Marc Lavallee" }]
9
+ classifiers = [
10
+ "Development Status :: 4 - Beta",
11
+ "License :: OSI Approved :: MIT License",
12
+ "Programming Language :: Python :: 3.12",
13
+ "Programming Language :: Python :: 3.13",
14
+ "Topic :: Software Development :: Libraries",
15
+ ]
16
+ dependencies = []
17
+
18
+ [project.urls]
19
+ Homepage = "https://github.com/lavallee/somm"
20
+ Repository = "https://github.com/lavallee/somm"
21
+ Issues = "https://github.com/lavallee/somm/issues"
22
+ Changelog = "https://github.com/lavallee/somm/blob/main/CHANGELOG.md"
23
+
24
+ [build-system]
25
+ requires = ["hatchling"]
26
+ build-backend = "hatchling.build"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src/somm_core"]
30
+ # hatchling includes non-Python package data (migrations/*.sql,
31
+ # data/pricing_bundle.json, py.typed) from the package dir by default.
@@ -0,0 +1,82 @@
1
+ """somm-core — shared schema, repository, config, parse, version across all somm packages."""
2
+
3
+ from somm_core.models import (
4
+ Call,
5
+ CallOutcome,
6
+ Decision,
7
+ EmbedResult,
8
+ FailureClass,
9
+ Outcome,
10
+ PrivacyClass,
11
+ Prompt,
12
+ SommResult,
13
+ ToolCall,
14
+ Workload,
15
+ )
16
+ from somm_core.plans import (
17
+ BurnRate,
18
+ CatalogEntry,
19
+ LimitStatus,
20
+ ObservedCeiling,
21
+ Plan,
22
+ PlanLimit,
23
+ limit_statuses,
24
+ load_catalog,
25
+ load_plans,
26
+ observed_ceilings,
27
+ payg_burn_rates,
28
+ plan_for,
29
+ recent_ok_calls,
30
+ )
31
+ from somm_core.pricing import (
32
+ cost_for_call,
33
+ list_intel,
34
+ merge_intel_capabilities,
35
+ seed_known_pricing,
36
+ sync_bundled_pricing,
37
+ write_intel,
38
+ )
39
+ from somm_core.registry import fleet_db_paths, register_project
40
+ from somm_core.repository import Repository
41
+ from somm_core.schema import current_schema_version, ensure_schema
42
+ from somm_core.version import SCHEMA_VERSION, VERSION
43
+
44
+ __all__ = [
45
+ "VERSION",
46
+ "SCHEMA_VERSION",
47
+ "Repository",
48
+ "ensure_schema",
49
+ "current_schema_version",
50
+ "Call",
51
+ "CallOutcome",
52
+ "Decision",
53
+ "EmbedResult",
54
+ "FailureClass",
55
+ "Outcome",
56
+ "PrivacyClass",
57
+ "Prompt",
58
+ "SommResult",
59
+ "ToolCall",
60
+ "Workload",
61
+ "cost_for_call",
62
+ "list_intel",
63
+ "merge_intel_capabilities",
64
+ "seed_known_pricing",
65
+ "sync_bundled_pricing",
66
+ "write_intel",
67
+ "Plan",
68
+ "PlanLimit",
69
+ "LimitStatus",
70
+ "CatalogEntry",
71
+ "ObservedCeiling",
72
+ "load_plans",
73
+ "load_catalog",
74
+ "plan_for",
75
+ "limit_statuses",
76
+ "observed_ceilings",
77
+ "BurnRate",
78
+ "payg_burn_rates",
79
+ "recent_ok_calls",
80
+ "register_project",
81
+ "fleet_db_paths",
82
+ ]
@@ -0,0 +1,145 @@
1
+ """Config loading: env > pyproject.toml > runtime override > defaults.
2
+
3
+ Minimal v0.1 surface. Expands as features demand.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import contextlib
9
+ import os
10
+ import tomllib
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+
14
+
15
+ @dataclass(slots=True)
16
+ class Config:
17
+ project: str = "default"
18
+ mode: str = "observe" # "observe" | "strict"
19
+ db_dir: Path = field(default_factory=lambda: Path("./.somm"))
20
+ spool_dir: Path = field(default_factory=lambda: Path("./.somm/spool"))
21
+ ollama_url: str = "http://localhost:11434"
22
+ ollama_model: str = "gemma4:e4b"
23
+ ollama_think: bool = False # Set "think": true on ollama requests (reasoning models)
24
+ ollama_keep_alive: str = "30m" # Model residency window; "0" to opt out
25
+ openrouter_api_key: str | None = None
26
+ openrouter_roster: list[str] | None = None
27
+ anthropic_api_key: str | None = None
28
+ anthropic_model: str = "claude-haiku-4-5-20251001"
29
+ openai_api_key: str | None = None
30
+ openai_model: str = "gpt-4o-mini"
31
+ openai_base_url: str = "https://api.openai.com/v1"
32
+ minimax_api_key: str | None = None
33
+ minimax_model: str = "MiniMax-M2.7"
34
+ deepseek_api_key: str | None = None
35
+ deepseek_model: str = "deepseek-chat"
36
+ gemini_api_key: str | None = None
37
+ gemini_model: str = "gemini-2.5-pro"
38
+ perplexity_api_key: str | None = None
39
+ perplexity_model: str = "sonar" # search-grounded; only used when pinned
40
+ http_timeout: float = 180.0 # seconds; used by openai-compat providers
41
+ provider_order: list[str] | None = None # e.g. ["openrouter", "minimax", "ollama"]
42
+ busy_timeout_ms: int = 5000
43
+ cross_project_enabled: bool = False
44
+ cross_project_path: Path | None = None # defaults to ~/.somm/global.sqlite
45
+ budget_fail_closed: bool = False # hard pre-request gate: block calls once a workload's daily cap is reached
46
+ budget_default_cap_usd_daily: float | None = None # daily cap for workloads with no explicit budget_cap_usd_daily (when fail_closed)
47
+ inprocess_workers: bool = False # run the somm-service scheduler inside the library process (SOMM_INPROCESS_WORKERS=1)
48
+
49
+ @property
50
+ def db_path(self) -> Path:
51
+ return self.db_dir / "calls.sqlite"
52
+
53
+ @property
54
+ def global_db_path(self) -> Path:
55
+ if self.cross_project_path is not None:
56
+ return Path(self.cross_project_path)
57
+ return Path.home() / ".somm" / "global.sqlite"
58
+
59
+
60
+ def load(project: str | None = None, cwd: Path | None = None) -> Config:
61
+ """Load config from env + ./pyproject.toml [tool.somm] + defaults.
62
+
63
+ Order: defaults < pyproject [tool.somm] < env (SOMM_*) < explicit args.
64
+ """
65
+ cwd = cwd or Path.cwd()
66
+ cfg = Config()
67
+
68
+ pyproject = cwd / "pyproject.toml"
69
+ if pyproject.exists():
70
+ with pyproject.open("rb") as f:
71
+ data = tomllib.load(f)
72
+ somm_cfg = data.get("tool", {}).get("somm", {})
73
+ for key in ("project", "mode", "ollama_url", "ollama_model"):
74
+ if key in somm_cfg:
75
+ setattr(cfg, key, somm_cfg[key])
76
+
77
+ env_map = {
78
+ "SOMM_PROJECT": "project",
79
+ "SOMM_MODE": "mode",
80
+ "SOMM_OLLAMA_URL": "ollama_url",
81
+ "SOMM_OLLAMA_MODEL": "ollama_model",
82
+ }
83
+ for env_var, attr in env_map.items():
84
+ if env_var in os.environ:
85
+ setattr(cfg, attr, os.environ[env_var])
86
+ if "OPENROUTER_API_KEY" in os.environ:
87
+ cfg.openrouter_api_key = os.environ["OPENROUTER_API_KEY"]
88
+ if "SOMM_OPENROUTER_ROSTER" in os.environ:
89
+ cfg.openrouter_roster = [
90
+ m.strip() for m in os.environ["SOMM_OPENROUTER_ROSTER"].split(",") if m.strip()
91
+ ]
92
+ if "SOMM_OLLAMA_THINK" in os.environ:
93
+ val = os.environ["SOMM_OLLAMA_THINK"].strip().lower()
94
+ cfg.ollama_think = val in ("1", "true", "yes", "on")
95
+ if "SOMM_OLLAMA_KEEP_ALIVE" in os.environ:
96
+ cfg.ollama_keep_alive = os.environ["SOMM_OLLAMA_KEEP_ALIVE"].strip()
97
+ if "SOMM_CROSS_PROJECT" in os.environ:
98
+ val = os.environ["SOMM_CROSS_PROJECT"].strip().lower()
99
+ cfg.cross_project_enabled = val in ("1", "true", "yes", "on")
100
+ if "SOMM_BUDGET_FAIL_CLOSED" in os.environ:
101
+ val = os.environ["SOMM_BUDGET_FAIL_CLOSED"].strip().lower()
102
+ cfg.budget_fail_closed = val in ("1", "true", "yes", "on")
103
+ if "SOMM_INPROCESS_WORKERS" in os.environ:
104
+ val = os.environ["SOMM_INPROCESS_WORKERS"].strip().lower()
105
+ cfg.inprocess_workers = val in ("1", "true", "yes", "on")
106
+ if "SOMM_BUDGET_DEFAULT_CAP_USD_DAILY" in os.environ:
107
+ with contextlib.suppress(ValueError):
108
+ cfg.budget_default_cap_usd_daily = float(os.environ["SOMM_BUDGET_DEFAULT_CAP_USD_DAILY"])
109
+ if "SOMM_GLOBAL_PATH" in os.environ:
110
+ cfg.cross_project_path = Path(os.environ["SOMM_GLOBAL_PATH"])
111
+ if "SOMM_PROVIDER_ORDER" in os.environ:
112
+ cfg.provider_order = [
113
+ p.strip() for p in os.environ["SOMM_PROVIDER_ORDER"].split(",") if p.strip()
114
+ ]
115
+ for env_var, attr in (
116
+ ("ANTHROPIC_API_KEY", "anthropic_api_key"),
117
+ ("SOMM_ANTHROPIC_MODEL", "anthropic_model"),
118
+ ("OPENAI_API_KEY", "openai_api_key"),
119
+ ("SOMM_OPENAI_MODEL", "openai_model"),
120
+ ("SOMM_OPENAI_BASE_URL", "openai_base_url"),
121
+ ("MINIMAX_API_KEY", "minimax_api_key"),
122
+ ("SOMM_MINIMAX_MODEL", "minimax_model"),
123
+ ("DEEPSEEK_API_KEY", "deepseek_api_key"),
124
+ ("SOMM_DEEPSEEK_MODEL", "deepseek_model"),
125
+ ("GEMINI_API_KEY", "gemini_api_key"),
126
+ ("SOMM_GEMINI_MODEL", "gemini_model"),
127
+ ("PERPLEXITY_API_KEY", "perplexity_api_key"),
128
+ ("SOMM_PERPLEXITY_MODEL", "perplexity_model"),
129
+ ):
130
+ if env_var in os.environ:
131
+ setattr(cfg, attr, os.environ[env_var])
132
+ if "SOMM_HTTP_TIMEOUT" in os.environ:
133
+ with contextlib.suppress(ValueError):
134
+ cfg.http_timeout = float(os.environ["SOMM_HTTP_TIMEOUT"])
135
+
136
+ if project is not None:
137
+ cfg.project = project
138
+
139
+ # Resolve paths relative to cwd; caller may also pass explicit paths.
140
+ cfg.db_dir = (
141
+ Path(cfg.db_dir).resolve() if cfg.db_dir.is_absolute() else (cwd / cfg.db_dir).resolve()
142
+ )
143
+ cfg.spool_dir = cfg.db_dir / "spool"
144
+
145
+ return cfg
@@ -0,0 +1,98 @@
1
+ # somm plan catalog — known commercial metered plans and their limits.
2
+ #
3
+ # Plan limits are vendor marketing copy, not an API: as of mid-2026 no
4
+ # major vendor publishes a complete, dated, numeric limits table
5
+ # (MiniMax describes quotas as "agent counts", Anthropic as multipliers,
6
+ # OpenAI hides its weekly cap). Every entry below therefore carries a
7
+ # `source` URL, a `last_verified` date (when a human last checked), and
8
+ # `notes` on provenance. Observed change cadence across vendors is
9
+ # roughly MONTHLY — `somm plans` warns when a referenced entry is >90
10
+ # days old, and RELEASING.md requires re-verification before releases.
11
+ #
12
+ # Where numbers are estimates, notes say so. The most reliable quota
13
+ # signal is your own telemetry: `somm plans` also reports "observed
14
+ # ceilings" — trailing-window usage at the moment of your own
15
+ # quota-429s.
16
+ #
17
+ # Reference an entry from ~/.somm/plans.toml:
18
+ # [minimax]
19
+ # mode = "metered"
20
+ # catalog = "token-plan-max"
21
+
22
+ [minimax.token-plan-plus]
23
+ display = "MiniMax Token Plan Plus ($20/mo)"
24
+ source = "https://platform.minimax.io/docs/guides/pricing-token-plan"
25
+ last_verified = "2026-07-02"
26
+ notes = "Rebranded from Coding Plan Q1 2026; vendor now describes quota qualitatively ('3-4 agents'). Monthly token figure below was published on the pricing page ~Jun 1-9 2026 then removed — treat as estimate. A 5h window also applies (size undisclosed; EN docs say rolling, CN docs say fixed). Unused quota does not carry over."
27
+ [[minimax.token-plan-plus.limits]]
28
+ window = "month"
29
+ quota = 1633000000
30
+ unit = "tokens_total"
31
+
32
+ [minimax.token-plan-max]
33
+ display = "MiniMax Token Plan Max ($50/mo)"
34
+ source = "https://platform.minimax.io/docs/guides/pricing-token-plan"
35
+ last_verified = "2026-07-02"
36
+ notes = "'4-5 agents'. Monthly token figure published ~Jun 1-9 2026 then removed — treat as estimate. Undisclosed 5h window also applies."
37
+ [[minimax.token-plan-max.limits]]
38
+ window = "month"
39
+ quota = 5053000000
40
+ unit = "tokens_total"
41
+
42
+ [minimax.token-plan-ultra]
43
+ display = "MiniMax Token Plan Ultra ($120/mo)"
44
+ source = "https://platform.minimax.io/docs/guides/pricing-token-plan"
45
+ last_verified = "2026-07-02"
46
+ notes = "'6-7 agents'. Monthly token figure published ~Jun 1-9 2026 then removed — treat as estimate. Undisclosed 5h window also applies."
47
+ [[minimax.token-plan-ultra.limits]]
48
+ window = "month"
49
+ quota = 9796000000
50
+ unit = "tokens_total"
51
+
52
+ [claude-cli.pro]
53
+ display = "Claude Pro ($20/mo)"
54
+ source = "https://support.claude.com/en/articles/11647753"
55
+ last_verified = "2026-07-02"
56
+ notes = "Anthropic publishes NO absolute numbers — limits are 5h rolling sessions plus weekly caps, described only relatively; pool is shared across claude.ai/Desktop/Claude Code. 5h limits doubled May 2026; +50% weekly boost expires 2026-07-13. No limits encoded here: rely on observed ceilings from your own 429s, or set your own from Settings > Usage."
57
+
58
+ [claude-cli.max-5x]
59
+ display = "Claude Max 5x ($100/mo)"
60
+ source = "https://support.claude.com/en/articles/11049741"
61
+ last_verified = "2026-07-02"
62
+ notes = "'5x Pro'; otherwise identical structure to Pro (see claude-cli/pro). The 'monthly spend limit' 429 message corresponds to the OPT-IN usage-credits cap you set at claude.ai/settings/usage, or was a mid-2026 labeling bug — not a published plan cap."
63
+
64
+ [claude-cli.max-20x]
65
+ display = "Claude Max 20x ($200/mo)"
66
+ source = "https://support.claude.com/en/articles/11049741"
67
+ last_verified = "2026-07-02"
68
+ notes = "'20x Pro'; see claude-cli/pro and claude-cli/max-5x notes."
69
+
70
+ [codex-cli.chatgpt-plus]
71
+ display = "ChatGPT Plus ($20/mo) — Codex"
72
+ source = "https://developers.openai.com/codex/pricing"
73
+ last_verified = "2026-07-02"
74
+ notes = "Official ranges per 5h rolling window: GPT-5.5 15-80, GPT-5.4 20-100, mini 60-350 'local messages' (cloud tasks + reviews share the pool). Underlying meter is token credits since Apr 2026. Quota below = GPT-5.5 range midpoint — a coarse approximation. An UNPUBLISHED weekly cap also applies."
75
+ [[codex-cli.chatgpt-plus.limits]]
76
+ window = "5h"
77
+ quota = 48
78
+ unit = "requests"
79
+
80
+ [codex-cli.chatgpt-pro-5x]
81
+ display = "ChatGPT Pro 5x ($100/mo) — Codex"
82
+ source = "https://developers.openai.com/codex/pricing"
83
+ last_verified = "2026-07-02"
84
+ notes = "GPT-5.5 75-400 per 5h; quota = range midpoint. Unpublished weekly cap also applies."
85
+ [[codex-cli.chatgpt-pro-5x.limits]]
86
+ window = "5h"
87
+ quota = 238
88
+ unit = "requests"
89
+
90
+ [codex-cli.chatgpt-pro-20x]
91
+ display = "ChatGPT Pro 20x ($200/mo) — Codex"
92
+ source = "https://developers.openai.com/codex/pricing"
93
+ last_verified = "2026-07-02"
94
+ notes = "GPT-5.5 300-1600 per 5h; quota = range midpoint. Unpublished weekly cap also applies. The $200 price is corroborated by secondary sources only; official table shows 'From $100/month'."
95
+ [[codex-cli.chatgpt-pro-20x.limits]]
96
+ window = "5h"
97
+ quota = 950
98
+ unit = "requests"