copilot-session-usage 0.9.1__tar.gz → 0.9.3__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.
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/PKG-INFO +1 -1
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/explanation/how-cost-estimation-works.md +19 -14
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/analyze-specific-session.md +1 -1
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/reference/pricing.md +13 -7
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/_internal/core.py +35 -26
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/data/models-and-pricing.lock +3 -3
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/data/models-and-pricing.yml +13 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_core.py +42 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_rendering.py +41 -7
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.editorconfig +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.gitattributes +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/changes/requests/skill-breakdown/01-request.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/guidelines/git-commit-message.guideline.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/guidelines/knowledge-base.guidelines.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/pull_request_template.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/consolidate-knowledge-base/SKILL.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/gh-commit-changes/SKILL.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/gh-create-pull-request/SKILL.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/gh-release-notes/SKILL.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/gh-release-notes/scripts/generate_release_notes.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/record-finding/README.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/record-finding/SKILL.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/workflows/ci.yml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/workflows/publish.yml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/workflows/refresh-pricing.yml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/workflows/release-notes.yml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/workflows/release.yml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.gitignore +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.readthedocs.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/AGENTS.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/CHANGELOG.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/CONSTITUTION.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/CONTRIBUTING.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/LICENSE +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/README.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/articles/presentation.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/internal/automated_release_proces.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/_static/changelog.js +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/_static/custom.css +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/changelog.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/conf.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/explanation/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/add-commit-trailer.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/batch-and-spending.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/copilot-cli-provider.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/export-json.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/wsl2.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/installation.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/reference/api.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/reference/cli.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/reference/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/tutorials/getting-started.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/tutorials/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/guidelines.yml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/justfile +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Base.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Concept.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Experiment.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Finding.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Hypothesis.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Outcome.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Playbook.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Principle.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Reference.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Structure.schema.yaml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/concepts/copilot-cli.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/concepts/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/concepts/overview.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/concepts/session-cost-analysis.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/concepts/threshold-based-pricing.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/experiments/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/experiments/verify-subagent-cost-attribution.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/findings/2026.07.02-00.00-subagent-logs-runsubagent-prefix.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/findings/2026.07.02-22.00-title-generation-not-counted-as-model-turn.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/findings/2026.07.02-23.00-cache-write-approximation.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/findings/2026.07.13-14.27-release-notes-overreported-maintainer-changes.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/findings/2026.07.24-15.01-actions-token-cannot-create-pr.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/findings/2026.08.05-16.24-utility-models-appear-in-logs-but-are-not-billed.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/findings/2026.10.07-17.30-cli-billed-nanoaiu-and-cache-write.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/findings/2026.10.07-request-billing-and-cache-write.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/findings/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/guides/automation-scripts.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/guides/cost-optimization.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/guides/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/guides/wsl2-setup.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/ideas/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/ideas/multi-session-efficiency-analytics.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/log.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/principles/findings-are-immutable.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/principles/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/reference/debug-log-format.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/reference/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/reference/pricing-formats.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/structures/cache-cost-approximation.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/structures/index.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/structures/knowledge-base-information-types.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/structures/session-discovery-algorithm.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/structures/subagent-cost-tracking.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/structures/vscode-copilot-extension.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/pyproject.toml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/scripts/refresh_pricing.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/skills/copilot-session-usage/SKILL.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/skills/copilot-session-usage/references/span-analysis-template.md +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/__init__.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/_internal/__init__.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/_internal/copilot_cli.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/_internal/git.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/_internal/vscode.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/api.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/cli.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/data/__init__.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/data/custom-models-pricing.yml +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/conftest.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_api.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_cli.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_copilot_cli.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_coverage_gaps.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_generate_release_notes.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_git.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_pricing_runtime.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_release_notes.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_vscode.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_vscode_platform.py +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/uv.lock +0 -0
- {copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/uv.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: copilot-session-usage
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
4
4
|
Summary: Usage and cost analytics for GitHub Copilot and Copilot-CLI session logs
|
|
5
5
|
Project-URL: Homepage, https://github.com/gsemet/copilot-session-usage
|
|
6
6
|
Project-URL: Documentation, https://copilot-session-usage.readthedocs.io/en/stable/
|
|
@@ -126,6 +126,7 @@ Subagent names are extracted from the JSONL filename
|
|
|
126
126
|
(`runSubagent-<Name>-functions.runSubagent:<id>.jsonl`) and from
|
|
127
127
|
`child_session_ref` events in `main.jsonl`.
|
|
128
128
|
|
|
129
|
+
(skill-attribution)=
|
|
129
130
|
## Skill attribution
|
|
130
131
|
|
|
131
132
|
Skills are detected from four sources in the debug logs:
|
|
@@ -237,22 +238,25 @@ provider's use of `copilotUsageNanoAiu` (1 nanoAIU = 1e-11 USD):
|
|
|
237
238
|
cumulative across resumes (the largest value from the last
|
|
238
239
|
`session.shutdown` or `session.usage_checkpoint`).
|
|
239
240
|
- Each `model_breakdown` row uses that model's `totalNanoAiu`. Without it, the
|
|
240
|
-
model's tokens are priced
|
|
241
|
-
cache writes, so fresh input is
|
|
242
|
-
cacheWriteTokens`, and only
|
|
243
|
-
cache-write rate. The pricing tier is
|
|
244
|
-
not the session sum.
|
|
241
|
+
model's tokens are priced using the reported cache-write count:
|
|
242
|
+
`inputTokens` includes cache reads and cache writes, so fresh input is
|
|
243
|
+
`inputTokens - cacheReadTokens - cacheWriteTokens`, and only
|
|
244
|
+
`cacheWriteTokens` are charged at the cache-write rate. The pricing tier is
|
|
245
|
+
chosen from the average request size, not the session sum. This fallback
|
|
246
|
+
remains an estimate because individual request sizes and discounts are absent.
|
|
245
247
|
- `subagents` lists the main agent and each subagent from
|
|
246
248
|
`session.shutdown.agentMetrics`, with their own billed `totalNanoAiu`.
|
|
247
|
-
- Per-model and per-agent metrics omit segments that ended without a
|
|
248
|
-
`session.shutdown` (a crash or kill before a resume).
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
249
|
+
- Per-model and per-agent metrics can omit segments that ended without a
|
|
250
|
+
`session.shutdown` (a crash or kill before a resume). A positive remainder
|
|
251
|
+
between session billing and attributed model costs is reported as
|
|
252
|
+
`total.unattributed_usd`, with a diagnostic. Displayed totals and breakdowns
|
|
253
|
+
are rounded to different precisions.
|
|
254
|
+
|
|
255
|
+
A local investigation on 2026-10-07 found that billed totals and breakdowns
|
|
256
|
+
reconciled across 101 CLI/App sessions. Pricing tokens with the reported
|
|
257
|
+
cache-write count at the base tier reproduced per-model billed amounts for
|
|
258
|
+
89 of 96 model entries. These are dated observations, not guarantees for future
|
|
259
|
+
sessions; see the [recorded finding](https://github.com/gsemet/copilot-session-usage/blob/5142026b2ec4c46432c4ff037ebea37030f3b56f/knowledge/findings/2026.10.07-17.30-cli-billed-nanoaiu-and-cache-write.md).
|
|
256
260
|
|
|
257
261
|
### When usage is unavailable
|
|
258
262
|
|
|
@@ -264,6 +268,7 @@ has no `session.shutdown` event. In that case:
|
|
|
264
268
|
fabricated zero.
|
|
265
269
|
- If a `session.usage_checkpoint` exists, its cumulative `totalNanoAiu`
|
|
266
270
|
provides `total.estimated_usd`, entirely reported as `unattributed_usd`.
|
|
271
|
+
Token totals and cache ratio remain unavailable.
|
|
267
272
|
- `diagnostics` explains exactly what is unavailable and why.
|
|
268
273
|
- Batch/aggregate operations exclude sessions with unavailable evidence from
|
|
269
274
|
their sums (rather than counting them as zero) and report how many were
|
|
@@ -109,7 +109,7 @@ invocation. It assigns the whole request context, not just the skill text or usa
|
|
|
109
109
|
caused by that skill. Automatically loaded skills, overlapping skills, and later
|
|
110
110
|
unrelated work cannot be reliably separated; requests before any invocation are
|
|
111
111
|
grouped under `unknown`. CLI/App per-skill tokens and costs remain unavailable.
|
|
112
|
-
See [
|
|
112
|
+
See [Skill attribution](../explanation/how-cost-estimation-works.md#skill-attribution).
|
|
113
113
|
:::
|
|
114
114
|
|
|
115
115
|
```bash
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/reference/pricing.md
RENAMED
|
@@ -189,21 +189,27 @@ matching pricing prefix. Aggregate-only CLI summaries cannot recover individual
|
|
|
189
189
|
request context sizes; without a billed figure, their tier is chosen from the
|
|
190
190
|
average request size.
|
|
191
191
|
|
|
192
|
+
Pricing row order does not affect tier selection, including at the boundary.
|
|
193
|
+
Supported thresholds are the current `≤`/`<=` and `>` comparisons, plus inclusive
|
|
194
|
+
`≥`/`>=`, with integer token counts and optional `K` or `M` suffixes.
|
|
195
|
+
Missing or `Not applicable` thresholds remain unbounded. Other syntax is rejected;
|
|
196
|
+
range expressions and future multi-tier formats are not inferred.
|
|
197
|
+
|
|
192
198
|
## Copilot CLI/App limitations
|
|
193
199
|
|
|
194
|
-
CLI/App costs use
|
|
195
|
-
|
|
196
|
-
usage
|
|
200
|
+
When reported, Copilot CLI/App costs use `totalNanoAiu` for the session, each
|
|
201
|
+
model, and each agent rather than token-based estimates. The event log has
|
|
202
|
+
cumulative usage rather than per-request token counts:
|
|
197
203
|
|
|
198
204
|
- **No per-skill cost.** There is no per-request usage to split by skill;
|
|
199
205
|
`skills.breakdown` is empty. Detected skills and tool counts remain.
|
|
200
|
-
- **Unattributed remainder.** Per-model and per-agent metrics omit session
|
|
206
|
+
- **Unattributed remainder.** Per-model and per-agent metrics can omit session
|
|
201
207
|
segments that ended without `session.shutdown` (crash or kill before a
|
|
202
208
|
resume). That cost is in `total.unattributed_usd`, not in any breakdown.
|
|
203
|
-
- **Active sessions** report
|
|
204
|
-
unattributed.
|
|
209
|
+
- **Active sessions** report the latest checkpoint total when available,
|
|
210
|
+
entirely unattributed, with token totals remaining unavailable.
|
|
205
211
|
- **Token fallback** (no billed figure) cannot see per-request long-context
|
|
206
|
-
tiers or discounts and is an estimate.
|
|
212
|
+
tiers or discounts and is an estimate. Reported costs are rounded.
|
|
207
213
|
|
|
208
214
|
## Custom pricing
|
|
209
215
|
|
|
@@ -151,23 +151,22 @@ def _clean_model_display_name(name: str) -> str:
|
|
|
151
151
|
return cleaned.strip()
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
def _parse_threshold(threshold: str) -> int | None:
|
|
155
|
-
"""Parse
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return int(value * multiplier)
|
|
154
|
+
def _parse_threshold(threshold: str | None) -> tuple[int, int | None]:
|
|
155
|
+
"""Parse current pricing comparisons into inclusive integer-token bounds."""
|
|
156
|
+
if threshold is None:
|
|
157
|
+
return 0, None
|
|
158
|
+
if not isinstance(threshold, str):
|
|
159
|
+
raise ValueError(f"unsupported pricing threshold: {threshold!r}")
|
|
160
|
+
if threshold.strip().lower() in ("not applicable", "n/a", ""):
|
|
161
|
+
return 0, None
|
|
162
|
+
match = re.fullmatch(r"\s*(<=|≤|>=|≥|>)\s*([0-9]+)\s*([KM]?)\s*", threshold, re.IGNORECASE)
|
|
163
|
+
if match is None:
|
|
164
|
+
raise ValueError(f"unsupported pricing threshold: {threshold!r}")
|
|
165
|
+
operator, amount, suffix = match.groups()
|
|
166
|
+
tokens = int(amount) * {"K": 1_000, "M": 1_000_000}.get(suffix.upper(), 1)
|
|
167
|
+
if operator in ("<=", "≤"):
|
|
168
|
+
return 0, tokens
|
|
169
|
+
return tokens + (operator == ">"), None
|
|
171
170
|
|
|
172
171
|
|
|
173
172
|
def _parse_price(price: str) -> float:
|
|
@@ -382,13 +381,15 @@ def _build_pricing_from_yaml(entries: list[dict], source: str) -> dict:
|
|
|
382
381
|
continue
|
|
383
382
|
name = _normalize_model_name(raw_name)
|
|
384
383
|
display_name = _clean_model_display_name(raw_name)
|
|
384
|
+
lower, upper = _parse_threshold(entry.get("threshold", ""))
|
|
385
385
|
tier = {
|
|
386
386
|
"input_per_m": _parse_price(entry.get("input", "0")),
|
|
387
387
|
"output_per_m": _parse_price(entry.get("output", "0")),
|
|
388
388
|
"cache_per_m": _parse_price(entry.get("cached_input", "0")),
|
|
389
389
|
"cache_write_per_m": _parse_price(entry.get("cache_write", "0")),
|
|
390
390
|
"tier": entry.get("tier", "Default"),
|
|
391
|
-
"threshold_tokens":
|
|
391
|
+
"threshold_tokens": upper,
|
|
392
|
+
"min_threshold_tokens": lower,
|
|
392
393
|
"provider": entry.get("provider", "unknown"),
|
|
393
394
|
"display_name": display_name,
|
|
394
395
|
}
|
|
@@ -759,14 +760,22 @@ def _get_model_rates(model: str, input_tok: int, pricing: dict[str, Any]) -> dic
|
|
|
759
760
|
if tiers is None:
|
|
760
761
|
tiers = models.get("default", [{}])
|
|
761
762
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
763
|
+
matches = [
|
|
764
|
+
tier
|
|
765
|
+
for tier in tiers
|
|
766
|
+
if input_tok >= tier.get("min_threshold_tokens", 0)
|
|
767
|
+
and (tier.get("threshold_tokens") is None or input_tok <= tier["threshold_tokens"])
|
|
768
|
+
]
|
|
769
|
+
if not matches:
|
|
770
|
+
raise ValueError(f"no pricing tier for {model} at {input_tok} input tokens")
|
|
771
|
+
return min( # type: ignore[no-any-return]
|
|
772
|
+
matches,
|
|
773
|
+
key=lambda tier: (
|
|
774
|
+
tier.get("threshold_tokens") is None,
|
|
775
|
+
tier.get("threshold_tokens") or 0,
|
|
776
|
+
-tier.get("min_threshold_tokens", 0),
|
|
777
|
+
),
|
|
778
|
+
)
|
|
770
779
|
|
|
771
780
|
|
|
772
781
|
def model_uses_fallback_pricing(model: str, pricing: dict[str, Any]) -> bool:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_captured": "2026-10-
|
|
2
|
+
"_captured": "2026-10-19T07:49:42.570898+00:00",
|
|
3
3
|
"_source": "https://raw.githubusercontent.com/github/docs/main/data/tables/copilot/models-and-pricing.yml",
|
|
4
4
|
"_yaml_path": "/home/runner/work/copilot-session-usage/copilot-session-usage/src/copilot_session_usage/data/models-and-pricing.yml",
|
|
5
|
-
"model_count":
|
|
6
|
-
"checksum": "
|
|
5
|
+
"model_count": 46,
|
|
6
|
+
"checksum": "c03a304b103a3dd1"
|
|
7
7
|
}
|
|
@@ -406,6 +406,19 @@
|
|
|
406
406
|
cached_input: $0.075
|
|
407
407
|
output: $3.75
|
|
408
408
|
|
|
409
|
+
# Promotional pricing expires 2026-12-01. Google hasn't shared post-promo pricing yet.
|
|
410
|
+
# When it does, update input, cached_input, and output below, then remove the
|
|
411
|
+
# [^gemini-4-argon-promo] footnote here and in models-and-pricing.md.
|
|
412
|
+
- model: 'Gemini 4 Argon[^gemini-4-argon-promo]'
|
|
413
|
+
provider: google
|
|
414
|
+
release_status: GA
|
|
415
|
+
category: Powerful
|
|
416
|
+
threshold: 'Not applicable'
|
|
417
|
+
tier: 'Default'
|
|
418
|
+
input: $2.00
|
|
419
|
+
cached_input: $0.10
|
|
420
|
+
output: $10.00
|
|
421
|
+
|
|
409
422
|
# xAI
|
|
410
423
|
- model: 'Grok 4.5'
|
|
411
424
|
provider: xai
|
|
@@ -304,6 +304,48 @@ def test_threshold_aware_pricing_exact_boundary():
|
|
|
304
304
|
assert rates["input_per_m"] == 2.50
|
|
305
305
|
|
|
306
306
|
|
|
307
|
+
@pytest.mark.parametrize("reverse", [False, True])
|
|
308
|
+
@pytest.mark.parametrize(
|
|
309
|
+
("upper", "lower", "boundary"),
|
|
310
|
+
[
|
|
311
|
+
("≤ 272K", "> 272K", 272_000),
|
|
312
|
+
("<= 272K", "> 272K", 272_000),
|
|
313
|
+
("<= 271999", ">= 272K", 271_999),
|
|
314
|
+
("≤ 271999", "≥ 272K", 271_999),
|
|
315
|
+
],
|
|
316
|
+
)
|
|
317
|
+
def test_threshold_aware_pricing_is_order_independent(reverse, upper, lower, boundary):
|
|
318
|
+
text = core._read_data_file("models-and-pricing.yml")
|
|
319
|
+
assert text is not None
|
|
320
|
+
rows = [
|
|
321
|
+
row
|
|
322
|
+
for row in core._parse_pricing_entries(text, "test")
|
|
323
|
+
if core._normalize_model_name(row.get("model", "")) == "gpt-5.4"
|
|
324
|
+
]
|
|
325
|
+
assert len(rows) == 2
|
|
326
|
+
rows = [{**rows[0], "threshold": upper}, {**rows[1], "threshold": lower}]
|
|
327
|
+
pricing = core._build_pricing_from_yaml(list(reversed(rows)) if reverse else rows, "test")
|
|
328
|
+
pricing["models"]["gpt-5.4"].reverse()
|
|
329
|
+
for tokens in [0, boundary - 1, boundary, boundary + 1, 500_000]:
|
|
330
|
+
expected = rows[0] if tokens <= boundary else rows[1]
|
|
331
|
+
assert core._get_model_rates("gpt-5.4", tokens, pricing)["tier"] == expected["tier"]
|
|
332
|
+
cached = tokens // 2
|
|
333
|
+
expected_cost = (
|
|
334
|
+
(tokens - cached) * core._parse_price(expected["input"])
|
|
335
|
+
+ cached * core._parse_price(expected["cached_input"])
|
|
336
|
+
+ 1000 * core._parse_price(expected["output"])
|
|
337
|
+
) / 1e6
|
|
338
|
+
assert core.estimate_cost(tokens, 1000, cached, "gpt-5.4", pricing) == pytest.approx(
|
|
339
|
+
expected_cost
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def test_threshold_aware_pricing_does_not_ignore_lower_bound():
|
|
344
|
+
pricing = core._build_pricing_from_yaml([{"model": "Test", "threshold": ">= 100K"}], "test")
|
|
345
|
+
with pytest.raises(ValueError, match="no pricing tier"):
|
|
346
|
+
core._get_model_rates("test", 99_999, pricing)
|
|
347
|
+
|
|
348
|
+
|
|
307
349
|
def test_estimate_cost_with_threshold():
|
|
308
350
|
pricing = {
|
|
309
351
|
"models": {
|
|
@@ -13,19 +13,53 @@ from copilot_session_usage._internal import core
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def test_parse_threshold_not_applicable():
|
|
16
|
-
assert core._parse_threshold("Not applicable")
|
|
17
|
-
assert core._parse_threshold("n/a")
|
|
18
|
-
assert core._parse_threshold("")
|
|
19
|
-
assert core._parse_threshold(None)
|
|
16
|
+
assert core._parse_threshold("Not applicable") == (0, None)
|
|
17
|
+
assert core._parse_threshold("n/a") == (0, None)
|
|
18
|
+
assert core._parse_threshold("") == (0, None)
|
|
19
|
+
assert core._parse_threshold(None) == (0, None)
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def test_parse_threshold_unbounded():
|
|
23
|
-
|
|
24
|
-
assert core._parse_threshold("> 272K") == 272_000
|
|
23
|
+
assert core._parse_threshold("> 272K") == (272_001, None)
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
def test_parse_threshold_no_match():
|
|
28
|
-
|
|
27
|
+
with pytest.raises(ValueError, match="threshold"):
|
|
28
|
+
core._parse_threshold("abc")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
("threshold", "expected"),
|
|
33
|
+
[
|
|
34
|
+
("<= 100K", (0, 100_000)),
|
|
35
|
+
(">= 100K", (100_000, None)),
|
|
36
|
+
(" > 100k ", (100_001, None)),
|
|
37
|
+
("≤ 100K", (0, 100_000)),
|
|
38
|
+
("≥ 100K", (100_000, None)),
|
|
39
|
+
("<= 1M", (0, 1_000_000)),
|
|
40
|
+
],
|
|
41
|
+
)
|
|
42
|
+
def test_parse_threshold_conditions(threshold, expected):
|
|
43
|
+
assert core._parse_threshold(threshold) == expected
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.parametrize(
|
|
47
|
+
"threshold",
|
|
48
|
+
[
|
|
49
|
+
"garbage 100K",
|
|
50
|
+
">=",
|
|
51
|
+
"100K trailing",
|
|
52
|
+
"200K-100K",
|
|
53
|
+
"< 0",
|
|
54
|
+
"> 2 and < 3",
|
|
55
|
+
">= 0.5M",
|
|
56
|
+
100_000,
|
|
57
|
+
True,
|
|
58
|
+
],
|
|
59
|
+
)
|
|
60
|
+
def test_parse_threshold_invalid_conditions(threshold):
|
|
61
|
+
with pytest.raises(ValueError, match="threshold"):
|
|
62
|
+
core._parse_threshold(threshold)
|
|
29
63
|
|
|
30
64
|
|
|
31
65
|
# ─── _parse_price edge cases ──────────────────────────────────────────────────
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/ISSUE_TEMPLATE/bug_report.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/pull_request_template.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/gh-release-notes/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/record-finding/README.md
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/skills/record-finding/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/workflows/refresh-pricing.yml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/.github/workflows/release-notes.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/_static/changelog.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/explanation/index.md
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/add-commit-trailer.md
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/batch-and-spending.md
RENAMED
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/how-to/export-json.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/docs/source/tutorials/getting-started.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Base.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Concept.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Experiment.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Finding.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Hypothesis.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Outcome.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Playbook.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Principle.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Reference.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/_schema/Structure.schema.yaml
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/concepts/copilot-cli.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/guides/automation-scripts.md
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/guides/cost-optimization.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/reference/debug-log-format.md
RENAMED
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/knowledge/reference/pricing-formats.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/skills/copilot-session-usage/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/api.py
RENAMED
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/src/copilot_session_usage/cli.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{copilot_session_usage-0.9.1 → copilot_session_usage-0.9.3}/tests/test_generate_release_notes.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|