@llblab/pi-kit 0.1.0
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.
- package/AGENTS.md +35 -0
- package/BACKLOG.md +3 -0
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +43 -0
- package/node_modules/@llblab/pi-actors/AGENTS.md +150 -0
- package/node_modules/@llblab/pi-actors/BACKLOG.md +6 -0
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +865 -0
- package/node_modules/@llblab/pi-actors/README.md +231 -0
- package/node_modules/@llblab/pi-actors/banner.jpg +0 -0
- package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/artifact-manifest.json +9 -0
- package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/control-endpoint.json +6 -0
- package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/control-record.json +9 -0
- package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/recipe-summary.json +8 -0
- package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/run-state.json +10 -0
- package/node_modules/@llblab/pi-actors/dist/fixtures/protocol/trace-event.json +9 -0
- package/node_modules/@llblab/pi-actors/dist/index.d.ts +7 -0
- package/node_modules/@llblab/pi-actors/dist/index.js +17 -0
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +146 -0
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +800 -0
- package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.d.ts +20 -0
- package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.js +103 -0
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.d.ts +85 -0
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +804 -0
- package/node_modules/@llblab/pi-actors/dist/lib/config.d.ts +34 -0
- package/node_modules/@llblab/pi-actors/dist/lib/config.js +216 -0
- package/node_modules/@llblab/pi-actors/dist/lib/control-projection.d.ts +20 -0
- package/node_modules/@llblab/pi-actors/dist/lib/control-projection.js +66 -0
- package/node_modules/@llblab/pi-actors/dist/lib/control.d.ts +15 -0
- package/node_modules/@llblab/pi-actors/dist/lib/control.js +97 -0
- package/node_modules/@llblab/pi-actors/dist/lib/draft-consolidation-transaction.d.ts +65 -0
- package/node_modules/@llblab/pi-actors/dist/lib/draft-consolidation-transaction.js +610 -0
- package/node_modules/@llblab/pi-actors/dist/lib/draft-consolidation.d.ts +35 -0
- package/node_modules/@llblab/pi-actors/dist/lib/draft-consolidation.js +126 -0
- package/node_modules/@llblab/pi-actors/dist/lib/draft-review.d.ts +56 -0
- package/node_modules/@llblab/pi-actors/dist/lib/draft-review.js +254 -0
- package/node_modules/@llblab/pi-actors/dist/lib/draft-sleep.d.ts +65 -0
- package/node_modules/@llblab/pi-actors/dist/lib/draft-sleep.js +467 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution-output.d.ts +25 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution-output.js +88 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.d.ts +17 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.js +85 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution.d.ts +99 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution.js +725 -0
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.d.ts +21 -0
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +141 -0
- package/node_modules/@llblab/pi-actors/dist/lib/file-state.d.ts +18 -0
- package/node_modules/@llblab/pi-actors/dist/lib/file-state.js +310 -0
- package/node_modules/@llblab/pi-actors/dist/lib/identity.d.ts +9 -0
- package/node_modules/@llblab/pi-actors/dist/lib/identity.js +27 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.d.ts +16 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.js +57 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-command.d.ts +7 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-command.js +37 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.d.ts +100 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.js +824 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector.d.ts +20 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector.js +136 -0
- package/node_modules/@llblab/pi-actors/dist/lib/limits.d.ts +28 -0
- package/node_modules/@llblab/pi-actors/dist/lib/limits.js +28 -0
- package/node_modules/@llblab/pi-actors/dist/lib/model-context.d.ts +56 -0
- package/node_modules/@llblab/pi-actors/dist/lib/model-context.js +220 -0
- package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +186 -0
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +951 -0
- package/node_modules/@llblab/pi-actors/dist/lib/paths.d.ts +27 -0
- package/node_modules/@llblab/pi-actors/dist/lib/paths.js +72 -0
- package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +19 -0
- package/node_modules/@llblab/pi-actors/dist/lib/pi.js +21 -0
- package/node_modules/@llblab/pi-actors/dist/lib/preflight-diagnostics.d.ts +27 -0
- package/node_modules/@llblab/pi-actors/dist/lib/preflight-diagnostics.js +86 -0
- package/node_modules/@llblab/pi-actors/dist/lib/prompts.d.ts +24 -0
- package/node_modules/@llblab/pi-actors/dist/lib/prompts.js +49 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipe-control.d.ts +7 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipe-control.js +43 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.d.ts +36 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.js +197 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-discovery.d.ts +77 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-discovery.js +783 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-references.d.ts +113 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-references.js +1108 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-usage.d.ts +21 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-usage.js +494 -0
- package/node_modules/@llblab/pi-actors/dist/lib/registry.d.ts +67 -0
- package/node_modules/@llblab/pi-actors/dist/lib/registry.js +504 -0
- package/node_modules/@llblab/pi-actors/dist/lib/review-control.d.ts +14 -0
- package/node_modules/@llblab/pi-actors/dist/lib/review-control.js +110 -0
- package/node_modules/@llblab/pi-actors/dist/lib/review-diagnostics.d.ts +11 -0
- package/node_modules/@llblab/pi-actors/dist/lib/review-diagnostics.js +148 -0
- package/node_modules/@llblab/pi-actors/dist/lib/review-projection.d.ts +14 -0
- package/node_modules/@llblab/pi-actors/dist/lib/review-projection.js +166 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-evidence-policy.d.ts +95 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-evidence-policy.js +177 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.d.ts +18 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +125 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.d.ts +21 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.js +68 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control-delivery.d.ts +28 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control-delivery.js +150 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control.d.ts +21 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control.js +89 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-controls.d.ts +39 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-controls.js +283 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-identity.d.ts +5 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-identity.js +11 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-index.d.ts +27 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-index.js +142 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.d.ts +7 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.js +96 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-parent-teardown.d.ts +51 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-parent-teardown.js +172 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process.d.ts +22 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process.js +125 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.d.ts +18 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.js +108 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-start.d.ts +12 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-start.js +68 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-status.d.ts +10 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-status.js +68 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.d.ts +48 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.js +491 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runtime-identity.d.ts +7 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runtime-identity.js +35 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runtime-triage.d.ts +29 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runtime-triage.js +60 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.d.ts +69 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +339 -0
- package/node_modules/@llblab/pi-actors/dist/lib/schema.d.ts +60 -0
- package/node_modules/@llblab/pi-actors/dist/lib/schema.js +428 -0
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.d.ts +45 -0
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.js +204 -0
- package/node_modules/@llblab/pi-actors/dist/lib/state-readers.d.ts +21 -0
- package/node_modules/@llblab/pi-actors/dist/lib/state-readers.js +74 -0
- package/node_modules/@llblab/pi-actors/dist/lib/temp.d.ts +10 -0
- package/node_modules/@llblab/pi-actors/dist/lib/temp.js +90 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review-lineage-transaction.d.ts +27 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review-lineage-transaction.js +597 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review-lineage.d.ts +24 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review-lineage.js +98 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review-scheduler.d.ts +80 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review-scheduler.js +494 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review-transaction.d.ts +50 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review-transaction.js +362 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review.d.ts +56 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tool-review.js +197 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-access.d.ts +19 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-access.js +44 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.d.ts +15 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.js +532 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-local.d.ts +17 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-local.js +191 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-message.d.ts +9 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-message.js +139 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-register.d.ts +18 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-register.js +41 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-response.d.ts +20 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-response.js +243 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.d.ts +15 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.js +133 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools.d.ts +28 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools.js +49 -0
- package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.d.ts +22 -0
- package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.js +231 -0
- package/node_modules/@llblab/pi-actors/dist/pi-actors/index.js +1 -0
- package/node_modules/@llblab/pi-actors/dist/scripts/async-runner.mjs +555 -0
- package/node_modules/@llblab/pi-actors/dist/scripts/build-dist.mjs +60 -0
- package/node_modules/@llblab/pi-actors/dist/scripts/conformance.mjs +63 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +126 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/command-validate.json +13 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/jsonl-tail.json +10 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/recipe-validate.json +11 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/resource-locker-snapshot.json +11 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/resource-locker.json +25 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/run-ops-snapshot.json +15 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/run-state-files.json +13 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/recipes/run-summary.json +9 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/references/diagnostics.md +44 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/references/persistent-tools.md +76 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/references/recipes.md +51 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/references/runs.md +41 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/scripts/resource-locker.mjs +425 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/scripts/run-utils.mjs +164 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/scripts/validate-recipe.mjs +370 -0
- package/node_modules/@llblab/pi-actors/dist/skills/artifacts/SKILL.md +33 -0
- package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/bundle.json +77 -0
- package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/file-write.json +14 -0
- package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/manifest.json +14 -0
- package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/report.json +39 -0
- package/node_modules/@llblab/pi-actors/dist/skills/artifacts/recipes/write.json +53 -0
- package/node_modules/@llblab/pi-actors/dist/skills/artifacts/scripts/artifact-utils.mjs +63 -0
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/SKILL.md +57 -0
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/genapps/music-player.mjs +360 -0
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/recipes/playback.json +29 -0
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/scripts/playback-client.mjs +143 -0
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/scripts/playback.mjs +1446 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/SKILL.md +37 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/changelog-head.json +11 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/changelog-section.json +11 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/docs-maintenance.json +66 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/git-log.json +11 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/git-status.json +9 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/markdown-index.json +14 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/package-summary.json +9 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/release-readiness.json +115 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/release-summary.json +74 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/repo-health.json +75 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/run-ops.json +56 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/recipes/skill-summary.json +11 -0
- package/node_modules/@llblab/pi-actors/dist/skills/project-work/scripts/project-utils.mjs +89 -0
- package/node_modules/@llblab/pi-actors/dist/skills/recipe-memory/SKILL.md +36 -0
- package/node_modules/@llblab/pi-actors/dist/skills/recipe-memory/recipes/draft-review.json +14 -0
- package/node_modules/@llblab/pi-actors/dist/skills/recipe-memory/recipes/tool-review.json +14 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/SKILL.md +84 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/architect.json +82 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/checkpoint-continuation.json +51 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/development-tasking.json +67 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/lens-review.json +63 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/quorum-review.json +65 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/research-synthesis.json +81 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/review-readiness.json +59 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-artifact.json +21 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-checkpoint.json +21 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-conflict-report.json +19 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-contradiction-map.json +20 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-critic.json +22 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-evidence-map.json +20 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-followup.json +21 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-judge.json +22 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-merge.json +24 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-normalize.json +20 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-plan.json +20 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-preflight.json +18 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-prompt.json +16 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-prompts.json +39 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-quorum.json +31 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-review-coordinator.json +170 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-review.json +26 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-task-card.json +22 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-tools.json +15 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/recipes/subagent-verify.json +23 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/references/development-swarm.md +189 -0
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/references/review-swarms.md +115 -0
- package/node_modules/@llblab/pi-actors/docs/README.md +20 -0
- package/node_modules/@llblab/pi-actors/docs/actor-inspector.md +50 -0
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +141 -0
- package/node_modules/@llblab/pi-actors/docs/command-templates.md +321 -0
- package/node_modules/@llblab/pi-actors/docs/recipe-library.md +89 -0
- package/node_modules/@llblab/pi-actors/docs/releasing.md +28 -0
- package/node_modules/@llblab/pi-actors/docs/template-recipes.md +175 -0
- package/node_modules/@llblab/pi-actors/docs/tool-registry.md +90 -0
- package/node_modules/@llblab/pi-actors/fixtures/protocol/artifact-manifest.json +9 -0
- package/node_modules/@llblab/pi-actors/fixtures/protocol/control-endpoint.json +6 -0
- package/node_modules/@llblab/pi-actors/fixtures/protocol/control-record.json +9 -0
- package/node_modules/@llblab/pi-actors/fixtures/protocol/recipe-summary.json +8 -0
- package/node_modules/@llblab/pi-actors/fixtures/protocol/run-state.json +10 -0
- package/node_modules/@llblab/pi-actors/fixtures/protocol/trace-event.json +9 -0
- package/node_modules/@llblab/pi-actors/index.ts +30 -0
- package/node_modules/@llblab/pi-actors/lib/async-runs.ts +1245 -0
- package/node_modules/@llblab/pi-actors/lib/automatic-review-runtime.ts +148 -0
- package/node_modules/@llblab/pi-actors/lib/command-templates.ts +1116 -0
- package/node_modules/@llblab/pi-actors/lib/config.ts +261 -0
- package/node_modules/@llblab/pi-actors/lib/control-projection.ts +105 -0
- package/node_modules/@llblab/pi-actors/lib/control.ts +117 -0
- package/node_modules/@llblab/pi-actors/lib/draft-consolidation-transaction.ts +821 -0
- package/node_modules/@llblab/pi-actors/lib/draft-consolidation.ts +181 -0
- package/node_modules/@llblab/pi-actors/lib/draft-review.ts +325 -0
- package/node_modules/@llblab/pi-actors/lib/draft-sleep.ts +575 -0
- package/node_modules/@llblab/pi-actors/lib/execution-output.ts +123 -0
- package/node_modules/@llblab/pi-actors/lib/execution-sessions.ts +111 -0
- package/node_modules/@llblab/pi-actors/lib/execution.ts +1153 -0
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +181 -0
- package/node_modules/@llblab/pi-actors/lib/file-state.ts +306 -0
- package/node_modules/@llblab/pi-actors/lib/identity.ts +29 -0
- package/node_modules/@llblab/pi-actors/lib/inspector-actions.ts +79 -0
- package/node_modules/@llblab/pi-actors/lib/inspector-command.ts +54 -0
- package/node_modules/@llblab/pi-actors/lib/inspector-overlay.ts +901 -0
- package/node_modules/@llblab/pi-actors/lib/inspector.ts +204 -0
- package/node_modules/@llblab/pi-actors/lib/limits.ts +29 -0
- package/node_modules/@llblab/pi-actors/lib/model-context.ts +359 -0
- package/node_modules/@llblab/pi-actors/lib/observability.ts +1337 -0
- package/node_modules/@llblab/pi-actors/lib/paths.ts +111 -0
- package/node_modules/@llblab/pi-actors/lib/pi.ts +47 -0
- package/node_modules/@llblab/pi-actors/lib/preflight-diagnostics.ts +132 -0
- package/node_modules/@llblab/pi-actors/lib/prompts.ts +69 -0
- package/node_modules/@llblab/pi-actors/lib/recipe-control.ts +52 -0
- package/node_modules/@llblab/pi-actors/lib/recipes-context.ts +268 -0
- package/node_modules/@llblab/pi-actors/lib/recipes-discovery.ts +1060 -0
- package/node_modules/@llblab/pi-actors/lib/recipes-references.ts +1598 -0
- package/node_modules/@llblab/pi-actors/lib/recipes-usage.ts +638 -0
- package/node_modules/@llblab/pi-actors/lib/registry.ts +806 -0
- package/node_modules/@llblab/pi-actors/lib/review-control.ts +136 -0
- package/node_modules/@llblab/pi-actors/lib/review-diagnostics.ts +164 -0
- package/node_modules/@llblab/pi-actors/lib/review-projection.ts +196 -0
- package/node_modules/@llblab/pi-actors/lib/run-evidence-policy.ts +242 -0
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +155 -0
- package/node_modules/@llblab/pi-actors/lib/runs-artifacts.ts +96 -0
- package/node_modules/@llblab/pi-actors/lib/runs-control-delivery.ts +209 -0
- package/node_modules/@llblab/pi-actors/lib/runs-control.ts +148 -0
- package/node_modules/@llblab/pi-actors/lib/runs-controls.ts +282 -0
- package/node_modules/@llblab/pi-actors/lib/runs-identity.ts +14 -0
- package/node_modules/@llblab/pi-actors/lib/runs-index.ts +192 -0
- package/node_modules/@llblab/pi-actors/lib/runs-ownership.ts +136 -0
- package/node_modules/@llblab/pi-actors/lib/runs-parent-teardown.ts +257 -0
- package/node_modules/@llblab/pi-actors/lib/runs-process.ts +174 -0
- package/node_modules/@llblab/pi-actors/lib/runs-retention.ts +140 -0
- package/node_modules/@llblab/pi-actors/lib/runs-start.ts +113 -0
- package/node_modules/@llblab/pi-actors/lib/runs-status.ts +101 -0
- package/node_modules/@llblab/pi-actors/lib/runs-trace.ts +611 -0
- package/node_modules/@llblab/pi-actors/lib/runtime-identity.ts +39 -0
- package/node_modules/@llblab/pi-actors/lib/runtime-triage.ts +106 -0
- package/node_modules/@llblab/pi-actors/lib/runtime.ts +430 -0
- package/node_modules/@llblab/pi-actors/lib/schema.ts +525 -0
- package/node_modules/@llblab/pi-actors/lib/session-evidence.ts +302 -0
- package/node_modules/@llblab/pi-actors/lib/state-readers.ts +93 -0
- package/node_modules/@llblab/pi-actors/lib/temp.ts +99 -0
- package/node_modules/@llblab/pi-actors/lib/tool-review-lineage-transaction.ts +881 -0
- package/node_modules/@llblab/pi-actors/lib/tool-review-lineage.ts +145 -0
- package/node_modules/@llblab/pi-actors/lib/tool-review-scheduler.ts +635 -0
- package/node_modules/@llblab/pi-actors/lib/tool-review-transaction.ts +563 -0
- package/node_modules/@llblab/pi-actors/lib/tool-review.ts +270 -0
- package/node_modules/@llblab/pi-actors/lib/tools-access.ts +68 -0
- package/node_modules/@llblab/pi-actors/lib/tools-inspect.ts +673 -0
- package/node_modules/@llblab/pi-actors/lib/tools-local.ts +303 -0
- package/node_modules/@llblab/pi-actors/lib/tools-message.ts +176 -0
- package/node_modules/@llblab/pi-actors/lib/tools-register.ts +63 -0
- package/node_modules/@llblab/pi-actors/lib/tools-response.ts +285 -0
- package/node_modules/@llblab/pi-actors/lib/tools-spawn.ts +212 -0
- package/node_modules/@llblab/pi-actors/lib/tools.ts +88 -0
- package/node_modules/@llblab/pi-actors/lib/trace-projection.ts +306 -0
- package/node_modules/@llblab/pi-actors/package.json +76 -0
- package/node_modules/@llblab/pi-actors/scripts/async-runner.mjs +555 -0
- package/node_modules/@llblab/pi-actors/scripts/build-dist.mjs +60 -0
- package/node_modules/@llblab/pi-actors/scripts/conformance.mjs +63 -0
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +126 -0
- package/node_modules/@llblab/pi-actors/skills/actors/recipes/command-validate.json +13 -0
- package/node_modules/@llblab/pi-actors/skills/actors/recipes/jsonl-tail.json +10 -0
- package/node_modules/@llblab/pi-actors/skills/actors/recipes/recipe-validate.json +11 -0
- package/node_modules/@llblab/pi-actors/skills/actors/recipes/resource-locker-snapshot.json +11 -0
- package/node_modules/@llblab/pi-actors/skills/actors/recipes/resource-locker.json +25 -0
- package/node_modules/@llblab/pi-actors/skills/actors/recipes/run-ops-snapshot.json +15 -0
- package/node_modules/@llblab/pi-actors/skills/actors/recipes/run-state-files.json +13 -0
- package/node_modules/@llblab/pi-actors/skills/actors/recipes/run-summary.json +9 -0
- package/node_modules/@llblab/pi-actors/skills/actors/references/diagnostics.md +44 -0
- package/node_modules/@llblab/pi-actors/skills/actors/references/persistent-tools.md +76 -0
- package/node_modules/@llblab/pi-actors/skills/actors/references/recipes.md +51 -0
- package/node_modules/@llblab/pi-actors/skills/actors/references/runs.md +41 -0
- package/node_modules/@llblab/pi-actors/skills/actors/scripts/resource-locker.mjs +425 -0
- package/node_modules/@llblab/pi-actors/skills/actors/scripts/run-utils.mjs +164 -0
- package/node_modules/@llblab/pi-actors/skills/actors/scripts/validate-recipe.mjs +370 -0
- package/node_modules/@llblab/pi-actors/skills/artifacts/SKILL.md +33 -0
- package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/bundle.json +77 -0
- package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/file-write.json +14 -0
- package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/manifest.json +14 -0
- package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/report.json +39 -0
- package/node_modules/@llblab/pi-actors/skills/artifacts/recipes/write.json +53 -0
- package/node_modules/@llblab/pi-actors/skills/artifacts/scripts/artifact-utils.mjs +63 -0
- package/node_modules/@llblab/pi-actors/skills/music-player/SKILL.md +57 -0
- package/node_modules/@llblab/pi-actors/skills/music-player/genapps/music-player.mjs +360 -0
- package/node_modules/@llblab/pi-actors/skills/music-player/recipes/playback.json +29 -0
- package/node_modules/@llblab/pi-actors/skills/music-player/scripts/playback-client.mjs +143 -0
- package/node_modules/@llblab/pi-actors/skills/music-player/scripts/playback.mjs +1446 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/SKILL.md +37 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/changelog-head.json +11 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/changelog-section.json +11 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/docs-maintenance.json +66 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/git-log.json +11 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/git-status.json +9 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/markdown-index.json +14 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/package-summary.json +9 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/release-readiness.json +115 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/release-summary.json +74 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/repo-health.json +75 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/run-ops.json +56 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/recipes/skill-summary.json +11 -0
- package/node_modules/@llblab/pi-actors/skills/project-work/scripts/project-utils.mjs +89 -0
- package/node_modules/@llblab/pi-actors/skills/recipe-memory/SKILL.md +36 -0
- package/node_modules/@llblab/pi-actors/skills/recipe-memory/recipes/draft-review.json +14 -0
- package/node_modules/@llblab/pi-actors/skills/recipe-memory/recipes/tool-review.json +14 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/SKILL.md +84 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/architect.json +82 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/checkpoint-continuation.json +51 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/development-tasking.json +67 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/lens-review.json +63 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/quorum-review.json +65 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/research-synthesis.json +81 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/review-readiness.json +59 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-artifact.json +21 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-checkpoint.json +21 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-conflict-report.json +19 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-contradiction-map.json +20 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-critic.json +22 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-evidence-map.json +20 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-followup.json +21 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-judge.json +22 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-merge.json +24 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-normalize.json +20 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-plan.json +20 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-preflight.json +18 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-prompt.json +16 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-prompts.json +39 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-quorum.json +31 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-review-coordinator.json +170 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-review.json +26 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-task-card.json +22 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-tools.json +15 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/recipes/subagent-verify.json +23 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/references/development-swarm.md +189 -0
- package/node_modules/@llblab/pi-actors/skills/swarm/references/review-swarms.md +115 -0
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +17 -0
- package/node_modules/@llblab/pi-codex-usage/BACKLOG.md +8 -0
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +80 -0
- package/node_modules/@llblab/pi-codex-usage/LICENSE +22 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +113 -0
- package/node_modules/@llblab/pi-codex-usage/banner.jpg +0 -0
- package/node_modules/@llblab/pi-codex-usage/index.ts +1525 -0
- package/node_modules/@llblab/pi-codex-usage/package.json +59 -0
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +56 -0
- package/node_modules/@llblab/pi-grow-loop/BACKLOG.md +3 -0
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +126 -0
- package/node_modules/@llblab/pi-grow-loop/README.md +216 -0
- package/node_modules/@llblab/pi-grow-loop/banner.jpg +0 -0
- package/node_modules/@llblab/pi-grow-loop/index.ts +219 -0
- package/node_modules/@llblab/pi-grow-loop/package.json +64 -0
- package/node_modules/@llblab/pi-grow-loop/skills/grow-loop/SKILL.md +131 -0
- package/node_modules/@llblab/pi-grow-loop/skills/while-true/SKILL.md +233 -0
- package/node_modules/@llblab/pi-telegram/AGENTS.md +157 -0
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +15 -0
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +886 -0
- package/node_modules/@llblab/pi-telegram/README.md +330 -0
- package/node_modules/@llblab/pi-telegram/api/activity.ts +15 -0
- package/node_modules/@llblab/pi-telegram/api/commands.ts +11 -0
- package/node_modules/@llblab/pi-telegram/api/delivery.ts +21 -0
- package/node_modules/@llblab/pi-telegram/api/inbound.ts +14 -0
- package/node_modules/@llblab/pi-telegram/api/keyboard.ts +11 -0
- package/node_modules/@llblab/pi-telegram/api/outbound.ts +11 -0
- package/node_modules/@llblab/pi-telegram/api/sections.ts +17 -0
- package/node_modules/@llblab/pi-telegram/api/status.ts +12 -0
- package/node_modules/@llblab/pi-telegram/api/updates.ts +16 -0
- package/node_modules/@llblab/pi-telegram/api/voice.ts +24 -0
- package/node_modules/@llblab/pi-telegram/docs/README.md +25 -0
- package/node_modules/@llblab/pi-telegram/docs/activity.md +304 -0
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +418 -0
- package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +48 -0
- package/node_modules/@llblab/pi-telegram/docs/command-templates.md +365 -0
- package/node_modules/@llblab/pi-telegram/docs/compact-matrix-literal.md +176 -0
- package/node_modules/@llblab/pi-telegram/docs/delivery.md +225 -0
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +310 -0
- package/node_modules/@llblab/pi-telegram/docs/inbound.md +136 -0
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +467 -0
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +193 -0
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +599 -0
- package/node_modules/@llblab/pi-telegram/docs/sections.md +452 -0
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +256 -0
- package/node_modules/@llblab/pi-telegram/docs/updates.md +171 -0
- package/node_modules/@llblab/pi-telegram/docs/voice.md +217 -0
- package/node_modules/@llblab/pi-telegram/index.ts +1336 -0
- package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +714 -0
- package/node_modules/@llblab/pi-telegram/lib/activity.ts +782 -0
- package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +121 -0
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +1129 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +379 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +1941 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +2119 -0
- package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +244 -0
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +2645 -0
- package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +954 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +1854 -0
- package/node_modules/@llblab/pi-telegram/lib/config.ts +1143 -0
- package/node_modules/@llblab/pi-telegram/lib/delivery.ts +891 -0
- package/node_modules/@llblab/pi-telegram/lib/generative-app-worker.mjs +104 -0
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +958 -0
- package/node_modules/@llblab/pi-telegram/lib/inbound.ts +824 -0
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +3140 -0
- package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +59 -0
- package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +623 -0
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +1422 -0
- package/node_modules/@llblab/pi-telegram/lib/logs.ts +393 -0
- package/node_modules/@llblab/pi-telegram/lib/media.ts +921 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +1182 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +926 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +876 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +274 -0
- package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +185 -0
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +867 -0
- package/node_modules/@llblab/pi-telegram/lib/model.ts +672 -0
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +981 -0
- package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +331 -0
- package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +515 -0
- package/node_modules/@llblab/pi-telegram/lib/outbound-voice.ts +285 -0
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +1007 -0
- package/node_modules/@llblab/pi-telegram/lib/ownership.ts +298 -0
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +127 -0
- package/node_modules/@llblab/pi-telegram/lib/pi.ts +197 -0
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +1588 -0
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +846 -0
- package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +158 -0
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +218 -0
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +3075 -0
- package/node_modules/@llblab/pi-telegram/lib/recovery.ts +464 -0
- package/node_modules/@llblab/pi-telegram/lib/rendering.ts +1213 -0
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +892 -0
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +2889 -0
- package/node_modules/@llblab/pi-telegram/lib/runtime.ts +628 -0
- package/node_modules/@llblab/pi-telegram/lib/sections.ts +710 -0
- package/node_modules/@llblab/pi-telegram/lib/setup.ts +220 -0
- package/node_modules/@llblab/pi-telegram/lib/skills.ts +21 -0
- package/node_modules/@llblab/pi-telegram/lib/status.ts +1550 -0
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +883 -0
- package/node_modules/@llblab/pi-telegram/lib/target.ts +49 -0
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +1747 -0
- package/node_modules/@llblab/pi-telegram/lib/text-groups.ts +487 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +1002 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +3183 -0
- package/node_modules/@llblab/pi-telegram/lib/time-injection.ts +78 -0
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +753 -0
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +4950 -0
- package/node_modules/@llblab/pi-telegram/lib/voice.ts +340 -0
- package/node_modules/@llblab/pi-telegram/package.json +83 -0
- package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
- package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +435 -0
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +241 -0
- package/node_modules/@llblab/pi-telegram/skills/generative-apps/SKILL.md +114 -0
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/SKILL.md +149 -0
- package/package.json +67 -0
|
@@ -0,0 +1,1525 @@
|
|
|
1
|
+
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ExtensionAPI,
|
|
6
|
+
ExtensionContext,
|
|
7
|
+
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
|
|
9
|
+
const CODEX_PROVIDER_ID = "openai-codex";
|
|
10
|
+
const CODEX_USAGE_EXTENSION_ID = "@llblab/pi-codex-usage";
|
|
11
|
+
const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
|
|
12
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
13
|
+
const SECOND_MS = 1000;
|
|
14
|
+
const MINUTE_MS = 60 * SECOND_MS;
|
|
15
|
+
const HOUR_MS = 60 * MINUTE_MS;
|
|
16
|
+
const HOUR_TENTH_MS = 6 * MINUTE_MS;
|
|
17
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
18
|
+
const DAY_TENTH_MS = 144 * MINUTE_MS;
|
|
19
|
+
const REFRESH_INTERVAL_MS = 30 * SECOND_MS;
|
|
20
|
+
const PROVISIONAL_RETRY_MS = SECOND_MS;
|
|
21
|
+
const FULL_AVAILABILITY_CONFIRMATION_MS = 15 * SECOND_MS;
|
|
22
|
+
const LOADING_FRAME_MS = 30;
|
|
23
|
+
const REDRAW_BLINK_MS = 150;
|
|
24
|
+
const STATUS_KEY = "aa-codex-usage";
|
|
25
|
+
const MAX_ERROR_BODY_CHARS = 600;
|
|
26
|
+
const DEFAULT_STATUS_LABEL_TEXT = "codex";
|
|
27
|
+
const SPARK_STATUS_LABEL_TEXT = "spark";
|
|
28
|
+
const CODEX_USAGE_LIMIT_ID = "codex";
|
|
29
|
+
const SPARK_USAGE_LIMIT_ID = "spark";
|
|
30
|
+
const SPARK_MODEL_KEY = "gpt-5.3-codex-spark";
|
|
31
|
+
const DUAL_BAR_WIDTH = 10;
|
|
32
|
+
const TELEGRAM_STATUS_IMPORT_SPECIFIERS = [
|
|
33
|
+
"@llblab/pi-telegram/status",
|
|
34
|
+
new URL("../pi-telegram/api/status.ts", import.meta.url).href,
|
|
35
|
+
];
|
|
36
|
+
const DUAL_BAR_CHARS = [
|
|
37
|
+
"⠀",
|
|
38
|
+
"▘",
|
|
39
|
+
"▝",
|
|
40
|
+
"▀",
|
|
41
|
+
"▖",
|
|
42
|
+
"▌",
|
|
43
|
+
"▞",
|
|
44
|
+
"▛",
|
|
45
|
+
"▗",
|
|
46
|
+
"▚",
|
|
47
|
+
"▐",
|
|
48
|
+
"▜",
|
|
49
|
+
"▄",
|
|
50
|
+
"▙",
|
|
51
|
+
"▟",
|
|
52
|
+
"█",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
type UsageSource = "pi-auth" | "codex-app-server";
|
|
56
|
+
type TimeoutHandle = ReturnType<typeof setTimeout> & { unref?: () => void };
|
|
57
|
+
type PiModel = NonNullable<ExtensionContext["model"]>;
|
|
58
|
+
export type CodexUsageModel = Pick<PiModel, "id" | "name" | "provider">;
|
|
59
|
+
type CodexUsageTelegramStatusModel = Pick<PiModel, "id" | "name" | "provider">;
|
|
60
|
+
type TelegramStatusLineProviderResult =
|
|
61
|
+
{ label: string; value: string } | undefined;
|
|
62
|
+
type TelegramStatusLineProvider = (ctx: {
|
|
63
|
+
activeModel?: CodexUsageTelegramStatusModel;
|
|
64
|
+
}) => TelegramStatusLineProviderResult;
|
|
65
|
+
type TelegramStatusLineModule = {
|
|
66
|
+
registerTelegramStatusLineProvider?: (
|
|
67
|
+
provider: TelegramStatusLineProvider,
|
|
68
|
+
options: { id: string },
|
|
69
|
+
) => () => void;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type QueryUsageOptions = {
|
|
73
|
+
timeoutMs: number;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type CachedReport = {
|
|
77
|
+
createdAt: number;
|
|
78
|
+
report: CodexUsageReport;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type QueryUsageResult =
|
|
82
|
+
| { ok: true; report: CodexUsageReport }
|
|
83
|
+
| { ok: false; errors: UsageQueryError[] };
|
|
84
|
+
|
|
85
|
+
export type UsageQueryError = {
|
|
86
|
+
source: UsageSource;
|
|
87
|
+
message: string;
|
|
88
|
+
cause?: unknown;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type CodexUsageReport = {
|
|
92
|
+
snapshots: NormalizedRateLimitSnapshot[];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type NormalizedRateLimitSnapshot = {
|
|
96
|
+
limitId: string;
|
|
97
|
+
primary?: NormalizedRateLimitWindow;
|
|
98
|
+
secondary?: NormalizedRateLimitWindow;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type NormalizedRateLimitWindow = {
|
|
102
|
+
usedPercent: number;
|
|
103
|
+
resetAt?: number;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
type RateLimitStatusPayload = {
|
|
107
|
+
rate_limit?: unknown;
|
|
108
|
+
additional_rate_limits?: unknown;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type BackendAdditionalRateLimit = {
|
|
112
|
+
limit_name?: unknown;
|
|
113
|
+
metered_feature?: unknown;
|
|
114
|
+
rate_limit?: unknown;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
type BackendRateLimitDetails = {
|
|
118
|
+
primary_window?: unknown;
|
|
119
|
+
secondary_window?: unknown;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
type BackendWindowSnapshot = {
|
|
123
|
+
used_percent?: unknown;
|
|
124
|
+
reset_at?: unknown;
|
|
125
|
+
resets_at?: unknown;
|
|
126
|
+
reset_time?: unknown;
|
|
127
|
+
end_time?: unknown;
|
|
128
|
+
ends_at?: unknown;
|
|
129
|
+
expires_at?: unknown;
|
|
130
|
+
reset_after_seconds?: unknown;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
type AppServerRateLimitResponse = {
|
|
134
|
+
rateLimits?: unknown;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
type AppServerRateLimitSnapshot = {
|
|
138
|
+
limitId?: unknown;
|
|
139
|
+
primary?: unknown;
|
|
140
|
+
secondary?: unknown;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
type AppServerWindowSnapshot = {
|
|
144
|
+
usedPercent?: unknown;
|
|
145
|
+
resetAt?: unknown;
|
|
146
|
+
resetsAt?: unknown;
|
|
147
|
+
resetTime?: unknown;
|
|
148
|
+
endTime?: unknown;
|
|
149
|
+
endsAt?: unknown;
|
|
150
|
+
expiresAt?: unknown;
|
|
151
|
+
resetAfterSeconds?: unknown;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
type RpcResponse = {
|
|
155
|
+
id?: unknown;
|
|
156
|
+
result?: unknown;
|
|
157
|
+
error?: { message?: unknown; code?: unknown };
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
type PendingRpc = {
|
|
161
|
+
resolve: (value: unknown) => void;
|
|
162
|
+
reject: (error: Error) => void;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export default function codexUsage(pi: ExtensionAPI) {
|
|
166
|
+
let cache: CachedReport | undefined;
|
|
167
|
+
let failedRefreshes = 0;
|
|
168
|
+
let inFlightUsageQuery:
|
|
169
|
+
{ limitId: string; promise: Promise<QueryUsageResult> } | undefined;
|
|
170
|
+
let statuslineBlinkTimer: TimeoutHandle | undefined;
|
|
171
|
+
let statuslineClearTimer: TimeoutHandle | undefined;
|
|
172
|
+
let statuslineCountdownTimer: TimeoutHandle | undefined;
|
|
173
|
+
let statuslineLoadingTimer: TimeoutHandle | undefined;
|
|
174
|
+
let statuslineRefreshTimer: TimeoutHandle | undefined;
|
|
175
|
+
let statuslineLoadingFrame = 0;
|
|
176
|
+
let statuslineLoadingLimitId: string | undefined;
|
|
177
|
+
let statuslineRequestId = 0;
|
|
178
|
+
let provisionalFullReport:
|
|
179
|
+
{ limitId: string; firstSeenAt: number } | undefined;
|
|
180
|
+
let unregisterTelegramStatusLine: (() => void) | undefined;
|
|
181
|
+
let telegramStatusLineRegistration: Promise<void> | undefined;
|
|
182
|
+
|
|
183
|
+
const ensureTelegramStatusLineRegistered = () => {
|
|
184
|
+
if (unregisterTelegramStatusLine || telegramStatusLineRegistration) return;
|
|
185
|
+
telegramStatusLineRegistration = registerCodexUsageTelegramStatusLine(
|
|
186
|
+
({ activeModel }) => {
|
|
187
|
+
if (!isOpenAICodexModel(activeModel)) return undefined;
|
|
188
|
+
if (!cache) return undefined;
|
|
189
|
+
const value = formatCodexUsageStatusValue(cache.report, activeModel);
|
|
190
|
+
return value
|
|
191
|
+
? { label: activeUsageLabel(activeModel), value }
|
|
192
|
+
: undefined;
|
|
193
|
+
},
|
|
194
|
+
)
|
|
195
|
+
.then((unregister) => {
|
|
196
|
+
unregisterTelegramStatusLine = unregister;
|
|
197
|
+
})
|
|
198
|
+
.finally(() => {
|
|
199
|
+
telegramStatusLineRegistration = undefined;
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const clearStatuslineTimers = () => {
|
|
204
|
+
if (statuslineBlinkTimer) clearTimeout(statuslineBlinkTimer);
|
|
205
|
+
if (statuslineClearTimer) clearTimeout(statuslineClearTimer);
|
|
206
|
+
if (statuslineCountdownTimer) clearTimeout(statuslineCountdownTimer);
|
|
207
|
+
if (statuslineLoadingTimer) clearTimeout(statuslineLoadingTimer);
|
|
208
|
+
if (statuslineRefreshTimer) clearTimeout(statuslineRefreshTimer);
|
|
209
|
+
statuslineBlinkTimer = undefined;
|
|
210
|
+
statuslineClearTimer = undefined;
|
|
211
|
+
statuslineCountdownTimer = undefined;
|
|
212
|
+
statuslineLoadingTimer = undefined;
|
|
213
|
+
statuslineLoadingLimitId = undefined;
|
|
214
|
+
statuslineRefreshTimer = undefined;
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const stopStatuslineLoading = () => {
|
|
218
|
+
if (statuslineLoadingTimer) clearTimeout(statuslineLoadingTimer);
|
|
219
|
+
statuslineLoadingTimer = undefined;
|
|
220
|
+
statuslineLoadingLimitId = undefined;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const startStatuslineLoading = (
|
|
224
|
+
ctx: ExtensionContext,
|
|
225
|
+
model: CodexUsageModel | undefined,
|
|
226
|
+
) => {
|
|
227
|
+
const limitId = activeUsageLimitId(model);
|
|
228
|
+
if (statuslineLoadingTimer && statuslineLoadingLimitId === limitId) return;
|
|
229
|
+
stopStatuslineLoading();
|
|
230
|
+
statuslineLoadingFrame = Math.random() < 0.5 ? 0 : DUAL_BAR_WIDTH * 2 - 1;
|
|
231
|
+
statuslineLoadingLimitId = limitId;
|
|
232
|
+
const drawNextFrame = () => {
|
|
233
|
+
try {
|
|
234
|
+
ctx.ui.setStatus(
|
|
235
|
+
STATUS_KEY,
|
|
236
|
+
formatStatuslineLoading(ctx, statuslineLoadingFrame, model),
|
|
237
|
+
);
|
|
238
|
+
statuslineLoadingFrame += 1;
|
|
239
|
+
statuslineLoadingTimer = setTimeout(
|
|
240
|
+
drawNextFrame,
|
|
241
|
+
LOADING_FRAME_MS,
|
|
242
|
+
) as TimeoutHandle;
|
|
243
|
+
statuslineLoadingTimer.unref?.();
|
|
244
|
+
} catch (error) {
|
|
245
|
+
statuslineLoadingTimer = undefined;
|
|
246
|
+
handleTimerError(error);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
drawNextFrame();
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const clearUsageStatusline = (ctx: ExtensionContext) => {
|
|
253
|
+
statuslineRequestId += 1;
|
|
254
|
+
clearStatuslineTimers();
|
|
255
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const scheduleTemporaryStatuslineClear = (ctx: ExtensionContext) => {
|
|
259
|
+
if (statuslineClearTimer) clearTimeout(statuslineClearTimer);
|
|
260
|
+
statuslineClearTimer = setTimeout(() => {
|
|
261
|
+
try {
|
|
262
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
263
|
+
statuslineClearTimer = undefined;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
handleTimerError(error);
|
|
266
|
+
}
|
|
267
|
+
}, REFRESH_INTERVAL_MS) as TimeoutHandle;
|
|
268
|
+
statuslineClearTimer.unref?.();
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const scheduleStatuslineRefresh = (
|
|
272
|
+
ctx: ExtensionContext,
|
|
273
|
+
delayMs = REFRESH_INTERVAL_MS,
|
|
274
|
+
) => {
|
|
275
|
+
if (statuslineRefreshTimer) clearTimeout(statuslineRefreshTimer);
|
|
276
|
+
statuslineRefreshTimer = setTimeout(() => {
|
|
277
|
+
void refreshCurrentCodexUsageStatusline(ctx, true).catch(
|
|
278
|
+
handleAsyncTimerError,
|
|
279
|
+
);
|
|
280
|
+
}, delayMs) as TimeoutHandle;
|
|
281
|
+
statuslineRefreshTimer.unref?.();
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const scheduleStatuslineCountdown = (
|
|
285
|
+
ctx: ExtensionContext,
|
|
286
|
+
report: CodexUsageReport,
|
|
287
|
+
model: CodexUsageModel | undefined,
|
|
288
|
+
) => {
|
|
289
|
+
if (statuslineCountdownTimer) clearTimeout(statuslineCountdownTimer);
|
|
290
|
+
const delayMs = nextResetCountdownDelayMs(report, Date.now(), model);
|
|
291
|
+
if (delayMs === undefined) {
|
|
292
|
+
statuslineCountdownTimer = undefined;
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
statuslineCountdownTimer = setTimeout(() => {
|
|
296
|
+
try {
|
|
297
|
+
if (isOpenAICodexModel(ctx.model)) {
|
|
298
|
+
ctx.ui.setStatus(
|
|
299
|
+
STATUS_KEY,
|
|
300
|
+
formatCodexUsageStatusline(report, ctx, model),
|
|
301
|
+
);
|
|
302
|
+
scheduleStatuslineCountdown(ctx, report, model);
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
handleTimerError(error);
|
|
306
|
+
}
|
|
307
|
+
}, delayMs) as TimeoutHandle;
|
|
308
|
+
statuslineCountdownTimer.unref?.();
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const setUsageStatusline = (
|
|
312
|
+
ctx: ExtensionContext,
|
|
313
|
+
report: CodexUsageReport,
|
|
314
|
+
options: {
|
|
315
|
+
autoRefresh: boolean;
|
|
316
|
+
blink: boolean;
|
|
317
|
+
model: CodexUsageModel | undefined;
|
|
318
|
+
},
|
|
319
|
+
) => {
|
|
320
|
+
if (statuslineBlinkTimer) clearTimeout(statuslineBlinkTimer);
|
|
321
|
+
if (statuslineClearTimer) clearTimeout(statuslineClearTimer);
|
|
322
|
+
if (statuslineCountdownTimer) clearTimeout(statuslineCountdownTimer);
|
|
323
|
+
stopStatuslineLoading();
|
|
324
|
+
statuslineBlinkTimer = undefined;
|
|
325
|
+
statuslineClearTimer = undefined;
|
|
326
|
+
statuslineCountdownTimer = undefined;
|
|
327
|
+
const text = formatCodexUsageStatusline(report, ctx, options.model);
|
|
328
|
+
if (options.blink) {
|
|
329
|
+
ctx.ui.setStatus(
|
|
330
|
+
STATUS_KEY,
|
|
331
|
+
formatStatuslineLoading(ctx, statuslineLoadingFrame, options.model),
|
|
332
|
+
);
|
|
333
|
+
statuslineBlinkTimer = setTimeout(() => {
|
|
334
|
+
try {
|
|
335
|
+
ctx.ui.setStatus(STATUS_KEY, text);
|
|
336
|
+
scheduleStatuslineCountdown(ctx, report, options.model);
|
|
337
|
+
statuslineBlinkTimer = undefined;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
handleTimerError(error);
|
|
340
|
+
}
|
|
341
|
+
}, REDRAW_BLINK_MS) as TimeoutHandle;
|
|
342
|
+
statuslineBlinkTimer.unref?.();
|
|
343
|
+
} else {
|
|
344
|
+
ctx.ui.setStatus(STATUS_KEY, text);
|
|
345
|
+
scheduleStatuslineCountdown(ctx, report, options.model);
|
|
346
|
+
}
|
|
347
|
+
if (options.autoRefresh) scheduleStatuslineRefresh(ctx);
|
|
348
|
+
else scheduleTemporaryStatuslineClear(ctx);
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const queryCurrentUsage = (
|
|
352
|
+
ctx: ExtensionContext,
|
|
353
|
+
model: CodexUsageModel | undefined,
|
|
354
|
+
) => {
|
|
355
|
+
const limitId = activeUsageLimitId(model);
|
|
356
|
+
if (inFlightUsageQuery?.limitId === limitId)
|
|
357
|
+
return inFlightUsageQuery.promise;
|
|
358
|
+
const promise = queryUsage(
|
|
359
|
+
ctx,
|
|
360
|
+
{ timeoutMs: DEFAULT_TIMEOUT_MS },
|
|
361
|
+
model,
|
|
362
|
+
).finally(() => {
|
|
363
|
+
if (inFlightUsageQuery?.promise === promise)
|
|
364
|
+
inFlightUsageQuery = undefined;
|
|
365
|
+
});
|
|
366
|
+
inFlightUsageQuery = { limitId, promise };
|
|
367
|
+
return promise;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const refreshCurrentCodexUsageStatusline = async (
|
|
371
|
+
ctx: ExtensionContext,
|
|
372
|
+
force: boolean,
|
|
373
|
+
model?: CodexUsageModel,
|
|
374
|
+
) => {
|
|
375
|
+
try {
|
|
376
|
+
const activeModel = model ?? ctx.model;
|
|
377
|
+
if (!isOpenAICodexModel(activeModel)) {
|
|
378
|
+
clearUsageStatusline(ctx);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const usableCache =
|
|
383
|
+
cache && canReuseCachedReport(cache.report, activeModel)
|
|
384
|
+
? cache
|
|
385
|
+
: undefined;
|
|
386
|
+
if (usableCache) {
|
|
387
|
+
setUsageStatusline(ctx, usableCache.report, {
|
|
388
|
+
autoRefresh: true,
|
|
389
|
+
blink: false,
|
|
390
|
+
model: activeModel,
|
|
391
|
+
});
|
|
392
|
+
} else {
|
|
393
|
+
startStatuslineLoading(ctx, activeModel);
|
|
394
|
+
}
|
|
395
|
+
const requestId = statuslineRequestId + 1;
|
|
396
|
+
statuslineRequestId = requestId;
|
|
397
|
+
const freshCache =
|
|
398
|
+
usableCache && Date.now() - usableCache.createdAt < REFRESH_INTERVAL_MS
|
|
399
|
+
? usableCache
|
|
400
|
+
: undefined;
|
|
401
|
+
if (freshCache && !force) {
|
|
402
|
+
setUsageStatusline(ctx, freshCache.report, {
|
|
403
|
+
autoRefresh: true,
|
|
404
|
+
blink: false,
|
|
405
|
+
model: activeModel,
|
|
406
|
+
});
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const result = await queryCurrentUsage(ctx, activeModel);
|
|
411
|
+
if (requestId !== statuslineRequestId) return;
|
|
412
|
+
if (!isOpenAICodexModel(ctx.model)) {
|
|
413
|
+
clearUsageStatusline(ctx);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!result.ok) {
|
|
418
|
+
failedRefreshes += 1;
|
|
419
|
+
const activeCache =
|
|
420
|
+
cache && canReuseCachedReport(cache.report, activeModel)
|
|
421
|
+
? cache
|
|
422
|
+
: undefined;
|
|
423
|
+
if (!activeCache || failedRefreshes >= 5) {
|
|
424
|
+
stopStatuslineLoading();
|
|
425
|
+
ctx.ui.setStatus(
|
|
426
|
+
STATUS_KEY,
|
|
427
|
+
formatStatuslineProblem(ctx, result.errors, activeModel),
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
scheduleStatuslineRefresh(ctx);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const previousReport = cache?.report;
|
|
435
|
+
const previousWasFullyAvailable = previousReport
|
|
436
|
+
? isFullyAvailableReport(previousReport, activeModel)
|
|
437
|
+
: false;
|
|
438
|
+
if (
|
|
439
|
+
isFullyAvailableReport(result.report, activeModel) &&
|
|
440
|
+
!previousWasFullyAvailable
|
|
441
|
+
) {
|
|
442
|
+
const now = Date.now();
|
|
443
|
+
const limitId = activeUsageLimitId(activeModel);
|
|
444
|
+
if (provisionalFullReport?.limitId !== limitId) {
|
|
445
|
+
provisionalFullReport = { limitId, firstSeenAt: now };
|
|
446
|
+
}
|
|
447
|
+
if (
|
|
448
|
+
now - provisionalFullReport.firstSeenAt <
|
|
449
|
+
FULL_AVAILABILITY_CONFIRMATION_MS
|
|
450
|
+
) {
|
|
451
|
+
scheduleStatuslineRefresh(ctx, PROVISIONAL_RETRY_MS);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
provisionalFullReport = undefined;
|
|
456
|
+
}
|
|
457
|
+
const blink = previousReport
|
|
458
|
+
? formatReportBar(previousReport, activeModel) !==
|
|
459
|
+
formatReportBar(result.report, activeModel)
|
|
460
|
+
: false;
|
|
461
|
+
failedRefreshes = 0;
|
|
462
|
+
provisionalFullReport = undefined;
|
|
463
|
+
cache = { createdAt: Date.now(), report: result.report };
|
|
464
|
+
setUsageStatusline(ctx, result.report, {
|
|
465
|
+
autoRefresh: true,
|
|
466
|
+
blink,
|
|
467
|
+
model: activeModel,
|
|
468
|
+
});
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (isStaleExtensionContextError(error)) {
|
|
471
|
+
clearStatuslineTimers();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
throw error;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
ensureTelegramStatusLineRegistered();
|
|
479
|
+
|
|
480
|
+
pi.on("session_start", (_event, ctx) => {
|
|
481
|
+
ensureTelegramStatusLineRegistered();
|
|
482
|
+
if (isOpenAICodexModel(ctx.model))
|
|
483
|
+
void refreshCurrentCodexUsageStatusline(ctx, false).catch(
|
|
484
|
+
handleAsyncTimerError,
|
|
485
|
+
);
|
|
486
|
+
else clearUsageStatusline(ctx);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
490
|
+
if (isOpenAICodexModel(ctx.model))
|
|
491
|
+
void refreshCurrentCodexUsageStatusline(ctx, false).catch(
|
|
492
|
+
handleAsyncTimerError,
|
|
493
|
+
);
|
|
494
|
+
else clearUsageStatusline(ctx);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
pi.on("model_select", (event, ctx) => {
|
|
498
|
+
if (isOpenAICodexModel(event.model)) {
|
|
499
|
+
void refreshCurrentCodexUsageStatusline(ctx, false, event.model).catch(
|
|
500
|
+
handleAsyncTimerError,
|
|
501
|
+
);
|
|
502
|
+
} else {
|
|
503
|
+
clearUsageStatusline(ctx);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
508
|
+
clearUsageStatusline(ctx);
|
|
509
|
+
unregisterTelegramStatusLine?.();
|
|
510
|
+
unregisterTelegramStatusLine = undefined;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function handleAsyncTimerError(error: unknown): void {
|
|
515
|
+
handleTimerError(error);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function handleTimerError(error: unknown): void {
|
|
519
|
+
if (isStaleExtensionContextError(error)) return;
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export function isStaleExtensionContextError(error: unknown): boolean {
|
|
524
|
+
return error instanceof Error && error.message.includes("ctx is stale");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function isOpenAICodexModel(
|
|
528
|
+
model: Pick<PiModel, "provider"> | undefined,
|
|
529
|
+
): boolean {
|
|
530
|
+
return model?.provider === CODEX_PROVIDER_ID;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function isSparkCodexModel(
|
|
534
|
+
model: Pick<PiModel, "id" | "name" | "provider"> | undefined,
|
|
535
|
+
): boolean {
|
|
536
|
+
if (!isOpenAICodexModel(model)) return false;
|
|
537
|
+
const key = `${model?.id ?? ""} ${model?.name ?? ""}`.toLowerCase();
|
|
538
|
+
return key.includes(SPARK_MODEL_KEY);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function activeUsageLimitId(
|
|
542
|
+
model: Pick<PiModel, "id" | "name" | "provider"> | undefined,
|
|
543
|
+
): string {
|
|
544
|
+
return isSparkCodexModel(model) ? SPARK_USAGE_LIMIT_ID : CODEX_USAGE_LIMIT_ID;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function activeUsageLabel(
|
|
548
|
+
model: Pick<PiModel, "id" | "name" | "provider"> | undefined,
|
|
549
|
+
): string {
|
|
550
|
+
return isSparkCodexModel(model)
|
|
551
|
+
? SPARK_STATUS_LABEL_TEXT
|
|
552
|
+
: DEFAULT_STATUS_LABEL_TEXT;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function importTelegramStatusLineModule(): Promise<
|
|
556
|
+
TelegramStatusLineModule | undefined
|
|
557
|
+
> {
|
|
558
|
+
for (const specifier of TELEGRAM_STATUS_IMPORT_SPECIFIERS) {
|
|
559
|
+
try {
|
|
560
|
+
const imported = (await import(specifier)) as TelegramStatusLineModule;
|
|
561
|
+
if (typeof imported.registerTelegramStatusLineProvider === "function") {
|
|
562
|
+
return imported;
|
|
563
|
+
}
|
|
564
|
+
} catch {
|
|
565
|
+
// pi-telegram is optional; absence just disables the Telegram status line.
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
async function registerCodexUsageTelegramStatusLine(
|
|
572
|
+
provider: TelegramStatusLineProvider,
|
|
573
|
+
): Promise<(() => void) | undefined> {
|
|
574
|
+
const telegramStatus = await importTelegramStatusLineModule();
|
|
575
|
+
return telegramStatus?.registerTelegramStatusLineProvider?.(provider, {
|
|
576
|
+
id: CODEX_USAGE_EXTENSION_ID,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
async function queryUsage(
|
|
581
|
+
ctx: ExtensionContext,
|
|
582
|
+
options: Pick<QueryUsageOptions, "timeoutMs">,
|
|
583
|
+
model: CodexUsageModel | undefined,
|
|
584
|
+
): Promise<QueryUsageResult> {
|
|
585
|
+
const errors: UsageQueryError[] = [];
|
|
586
|
+
const sources = isSparkCodexModel(model)
|
|
587
|
+
? (["codex-app-server", "pi-auth"] as const)
|
|
588
|
+
: (["pi-auth", "codex-app-server"] as const);
|
|
589
|
+
|
|
590
|
+
for (const source of sources) {
|
|
591
|
+
try {
|
|
592
|
+
const report =
|
|
593
|
+
source === "pi-auth"
|
|
594
|
+
? await queryViaPiAuth(ctx, options.timeoutMs)
|
|
595
|
+
: await queryViaCodexAppServer(options.timeoutMs);
|
|
596
|
+
if (selectUsageSnapshot(report, activeUsageLimitId(model))) {
|
|
597
|
+
return { ok: true, report };
|
|
598
|
+
}
|
|
599
|
+
errors.push({
|
|
600
|
+
source,
|
|
601
|
+
message: `${source} returned no displayable ${activeUsageLabel(model)} rate-limit windows`,
|
|
602
|
+
});
|
|
603
|
+
} catch (cause) {
|
|
604
|
+
errors.push({ source, message: errorMessage(cause), cause });
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return { ok: false, errors };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async function queryViaPiAuth(
|
|
612
|
+
ctx: ExtensionContext,
|
|
613
|
+
timeoutMs: number,
|
|
614
|
+
): Promise<CodexUsageReport> {
|
|
615
|
+
const auth = await resolvePiCodexAuth(ctx);
|
|
616
|
+
if (!auth) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
"No Pi OpenAI Codex subscription auth was available. Use a Pi OpenAI Codex model or run /login for OpenAI ChatGPT Plus/Pro (Codex).",
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const response = await fetchWithTimeout(
|
|
623
|
+
CODEX_USAGE_URL,
|
|
624
|
+
{ headers: auth.headers },
|
|
625
|
+
timeoutMs,
|
|
626
|
+
);
|
|
627
|
+
const text = await response.text();
|
|
628
|
+
if (!response.ok) {
|
|
629
|
+
throw new Error(
|
|
630
|
+
`Codex usage endpoint returned ${response.status} ${response.statusText}: ${redactErrorBody(text)}`,
|
|
631
|
+
);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const payload = parseJsonObject(text, "Codex usage endpoint response");
|
|
635
|
+
return normalizeBackendPayload(
|
|
636
|
+
payload as RateLimitStatusPayload,
|
|
637
|
+
Date.now(),
|
|
638
|
+
"pi-auth",
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function resolvePiCodexAuth(
|
|
643
|
+
ctx: ExtensionContext,
|
|
644
|
+
): Promise<{ headers: Record<string, string> } | undefined> {
|
|
645
|
+
const models = codexAuthCandidateModels(ctx);
|
|
646
|
+
const errors: string[] = [];
|
|
647
|
+
|
|
648
|
+
for (const model of models) {
|
|
649
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
650
|
+
if (!auth.ok) {
|
|
651
|
+
errors.push(auth.error);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const headers = { ...(auth.headers ?? {}) };
|
|
656
|
+
if (!hasHeader(headers, "Authorization") && auth.apiKey) {
|
|
657
|
+
headers.Authorization = `Bearer ${auth.apiKey}`;
|
|
658
|
+
}
|
|
659
|
+
if (!hasHeader(headers, "User-Agent")) {
|
|
660
|
+
headers["User-Agent"] = "pi-codex-usage";
|
|
661
|
+
}
|
|
662
|
+
if (hasHeader(headers, "Authorization")) {
|
|
663
|
+
return { headers };
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (errors.length > 0) {
|
|
668
|
+
throw new Error(errors.join("; "));
|
|
669
|
+
}
|
|
670
|
+
return undefined;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function codexAuthCandidateModels(ctx: ExtensionContext): PiModel[] {
|
|
674
|
+
const candidates: PiModel[] = [];
|
|
675
|
+
const seen = new Set<string>();
|
|
676
|
+
const add = (model: PiModel | undefined) => {
|
|
677
|
+
if (!model || model.provider !== CODEX_PROVIDER_ID) return;
|
|
678
|
+
const key = `${model.provider}/${model.id}`;
|
|
679
|
+
if (seen.has(key)) return;
|
|
680
|
+
seen.add(key);
|
|
681
|
+
candidates.push(model);
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
add(ctx.model);
|
|
685
|
+
for (const model of ctx.modelRegistry.getAvailable()) add(model);
|
|
686
|
+
for (const model of ctx.modelRegistry.getAll()) add(model);
|
|
687
|
+
return candidates;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
async function fetchWithTimeout(
|
|
691
|
+
url: string,
|
|
692
|
+
init: RequestInit,
|
|
693
|
+
timeoutMs: number,
|
|
694
|
+
): Promise<Response> {
|
|
695
|
+
const controller = new AbortController();
|
|
696
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
697
|
+
try {
|
|
698
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
699
|
+
} catch (error) {
|
|
700
|
+
if (controller.signal.aborted) {
|
|
701
|
+
throw new Error(
|
|
702
|
+
`Timed out after ${Math.round(timeoutMs / 1000)}s while fetching Codex usage.`,
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
throw error;
|
|
706
|
+
} finally {
|
|
707
|
+
clearTimeout(timeout);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
async function queryViaCodexAppServer(
|
|
712
|
+
timeoutMs: number,
|
|
713
|
+
): Promise<CodexUsageReport> {
|
|
714
|
+
const client = new CodexAppServerClient(timeoutMs);
|
|
715
|
+
try {
|
|
716
|
+
await client.start();
|
|
717
|
+
await client.request("initialize", {
|
|
718
|
+
clientInfo: {
|
|
719
|
+
name: "pi_codex_usage",
|
|
720
|
+
title: "Pi Codex Usage",
|
|
721
|
+
version: "0.1.0",
|
|
722
|
+
},
|
|
723
|
+
capabilities: {
|
|
724
|
+
experimentalApi: false,
|
|
725
|
+
requestAttestation: false,
|
|
726
|
+
optOutNotificationMethods: [],
|
|
727
|
+
},
|
|
728
|
+
});
|
|
729
|
+
client.notify("initialized");
|
|
730
|
+
const result = await client.request("account/rateLimits/read", undefined);
|
|
731
|
+
return normalizeAppServerResponse(
|
|
732
|
+
assertObject(
|
|
733
|
+
result,
|
|
734
|
+
"account/rateLimits/read result",
|
|
735
|
+
) as AppServerRateLimitResponse,
|
|
736
|
+
Date.now(),
|
|
737
|
+
);
|
|
738
|
+
} finally {
|
|
739
|
+
client.dispose();
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
class CodexAppServerClient {
|
|
744
|
+
private child?: ChildProcessWithoutNullStreams;
|
|
745
|
+
private nextId = 1;
|
|
746
|
+
private stderr = "";
|
|
747
|
+
private readonly pending = new Map<number, PendingRpc>();
|
|
748
|
+
private startPromise?: Promise<void>;
|
|
749
|
+
private exitError?: Error;
|
|
750
|
+
private readonly timeoutMs: number;
|
|
751
|
+
|
|
752
|
+
constructor(timeoutMs: number) {
|
|
753
|
+
this.timeoutMs = timeoutMs;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
start(): Promise<void> {
|
|
757
|
+
if (this.startPromise) return this.startPromise;
|
|
758
|
+
|
|
759
|
+
this.startPromise = new Promise((resolve, reject) => {
|
|
760
|
+
const child = spawn("codex", ["app-server", "--listen", "stdio://"], {
|
|
761
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
762
|
+
});
|
|
763
|
+
this.child = child;
|
|
764
|
+
|
|
765
|
+
const startupTimeout = setTimeout(() => {
|
|
766
|
+
reject(
|
|
767
|
+
new Error(
|
|
768
|
+
`Timed out after ${Math.round(this.timeoutMs / 1000)}s starting codex app-server.`,
|
|
769
|
+
),
|
|
770
|
+
);
|
|
771
|
+
}, this.timeoutMs);
|
|
772
|
+
|
|
773
|
+
child.once("spawn", () => {
|
|
774
|
+
clearTimeout(startupTimeout);
|
|
775
|
+
resolve();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
child.once("error", (error) => {
|
|
779
|
+
clearTimeout(startupTimeout);
|
|
780
|
+
reject(new Error(`Failed to start codex app-server: ${error.message}`));
|
|
781
|
+
this.rejectAll(error);
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
child.once("exit", (code, signal) => {
|
|
785
|
+
const suffix = this.stderr
|
|
786
|
+
? ` stderr: ${redactErrorBody(this.stderr)}`
|
|
787
|
+
: "";
|
|
788
|
+
this.exitError = new Error(
|
|
789
|
+
`codex app-server exited before completing the request (code ${code ?? "unknown"}, signal ${signal ?? "none"}).${suffix}`,
|
|
790
|
+
);
|
|
791
|
+
this.rejectAll(this.exitError);
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
child.stderr.setEncoding("utf8");
|
|
795
|
+
child.stderr.on("data", (chunk: string) => {
|
|
796
|
+
this.stderr = truncateEnd(this.stderr + chunk, MAX_ERROR_BODY_CHARS);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
const lines = createInterface({ input: child.stdout });
|
|
800
|
+
lines.on("line", (line) => this.handleLine(line));
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
return this.startPromise;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
request(method: string, params: unknown): Promise<unknown> {
|
|
807
|
+
const child = this.child;
|
|
808
|
+
if (!child?.stdin.writable) {
|
|
809
|
+
throw new Error("codex app-server is not running.");
|
|
810
|
+
}
|
|
811
|
+
if (this.exitError) throw this.exitError;
|
|
812
|
+
|
|
813
|
+
const id = this.nextId++;
|
|
814
|
+
const payload =
|
|
815
|
+
params === undefined ? { method, id } : { method, id, params };
|
|
816
|
+
const response = new Promise<unknown>((resolve, reject) => {
|
|
817
|
+
const timeout = setTimeout(() => {
|
|
818
|
+
this.pending.delete(id);
|
|
819
|
+
reject(
|
|
820
|
+
new Error(
|
|
821
|
+
`Timed out after ${Math.round(this.timeoutMs / 1000)}s waiting for ${method}.`,
|
|
822
|
+
),
|
|
823
|
+
);
|
|
824
|
+
}, this.timeoutMs);
|
|
825
|
+
|
|
826
|
+
this.pending.set(id, {
|
|
827
|
+
resolve: (value) => {
|
|
828
|
+
clearTimeout(timeout);
|
|
829
|
+
resolve(value);
|
|
830
|
+
},
|
|
831
|
+
reject: (error) => {
|
|
832
|
+
clearTimeout(timeout);
|
|
833
|
+
reject(error);
|
|
834
|
+
},
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
child.stdin.write(`${JSON.stringify(payload)}\n`);
|
|
839
|
+
return response;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
notify(method: string): void {
|
|
843
|
+
const child = this.child;
|
|
844
|
+
if (!child?.stdin.writable) return;
|
|
845
|
+
child.stdin.write(`${JSON.stringify({ method })}\n`);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
dispose(): void {
|
|
849
|
+
for (const [id, pending] of this.pending) {
|
|
850
|
+
pending.reject(new Error(`codex app-server request ${id} cancelled.`));
|
|
851
|
+
}
|
|
852
|
+
this.pending.clear();
|
|
853
|
+
|
|
854
|
+
const child = this.child;
|
|
855
|
+
if (!child) return;
|
|
856
|
+
child.stdin.end();
|
|
857
|
+
if (!child.killed) child.kill();
|
|
858
|
+
this.child = undefined;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
private handleLine(line: string): void {
|
|
862
|
+
let parsed: RpcResponse;
|
|
863
|
+
try {
|
|
864
|
+
parsed = JSON.parse(line) as RpcResponse;
|
|
865
|
+
} catch {
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (typeof parsed.id !== "number") return;
|
|
870
|
+
const pending = this.pending.get(parsed.id);
|
|
871
|
+
if (!pending) return;
|
|
872
|
+
this.pending.delete(parsed.id);
|
|
873
|
+
|
|
874
|
+
if (parsed.error) {
|
|
875
|
+
const message =
|
|
876
|
+
typeof parsed.error.message === "string"
|
|
877
|
+
? parsed.error.message
|
|
878
|
+
: "unknown error";
|
|
879
|
+
pending.reject(new Error(`codex app-server request failed: ${message}`));
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
pending.resolve(parsed.result);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
private rejectAll(error: Error): void {
|
|
887
|
+
for (const pending of this.pending.values()) pending.reject(error);
|
|
888
|
+
this.pending.clear();
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export function normalizeBackendPayload(
|
|
893
|
+
payload: RateLimitStatusPayload,
|
|
894
|
+
_capturedAt: number,
|
|
895
|
+
_source: UsageSource,
|
|
896
|
+
): CodexUsageReport {
|
|
897
|
+
const snapshots: NormalizedRateLimitSnapshot[] = [];
|
|
898
|
+
const primarySnapshot = normalizeBackendSnapshot(
|
|
899
|
+
CODEX_USAGE_LIMIT_ID,
|
|
900
|
+
payload.rate_limit,
|
|
901
|
+
_capturedAt,
|
|
902
|
+
);
|
|
903
|
+
if (primarySnapshot) snapshots.push(primarySnapshot);
|
|
904
|
+
|
|
905
|
+
if (Array.isArray(payload.additional_rate_limits)) {
|
|
906
|
+
for (const item of payload.additional_rate_limits) {
|
|
907
|
+
const additional = assertObject(
|
|
908
|
+
item,
|
|
909
|
+
"additional rate limit",
|
|
910
|
+
) as BackendAdditionalRateLimit;
|
|
911
|
+
const snapshot = normalizeBackendSnapshot(
|
|
912
|
+
backendAdditionalLimitId(additional),
|
|
913
|
+
additional.rate_limit,
|
|
914
|
+
_capturedAt,
|
|
915
|
+
);
|
|
916
|
+
if (snapshot) snapshots.push(snapshot);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (snapshots.length === 0) {
|
|
921
|
+
throw new Error(
|
|
922
|
+
"Codex usage endpoint returned no displayable rate-limit windows.",
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
return { snapshots };
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function backendAdditionalLimitId(limit: BackendAdditionalRateLimit): string {
|
|
929
|
+
const raw = `${asString(limit.limit_name) ?? ""} ${asString(limit.metered_feature) ?? ""}`;
|
|
930
|
+
return raw.toLowerCase().includes("spark")
|
|
931
|
+
? SPARK_USAGE_LIMIT_ID
|
|
932
|
+
: (normalizedUsageKey(raw) ?? CODEX_USAGE_LIMIT_ID);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function normalizeBackendSnapshot(
|
|
936
|
+
limitId: string,
|
|
937
|
+
rateLimit: unknown,
|
|
938
|
+
capturedAt: number,
|
|
939
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
940
|
+
if (rateLimit === null || rateLimit === undefined) return undefined;
|
|
941
|
+
const details = assertObject(
|
|
942
|
+
rateLimit,
|
|
943
|
+
"rate limit",
|
|
944
|
+
) as BackendRateLimitDetails;
|
|
945
|
+
const primary = normalizeBackendWindow(details.primary_window, capturedAt);
|
|
946
|
+
const secondary = normalizeBackendWindow(
|
|
947
|
+
details.secondary_window,
|
|
948
|
+
capturedAt,
|
|
949
|
+
);
|
|
950
|
+
if (!primary && !secondary) return undefined;
|
|
951
|
+
return { limitId, primary, secondary };
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
function normalizeBackendWindow(
|
|
955
|
+
value: unknown,
|
|
956
|
+
capturedAt: number,
|
|
957
|
+
): NormalizedRateLimitWindow | undefined {
|
|
958
|
+
if (value === null || value === undefined) return undefined;
|
|
959
|
+
const window = assertObject(
|
|
960
|
+
value,
|
|
961
|
+
"rate-limit window",
|
|
962
|
+
) as BackendWindowSnapshot;
|
|
963
|
+
const usedPercent = asNumber(window.used_percent);
|
|
964
|
+
if (usedPercent === undefined) return undefined;
|
|
965
|
+
const resetAt = asResetTime(
|
|
966
|
+
[
|
|
967
|
+
window.reset_at,
|
|
968
|
+
window.resets_at,
|
|
969
|
+
window.reset_time,
|
|
970
|
+
window.end_time,
|
|
971
|
+
window.ends_at,
|
|
972
|
+
window.expires_at,
|
|
973
|
+
],
|
|
974
|
+
window.reset_after_seconds,
|
|
975
|
+
capturedAt,
|
|
976
|
+
);
|
|
977
|
+
return resetAt === undefined ? { usedPercent } : { usedPercent, resetAt };
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
export function normalizeAppServerResponse(
|
|
981
|
+
response: AppServerRateLimitResponse,
|
|
982
|
+
_capturedAt: number,
|
|
983
|
+
): CodexUsageReport {
|
|
984
|
+
const snapshots: NormalizedRateLimitSnapshot[] = [];
|
|
985
|
+
const addSnapshot = (raw: unknown, fallbackId: string) => {
|
|
986
|
+
const snapshot = normalizeAppServerSnapshot(raw, fallbackId, _capturedAt);
|
|
987
|
+
if (!snapshot) return;
|
|
988
|
+
const existingIndex = snapshots.findIndex(
|
|
989
|
+
(item) => item.limitId === snapshot.limitId,
|
|
990
|
+
);
|
|
991
|
+
if (existingIndex >= 0)
|
|
992
|
+
snapshots[existingIndex] = mergeSnapshot(
|
|
993
|
+
snapshots[existingIndex],
|
|
994
|
+
snapshot,
|
|
995
|
+
);
|
|
996
|
+
else snapshots.push(snapshot);
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
if (Array.isArray(response.rateLimits)) {
|
|
1000
|
+
for (const item of response.rateLimits) addSnapshot(item, "codex");
|
|
1001
|
+
} else {
|
|
1002
|
+
addSnapshot(response.rateLimits, "codex");
|
|
1003
|
+
}
|
|
1004
|
+
if (snapshots.length === 0) {
|
|
1005
|
+
throw new Error(
|
|
1006
|
+
"codex app-server returned no displayable rate-limit windows.",
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
return { snapshots };
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function normalizeAppServerSnapshot(
|
|
1014
|
+
raw: unknown,
|
|
1015
|
+
fallbackId: string,
|
|
1016
|
+
capturedAt: number,
|
|
1017
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
1018
|
+
if (raw === null || raw === undefined) return undefined;
|
|
1019
|
+
const snapshot = assertObject(
|
|
1020
|
+
raw,
|
|
1021
|
+
"app-server rate-limit snapshot",
|
|
1022
|
+
) as AppServerRateLimitSnapshot;
|
|
1023
|
+
const limitId = asString(snapshot.limitId) ?? fallbackId;
|
|
1024
|
+
const primary = normalizeAppServerWindow(snapshot.primary, capturedAt);
|
|
1025
|
+
const secondary = normalizeAppServerWindow(snapshot.secondary, capturedAt);
|
|
1026
|
+
if (!primary && !secondary) return undefined;
|
|
1027
|
+
return { limitId, primary, secondary };
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function normalizeAppServerWindow(
|
|
1031
|
+
value: unknown,
|
|
1032
|
+
capturedAt: number,
|
|
1033
|
+
): NormalizedRateLimitWindow | undefined {
|
|
1034
|
+
if (value === null || value === undefined) return undefined;
|
|
1035
|
+
const window = assertObject(
|
|
1036
|
+
value,
|
|
1037
|
+
"app-server rate-limit window",
|
|
1038
|
+
) as AppServerWindowSnapshot;
|
|
1039
|
+
const usedPercent = asNumber(window.usedPercent);
|
|
1040
|
+
if (usedPercent === undefined) return undefined;
|
|
1041
|
+
const resetAt = asResetTime(
|
|
1042
|
+
[
|
|
1043
|
+
window.resetAt,
|
|
1044
|
+
window.resetsAt,
|
|
1045
|
+
window.resetTime,
|
|
1046
|
+
window.endTime,
|
|
1047
|
+
window.endsAt,
|
|
1048
|
+
window.expiresAt,
|
|
1049
|
+
],
|
|
1050
|
+
window.resetAfterSeconds,
|
|
1051
|
+
capturedAt,
|
|
1052
|
+
);
|
|
1053
|
+
return resetAt === undefined ? { usedPercent } : { usedPercent, resetAt };
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function mergeSnapshot(
|
|
1057
|
+
left: NormalizedRateLimitSnapshot,
|
|
1058
|
+
right: NormalizedRateLimitSnapshot,
|
|
1059
|
+
): NormalizedRateLimitSnapshot {
|
|
1060
|
+
return {
|
|
1061
|
+
limitId: right.limitId || left.limitId,
|
|
1062
|
+
primary: right.primary ?? left.primary,
|
|
1063
|
+
secondary: right.secondary ?? left.secondary,
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
export function formatCodexUsageStatusline(
|
|
1068
|
+
report: CodexUsageReport,
|
|
1069
|
+
ctx: ExtensionContext,
|
|
1070
|
+
model?: CodexUsageModel,
|
|
1071
|
+
): string {
|
|
1072
|
+
const value = formatCodexUsageStatusValue(report, model);
|
|
1073
|
+
if (!value) return formatStatuslineText(ctx, "n/a", model);
|
|
1074
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1075
|
+
if (!snapshot?.primary || !snapshot.secondary)
|
|
1076
|
+
return formatStatuslineText(ctx, value, model);
|
|
1077
|
+
const [bar, countdown] = value.split(" ", 2);
|
|
1078
|
+
const barText = formatStatuslineBarText(
|
|
1079
|
+
ctx,
|
|
1080
|
+
bar ?? "",
|
|
1081
|
+
hasExhaustedQuotaWindow(report, model) ? "toolErrorBg" : "selectedBg",
|
|
1082
|
+
model,
|
|
1083
|
+
);
|
|
1084
|
+
return countdown
|
|
1085
|
+
? `${barText} ${ctx.ui.theme.fg("dim", countdown)}`
|
|
1086
|
+
: barText;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
export function formatCodexUsageBar(
|
|
1090
|
+
report: CodexUsageReport,
|
|
1091
|
+
): string | undefined {
|
|
1092
|
+
return formatReportBar(report);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
export function formatCodexUsageStatusValue(
|
|
1096
|
+
report: CodexUsageReport,
|
|
1097
|
+
modelOrNow?: CodexUsageModel | number,
|
|
1098
|
+
now = Date.now(),
|
|
1099
|
+
): string | undefined {
|
|
1100
|
+
const model = typeof modelOrNow === "number" ? undefined : modelOrNow;
|
|
1101
|
+
const capturedNow = typeof modelOrNow === "number" ? modelOrNow : now;
|
|
1102
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1103
|
+
if (!snapshot || (!snapshot.primary && !snapshot.secondary)) return undefined;
|
|
1104
|
+
if (!snapshot.primary || !snapshot.secondary) {
|
|
1105
|
+
const window = snapshot.primary ?? snapshot.secondary;
|
|
1106
|
+
if (!window) return undefined;
|
|
1107
|
+
const percentage = `${Math.round(remainingPercent(window))}%`;
|
|
1108
|
+
const countdown = window.resetAt
|
|
1109
|
+
? formatResetCountdown(window.resetAt, capturedNow)
|
|
1110
|
+
: undefined;
|
|
1111
|
+
return countdown ? `${percentage} ${countdown}` : percentage;
|
|
1112
|
+
}
|
|
1113
|
+
const bar = formatDualLimitBar(snapshot.primary, snapshot.secondary);
|
|
1114
|
+
if (isQuotaWindowExhausted(snapshot.primary) && snapshot.primary?.resetAt) {
|
|
1115
|
+
const primaryCountdown = formatResetCountdown(
|
|
1116
|
+
snapshot.primary.resetAt,
|
|
1117
|
+
capturedNow,
|
|
1118
|
+
);
|
|
1119
|
+
const weeklyCountdown = snapshot.secondary?.resetAt
|
|
1120
|
+
? formatResetCountdown(snapshot.secondary.resetAt, capturedNow)
|
|
1121
|
+
: undefined;
|
|
1122
|
+
return weeklyCountdown
|
|
1123
|
+
? `${bar} ${primaryCountdown}/${weeklyCountdown}`
|
|
1124
|
+
: `${bar} ${primaryCountdown}`;
|
|
1125
|
+
}
|
|
1126
|
+
const countdown = formatWeeklyResetCountdown(report, model, capturedNow);
|
|
1127
|
+
return countdown ? `${bar} ${countdown}` : bar;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
export function formatWeeklyResetCountdown(
|
|
1131
|
+
report: CodexUsageReport,
|
|
1132
|
+
modelOrNow?: CodexUsageModel | number,
|
|
1133
|
+
now = Date.now(),
|
|
1134
|
+
): string | undefined {
|
|
1135
|
+
const model = typeof modelOrNow === "number" ? undefined : modelOrNow;
|
|
1136
|
+
const capturedNow = typeof modelOrNow === "number" ? modelOrNow : now;
|
|
1137
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1138
|
+
const resetAt = weeklyWindow(snapshot)?.resetAt;
|
|
1139
|
+
if (resetAt === undefined) return undefined;
|
|
1140
|
+
return formatResetCountdown(resetAt, capturedNow);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
export function formatResetCountdown(
|
|
1144
|
+
resetAt: number,
|
|
1145
|
+
now = Date.now(),
|
|
1146
|
+
): string {
|
|
1147
|
+
const remainingMs = Math.max(0, resetAt - now);
|
|
1148
|
+
if (remainingMs > DAY_MS) {
|
|
1149
|
+
const dayTenths = Math.max(10, Math.ceil(remainingMs / DAY_TENTH_MS));
|
|
1150
|
+
return `${formatTenths(dayTenths)}d`;
|
|
1151
|
+
}
|
|
1152
|
+
if (remainingMs >= HOUR_MS) {
|
|
1153
|
+
const hourTenths = Math.max(10, Math.ceil(remainingMs / HOUR_TENTH_MS));
|
|
1154
|
+
return `${formatTenths(hourTenths)}h`;
|
|
1155
|
+
}
|
|
1156
|
+
if (remainingMs >= MINUTE_MS)
|
|
1157
|
+
return `${Math.floor(remainingMs / MINUTE_MS)}m`;
|
|
1158
|
+
return `${Math.floor(remainingMs / SECOND_MS)}s`;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
export function nextResetCountdownDelayMs(
|
|
1162
|
+
report: CodexUsageReport,
|
|
1163
|
+
now = Date.now(),
|
|
1164
|
+
model?: CodexUsageModel,
|
|
1165
|
+
): number | undefined {
|
|
1166
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1167
|
+
const resetTimes = [weeklyWindow(snapshot)?.resetAt];
|
|
1168
|
+
if (snapshot?.secondary && isQuotaWindowExhausted(snapshot.primary)) {
|
|
1169
|
+
resetTimes.push(snapshot.primary?.resetAt);
|
|
1170
|
+
}
|
|
1171
|
+
const delays = resetTimes
|
|
1172
|
+
.filter((resetAt): resetAt is number => resetAt !== undefined)
|
|
1173
|
+
.map((resetAt) => nextResetCountdownDelayForRemainingMs(resetAt - now))
|
|
1174
|
+
.filter((delay): delay is number => delay !== undefined);
|
|
1175
|
+
return delays.length > 0 ? Math.min(...delays) : undefined;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
export function nextResetCountdownDelayForRemainingMs(
|
|
1179
|
+
remainingMs: number,
|
|
1180
|
+
): number | undefined {
|
|
1181
|
+
if (remainingMs <= 0) return undefined;
|
|
1182
|
+
if (remainingMs > DAY_MS) {
|
|
1183
|
+
const dayTenths = Math.max(10, Math.ceil(remainingMs / DAY_TENTH_MS));
|
|
1184
|
+
return Math.max(1, remainingMs - (dayTenths - 1) * DAY_TENTH_MS);
|
|
1185
|
+
}
|
|
1186
|
+
if (remainingMs >= HOUR_MS) {
|
|
1187
|
+
const hourTenths = Math.max(10, Math.ceil(remainingMs / HOUR_TENTH_MS));
|
|
1188
|
+
if (hourTenths === 10) return Math.max(1, remainingMs - HOUR_MS + 1);
|
|
1189
|
+
return Math.max(1, remainingMs - (hourTenths - 1) * HOUR_TENTH_MS);
|
|
1190
|
+
}
|
|
1191
|
+
if (remainingMs >= MINUTE_MS) {
|
|
1192
|
+
return Math.max(
|
|
1193
|
+
1,
|
|
1194
|
+
remainingMs - Math.floor(remainingMs / MINUTE_MS) * MINUTE_MS + 1,
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
return Math.max(
|
|
1198
|
+
1,
|
|
1199
|
+
remainingMs - Math.floor(remainingMs / SECOND_MS) * SECOND_MS + 1,
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function formatTenths(value: number): string {
|
|
1204
|
+
return value % 10 === 0 ? String(value / 10) : (value / 10).toFixed(1);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
function formatReportBar(
|
|
1208
|
+
report: CodexUsageReport,
|
|
1209
|
+
model?: CodexUsageModel,
|
|
1210
|
+
): string | undefined {
|
|
1211
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1212
|
+
if (!snapshot || (!snapshot.primary && !snapshot.secondary)) return undefined;
|
|
1213
|
+
return formatAdaptiveLimitBar(snapshot.primary, snapshot.secondary);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
function formatStatuslineText(
|
|
1217
|
+
ctx: ExtensionContext,
|
|
1218
|
+
value: string,
|
|
1219
|
+
model?: CodexUsageModel,
|
|
1220
|
+
): string {
|
|
1221
|
+
const label = ctx.ui.theme.fg("accent", activeUsageLabel(model));
|
|
1222
|
+
return `${label} ${ctx.ui.theme.fg("dim", value)}`;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
function formatStatuslineBarText(
|
|
1226
|
+
ctx: ExtensionContext,
|
|
1227
|
+
bar: string,
|
|
1228
|
+
background: "selectedBg" | "toolErrorBg" = "selectedBg",
|
|
1229
|
+
model?: CodexUsageModel,
|
|
1230
|
+
): string {
|
|
1231
|
+
const label = ctx.ui.theme.fg("accent", activeUsageLabel(model));
|
|
1232
|
+
const value = ctx.ui.theme.bg(background, ctx.ui.theme.fg("dim", bar));
|
|
1233
|
+
return `${label} ${value}`;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
export function formatCodexUsageLoadingBar(frame: number): string {
|
|
1237
|
+
const totalParts = DUAL_BAR_WIDTH * 2;
|
|
1238
|
+
const cycleFrames = (totalParts - 1) * 2;
|
|
1239
|
+
const finiteFrame = Number.isFinite(frame) ? frame : 0;
|
|
1240
|
+
const cycleFrame = Math.abs(Math.trunc(finiteFrame)) % cycleFrames;
|
|
1241
|
+
const primaryPart =
|
|
1242
|
+
cycleFrame < totalParts ? cycleFrame : cycleFrames - cycleFrame;
|
|
1243
|
+
const secondaryPart = totalParts - primaryPart - 1;
|
|
1244
|
+
const masks = Array<number>(DUAL_BAR_WIDTH).fill(0);
|
|
1245
|
+
const primaryCell = Math.floor(primaryPart / 2);
|
|
1246
|
+
const secondaryCell = Math.floor(secondaryPart / 2);
|
|
1247
|
+
masks[primaryCell] |= primaryPart % 2 === 0 ? 1 : 2;
|
|
1248
|
+
masks[secondaryCell] |= secondaryPart % 2 === 0 ? 4 : 8;
|
|
1249
|
+
return masks.map((mask) => DUAL_BAR_CHARS[mask]).join("");
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function formatStatuslineLoading(
|
|
1253
|
+
ctx: ExtensionContext,
|
|
1254
|
+
frame: number,
|
|
1255
|
+
model?: CodexUsageModel,
|
|
1256
|
+
): string {
|
|
1257
|
+
return formatStatuslineBarText(
|
|
1258
|
+
ctx,
|
|
1259
|
+
formatCodexUsageLoadingBar(frame),
|
|
1260
|
+
"selectedBg",
|
|
1261
|
+
model,
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
function formatStatuslineProblem(
|
|
1266
|
+
ctx: ExtensionContext,
|
|
1267
|
+
errors: UsageQueryError[],
|
|
1268
|
+
model?: CodexUsageModel,
|
|
1269
|
+
): string {
|
|
1270
|
+
const label = ctx.ui.theme.fg("accent", activeUsageLabel(model));
|
|
1271
|
+
const value = isUsageUnavailable(errors)
|
|
1272
|
+
? ctx.ui.theme.fg("muted", "n/a")
|
|
1273
|
+
: ctx.ui.theme.fg("error", "error");
|
|
1274
|
+
return `${label} ${value}`;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
export function isUsageUnavailable(errors: UsageQueryError[]): boolean {
|
|
1278
|
+
return errors.length > 0 && errors.every(isUnavailableError);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function isUnavailableError(error: UsageQueryError): boolean {
|
|
1282
|
+
const message = error.message.toLowerCase();
|
|
1283
|
+
return (
|
|
1284
|
+
message.includes("no pi openai codex subscription auth") ||
|
|
1285
|
+
message.includes("no displayable rate-limit windows") ||
|
|
1286
|
+
message.includes("returned no displayable rate-limit windows") ||
|
|
1287
|
+
message.includes("returned 401") ||
|
|
1288
|
+
message.includes("returned 403") ||
|
|
1289
|
+
message.includes("unauthorized") ||
|
|
1290
|
+
message.includes("forbidden") ||
|
|
1291
|
+
message.includes("subscription") ||
|
|
1292
|
+
message.includes("no active plan") ||
|
|
1293
|
+
message.includes("plan unavailable") ||
|
|
1294
|
+
message.includes("quota unavailable") ||
|
|
1295
|
+
message.includes("rate limits unavailable")
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function normalizedUsageKey(value: string | undefined): string | undefined {
|
|
1300
|
+
const key = value
|
|
1301
|
+
?.toLowerCase()
|
|
1302
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
1303
|
+
.replace(/^-+|-+$/g, "");
|
|
1304
|
+
return key || undefined;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
function formatAdaptiveLimitBar(
|
|
1308
|
+
primary: NormalizedRateLimitWindow | undefined,
|
|
1309
|
+
secondary: NormalizedRateLimitWindow | undefined,
|
|
1310
|
+
): string {
|
|
1311
|
+
if (!primary || !secondary) return formatSingleLimitBar(primary ?? secondary);
|
|
1312
|
+
return formatDualLimitBar(primary, secondary);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function formatSingleLimitBar(
|
|
1316
|
+
window: NormalizedRateLimitWindow | undefined,
|
|
1317
|
+
): string {
|
|
1318
|
+
const filled = filledParts(window, DUAL_BAR_WIDTH * 4);
|
|
1319
|
+
const partMasks = [1, 4, 2, 8];
|
|
1320
|
+
let value = "";
|
|
1321
|
+
for (let index = 0; index < DUAL_BAR_WIDTH; index++) {
|
|
1322
|
+
const cellParts = Math.min(4, Math.max(0, filled - index * 4));
|
|
1323
|
+
let mask = 0;
|
|
1324
|
+
for (let part = 0; part < cellParts; part++) mask |= partMasks[part] ?? 0;
|
|
1325
|
+
value += DUAL_BAR_CHARS[mask];
|
|
1326
|
+
}
|
|
1327
|
+
return value;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
function formatDualLimitBar(
|
|
1331
|
+
primary: NormalizedRateLimitWindow,
|
|
1332
|
+
secondary: NormalizedRateLimitWindow,
|
|
1333
|
+
): string {
|
|
1334
|
+
const primaryParts = filledTwentieths(primary);
|
|
1335
|
+
const secondaryParts = filledTwentieths(secondary);
|
|
1336
|
+
let value = "";
|
|
1337
|
+
for (let index = 0; index < DUAL_BAR_WIDTH; index++) {
|
|
1338
|
+
const leftPart = index * 2 + 1;
|
|
1339
|
+
const rightPart = leftPart + 1;
|
|
1340
|
+
let mask = 0;
|
|
1341
|
+
if (primaryParts >= leftPart) mask |= 1;
|
|
1342
|
+
if (primaryParts >= rightPart) mask |= 2;
|
|
1343
|
+
if (secondaryParts >= leftPart) mask |= 4;
|
|
1344
|
+
if (secondaryParts >= rightPart) mask |= 8;
|
|
1345
|
+
value += DUAL_BAR_CHARS[mask];
|
|
1346
|
+
}
|
|
1347
|
+
return value;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
function filledTwentieths(
|
|
1351
|
+
window: NormalizedRateLimitWindow | undefined,
|
|
1352
|
+
): number {
|
|
1353
|
+
return filledParts(window, 20);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function filledParts(
|
|
1357
|
+
window: NormalizedRateLimitWindow | undefined,
|
|
1358
|
+
totalParts: number,
|
|
1359
|
+
): number {
|
|
1360
|
+
if (!window) return 0;
|
|
1361
|
+
const remaining = remainingPercent(window);
|
|
1362
|
+
if (remaining <= 0) return 0;
|
|
1363
|
+
return Math.max(1, Math.round(remaining / (100 / totalParts)));
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
function remainingPercent(window: NormalizedRateLimitWindow): number {
|
|
1367
|
+
return 100 - clampPercent(window.usedPercent);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
function isQuotaWindowExhausted(
|
|
1371
|
+
window: NormalizedRateLimitWindow | undefined,
|
|
1372
|
+
): boolean {
|
|
1373
|
+
return window !== undefined && remainingPercent(window) <= 0;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function hasExhaustedQuotaWindow(
|
|
1377
|
+
report: CodexUsageReport,
|
|
1378
|
+
model?: CodexUsageModel,
|
|
1379
|
+
): boolean {
|
|
1380
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1381
|
+
return (
|
|
1382
|
+
isQuotaWindowExhausted(snapshot?.primary) ||
|
|
1383
|
+
isQuotaWindowExhausted(snapshot?.secondary)
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
export function canReuseCachedReport(
|
|
1388
|
+
report: CodexUsageReport,
|
|
1389
|
+
model: CodexUsageModel | undefined,
|
|
1390
|
+
): boolean {
|
|
1391
|
+
return selectActiveUsageSnapshot(report, model) !== undefined;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
export function isFullyAvailableReport(
|
|
1395
|
+
report: CodexUsageReport,
|
|
1396
|
+
model?: CodexUsageModel,
|
|
1397
|
+
): boolean {
|
|
1398
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1399
|
+
const windows = [snapshot?.primary, snapshot?.secondary].filter(
|
|
1400
|
+
(window): window is NormalizedRateLimitWindow => window !== undefined,
|
|
1401
|
+
);
|
|
1402
|
+
return (
|
|
1403
|
+
windows.length > 0 &&
|
|
1404
|
+
windows.every((window) => clampPercent(window.usedPercent) === 0)
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function weeklyWindow(
|
|
1409
|
+
snapshot: NormalizedRateLimitSnapshot | undefined,
|
|
1410
|
+
): NormalizedRateLimitWindow | undefined {
|
|
1411
|
+
return snapshot?.secondary ?? snapshot?.primary;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
function selectActiveUsageSnapshot(
|
|
1415
|
+
report: CodexUsageReport,
|
|
1416
|
+
model: CodexUsageModel | undefined,
|
|
1417
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
1418
|
+
return selectUsageSnapshot(report, activeUsageLimitId(model));
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function selectUsageSnapshot(
|
|
1422
|
+
report: CodexUsageReport,
|
|
1423
|
+
limitId: string,
|
|
1424
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
1425
|
+
const normalizedLimitId = normalizedUsageKey(limitId);
|
|
1426
|
+
return report.snapshots.find(
|
|
1427
|
+
(snapshot) => normalizedUsageKey(snapshot.limitId) === normalizedLimitId,
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function clampPercent(value: number): number {
|
|
1432
|
+
if (!Number.isFinite(value)) return 0;
|
|
1433
|
+
return Math.min(100, Math.max(0, value));
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function parseJsonObject(
|
|
1437
|
+
text: string,
|
|
1438
|
+
description: string,
|
|
1439
|
+
): Record<string, unknown> {
|
|
1440
|
+
let parsed: unknown;
|
|
1441
|
+
try {
|
|
1442
|
+
parsed = JSON.parse(text) as unknown;
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
throw new Error(
|
|
1445
|
+
`${description} was not valid JSON: ${errorMessage(error)}`,
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
return assertObject(parsed, description);
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function assertObject(
|
|
1452
|
+
value: unknown,
|
|
1453
|
+
description: string,
|
|
1454
|
+
): Record<string, unknown> {
|
|
1455
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1456
|
+
throw new Error(`${description} was not an object.`);
|
|
1457
|
+
}
|
|
1458
|
+
return value as Record<string, unknown>;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
function asString(value: unknown): string | undefined {
|
|
1462
|
+
return typeof value === "string" ? value : undefined;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
function asNumber(value: unknown): number | undefined {
|
|
1466
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1467
|
+
if (typeof value === "string" && value.trim()) {
|
|
1468
|
+
const parsed = Number(value);
|
|
1469
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1470
|
+
}
|
|
1471
|
+
return undefined;
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
function asResetTime(
|
|
1475
|
+
absoluteValues: unknown[],
|
|
1476
|
+
relativeSeconds: unknown,
|
|
1477
|
+
capturedAt: number,
|
|
1478
|
+
): number | undefined {
|
|
1479
|
+
for (const value of absoluteValues) {
|
|
1480
|
+
const timestamp = asTimestampMs(value);
|
|
1481
|
+
if (timestamp !== undefined) return timestamp;
|
|
1482
|
+
}
|
|
1483
|
+
const seconds = asNumber(relativeSeconds);
|
|
1484
|
+
if (seconds === undefined || seconds < 0) return undefined;
|
|
1485
|
+
return capturedAt + seconds * SECOND_MS;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function asTimestampMs(value: unknown): number | undefined {
|
|
1489
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
1490
|
+
if (value <= 0) return undefined;
|
|
1491
|
+
return value < 10_000_000_000 ? value * SECOND_MS : value;
|
|
1492
|
+
}
|
|
1493
|
+
if (typeof value === "string" && value.trim()) {
|
|
1494
|
+
const numeric = Number(value);
|
|
1495
|
+
if (Number.isFinite(numeric)) return asTimestampMs(numeric);
|
|
1496
|
+
const parsed = Date.parse(value);
|
|
1497
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1498
|
+
}
|
|
1499
|
+
return undefined;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
function hasHeader(headers: Record<string, string>, name: string): boolean {
|
|
1503
|
+
return Object.keys(headers).some(
|
|
1504
|
+
(key) => key.toLowerCase() === name.toLowerCase(),
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
function redactErrorBody(body: string): string {
|
|
1509
|
+
return truncateEnd(
|
|
1510
|
+
body
|
|
1511
|
+
.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer <redacted>")
|
|
1512
|
+
.replace(/"access_token"\s*:\s*"[^"]+"/gi, '"access_token":"<redacted>"')
|
|
1513
|
+
.trim(),
|
|
1514
|
+
MAX_ERROR_BODY_CHARS,
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
function truncateEnd(value: string, maxChars: number): string {
|
|
1519
|
+
if (value.length <= maxChars) return value;
|
|
1520
|
+
return `${value.slice(0, maxChars - 1)}…`;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
function errorMessage(error: unknown): string {
|
|
1524
|
+
return error instanceof Error ? error.message : String(error);
|
|
1525
|
+
}
|