aw-ecc 1.4.32 → 1.4.47
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/.claude-plugin/plugin.json +1 -1
- package/.codex/hooks/aw-post-tool-use.sh +8 -2
- package/.codex/hooks/aw-session-start.sh +11 -4
- package/.codex/hooks/aw-stop.sh +8 -2
- package/.codex/hooks/aw-user-prompt-submit.sh +10 -2
- package/.codex/hooks.json +8 -8
- package/.cursor/INSTALL.md +7 -5
- package/.cursor/hooks/adapter.js +41 -4
- package/.cursor/hooks/after-agent-response.js +62 -0
- package/.cursor/hooks/before-submit-prompt.js +7 -1
- package/.cursor/hooks/post-tool-use-failure.js +21 -0
- package/.cursor/hooks/post-tool-use.js +39 -0
- package/.cursor/hooks/shared/aw-phase-definitions.js +53 -0
- package/.cursor/hooks/shared/aw-phase-runner.js +3 -1
- package/.cursor/hooks/subagent-start.js +22 -4
- package/.cursor/hooks/subagent-stop.js +18 -1
- package/.cursor/hooks.json +23 -2
- package/.opencode/package.json +1 -1
- package/AGENTS.md +3 -3
- package/README.md +5 -5
- package/commands/adk.md +52 -0
- package/commands/build.md +22 -9
- package/commands/deploy.md +12 -0
- package/commands/execute.md +9 -0
- package/commands/feature.md +333 -0
- package/commands/investigate.md +18 -5
- package/commands/plan.md +23 -9
- package/commands/publish.md +65 -0
- package/commands/review.md +12 -0
- package/commands/ship.md +12 -0
- package/commands/test.md +12 -0
- package/commands/verify.md +9 -0
- package/hooks/hooks.json +36 -0
- package/manifests/install-components.json +8 -0
- package/manifests/install-modules.json +83 -0
- package/manifests/install-profiles.json +7 -0
- package/package.json +1 -1
- package/scripts/ci/validate-rules.js +51 -0
- package/scripts/cursor-aw-home/hooks.json +23 -2
- package/scripts/cursor-aw-hooks/adapter.js +41 -4
- package/scripts/cursor-aw-hooks/before-submit-prompt.js +7 -1
- package/scripts/hooks/aw-usage-commit-created.js +32 -0
- package/scripts/hooks/aw-usage-post-tool-use-failure.js +56 -0
- package/scripts/hooks/aw-usage-post-tool-use.js +242 -0
- package/scripts/hooks/aw-usage-prompt-submit.js +112 -0
- package/scripts/hooks/aw-usage-session-start.js +48 -0
- package/scripts/hooks/aw-usage-stop.js +182 -0
- package/scripts/hooks/aw-usage-telemetry-send.js +84 -0
- package/scripts/hooks/cost-tracker.js +3 -23
- package/scripts/hooks/shared/aw-phase-definitions.js +53 -0
- package/scripts/hooks/shared/aw-phase-runner.js +3 -1
- package/scripts/lib/aw-hook-contract.js +2 -2
- package/scripts/lib/aw-pricing.js +306 -0
- package/scripts/lib/aw-usage-telemetry.js +472 -0
- package/scripts/lib/codex-hook-config.js +8 -8
- package/scripts/lib/cursor-hook-config.js +25 -10
- package/scripts/lib/install-targets/cursor-project.js +3 -0
- package/scripts/lib/install-targets/helpers.js +20 -3
- package/skills/aw-adk/SKILL.md +317 -0
- package/skills/aw-adk/agents/analyzer.md +113 -0
- package/skills/aw-adk/agents/comparator.md +113 -0
- package/skills/aw-adk/agents/grader.md +115 -0
- package/skills/aw-adk/assets/eval_review.html +76 -0
- package/skills/aw-adk/eval-viewer/generate_review.py +164 -0
- package/skills/aw-adk/eval-viewer/viewer.html +181 -0
- package/skills/aw-adk/evals/eval-colocated-placement.md +84 -0
- package/skills/aw-adk/evals/eval-create-agent.md +90 -0
- package/skills/aw-adk/evals/eval-create-command.md +98 -0
- package/skills/aw-adk/evals/eval-create-eval.md +89 -0
- package/skills/aw-adk/evals/eval-create-rule.md +99 -0
- package/skills/aw-adk/evals/eval-create-skill.md +97 -0
- package/skills/aw-adk/evals/eval-delete-agent.md +79 -0
- package/skills/aw-adk/evals/eval-delete-command.md +89 -0
- package/skills/aw-adk/evals/eval-delete-rule.md +86 -0
- package/skills/aw-adk/evals/eval-delete-skill.md +90 -0
- package/skills/aw-adk/evals/eval-meta-eval-coverage.md +78 -0
- package/skills/aw-adk/evals/eval-meta-eval-determinism.md +81 -0
- package/skills/aw-adk/evals/eval-meta-eval-false-pass.md +81 -0
- package/skills/aw-adk/evals/eval-score-accuracy.md +95 -0
- package/skills/aw-adk/evals/eval-type-redirect.md +68 -0
- package/skills/aw-adk/evals/evals.json +96 -0
- package/skills/aw-adk/references/artifact-wiring.md +162 -0
- package/skills/aw-adk/references/cross-ide-mapping.md +71 -0
- package/skills/aw-adk/references/eval-placement-guide.md +183 -0
- package/skills/aw-adk/references/external-resources.md +75 -0
- package/skills/aw-adk/references/getting-started.md +66 -0
- package/skills/aw-adk/references/registry-structure.md +152 -0
- package/skills/aw-adk/references/rubric-agent.md +36 -0
- package/skills/aw-adk/references/rubric-command.md +36 -0
- package/skills/aw-adk/references/rubric-eval.md +36 -0
- package/skills/aw-adk/references/rubric-meta-eval.md +132 -0
- package/skills/aw-adk/references/rubric-rule.md +36 -0
- package/skills/aw-adk/references/rubric-skill.md +36 -0
- package/skills/aw-adk/references/schemas.md +222 -0
- package/skills/aw-adk/references/template-agent.md +251 -0
- package/skills/aw-adk/references/template-command.md +279 -0
- package/skills/aw-adk/references/template-eval.md +176 -0
- package/skills/aw-adk/references/template-rule.md +119 -0
- package/skills/aw-adk/references/template-skill.md +123 -0
- package/skills/aw-adk/references/type-classifier.md +98 -0
- package/skills/aw-adk/references/writing-good-agents.md +227 -0
- package/skills/aw-adk/references/writing-good-commands.md +258 -0
- package/skills/aw-adk/references/writing-good-evals.md +271 -0
- package/skills/aw-adk/references/writing-good-rules.md +214 -0
- package/skills/aw-adk/references/writing-good-skills.md +159 -0
- package/skills/aw-adk/scripts/aggregate-benchmark.py +190 -0
- package/skills/aw-adk/scripts/lint-artifact.sh +211 -0
- package/skills/aw-adk/scripts/score-artifact.sh +179 -0
- package/skills/aw-adk/scripts/trigger-eval.py +192 -0
- package/skills/aw-build/SKILL.md +19 -2
- package/skills/aw-deploy/SKILL.md +65 -3
- package/skills/aw-design/SKILL.md +156 -0
- package/skills/aw-design/references/highrise-tokens.md +394 -0
- package/skills/aw-design/references/micro-interactions.md +76 -0
- package/skills/aw-design/references/prompt-template.md +160 -0
- package/skills/aw-design/references/quality-checklist.md +70 -0
- package/skills/aw-design/references/self-review.md +497 -0
- package/skills/aw-design/references/stitch-workflow.md +127 -0
- package/skills/aw-feature/SKILL.md +293 -0
- package/skills/aw-investigate/SKILL.md +17 -0
- package/skills/aw-plan/SKILL.md +34 -3
- package/skills/aw-publish/SKILL.md +300 -0
- package/skills/aw-publish/evals/eval-confirmation-gate.md +60 -0
- package/skills/aw-publish/evals/eval-intent-detection.md +111 -0
- package/skills/aw-publish/evals/eval-push-modes.md +67 -0
- package/skills/aw-publish/evals/eval-rules-push.md +60 -0
- package/skills/aw-publish/evals/evals.json +29 -0
- package/skills/aw-publish/references/push-modes.md +38 -0
- package/skills/aw-review/SKILL.md +88 -9
- package/skills/aw-rules-review/SKILL.md +124 -0
- package/skills/aw-rules-review/agents/openai.yaml +3 -0
- package/skills/aw-rules-review/scripts/generate-review-template.mjs +323 -0
- package/skills/aw-ship/SKILL.md +16 -0
- package/skills/aw-spec/SKILL.md +15 -0
- package/skills/aw-tasks/SKILL.md +15 -0
- package/skills/aw-test/SKILL.md +16 -0
- package/skills/aw-yolo/SKILL.md +4 -0
- package/skills/diagnose/SKILL.md +121 -0
- package/skills/diagnose/scripts/hitl-loop.template.sh +41 -0
- package/skills/finish-only-when-green/SKILL.md +265 -0
- package/skills/grill-me/SKILL.md +24 -0
- package/skills/grill-with-docs/SKILL.md +92 -0
- package/skills/grill-with-docs/adr-format.md +47 -0
- package/skills/grill-with-docs/context-format.md +67 -0
- package/skills/improve-codebase-architecture/SKILL.md +75 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/skills/local-ghl-setup-from-screenshot/SKILL.md +538 -0
- package/skills/tdd/SKILL.md +115 -0
- package/skills/tdd/deep-modules.md +33 -0
- package/skills/tdd/interface-design.md +31 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/refactoring.md +10 -0
- package/skills/tdd/tests.md +61 -0
- package/skills/to-issues/SKILL.md +62 -0
- package/skills/to-prd/SKILL.md +75 -0
- package/skills/using-aw-skills/SKILL.md +170 -237
- package/skills/using-aw-skills/hooks/session-start.sh +11 -41
- package/skills/zoom-out/SKILL.md +24 -0
- package/.cursor/rules/common-agents.md +0 -53
- package/.cursor/rules/common-aw-routing.md +0 -43
- package/.cursor/rules/common-coding-style.md +0 -52
- package/.cursor/rules/common-development-workflow.md +0 -33
- package/.cursor/rules/common-git-workflow.md +0 -28
- package/.cursor/rules/common-hooks.md +0 -34
- package/.cursor/rules/common-patterns.md +0 -35
- package/.cursor/rules/common-performance.md +0 -59
- package/.cursor/rules/common-security.md +0 -33
- package/.cursor/rules/common-testing.md +0 -33
- package/.cursor/skills/api-and-interface-design/SKILL.md +0 -75
- package/.cursor/skills/article-writing/SKILL.md +0 -85
- package/.cursor/skills/aw-brainstorm/SKILL.md +0 -115
- package/.cursor/skills/aw-build/SKILL.md +0 -152
- package/.cursor/skills/aw-build/evals/build-stage-cases.json +0 -28
- package/.cursor/skills/aw-debug/SKILL.md +0 -49
- package/.cursor/skills/aw-deploy/SKILL.md +0 -101
- package/.cursor/skills/aw-deploy/evals/deploy-stage-cases.json +0 -32
- package/.cursor/skills/aw-execute/SKILL.md +0 -47
- package/.cursor/skills/aw-execute/references/mode-code.md +0 -47
- package/.cursor/skills/aw-execute/references/mode-docs.md +0 -28
- package/.cursor/skills/aw-execute/references/mode-infra.md +0 -44
- package/.cursor/skills/aw-execute/references/mode-migration.md +0 -58
- package/.cursor/skills/aw-execute/references/worker-implementer.md +0 -26
- package/.cursor/skills/aw-execute/references/worker-parallel-worker.md +0 -23
- package/.cursor/skills/aw-execute/references/worker-quality-reviewer.md +0 -23
- package/.cursor/skills/aw-execute/references/worker-spec-reviewer.md +0 -23
- package/.cursor/skills/aw-execute/scripts/build-worker-bundle.js +0 -229
- package/.cursor/skills/aw-finish/SKILL.md +0 -111
- package/.cursor/skills/aw-investigate/SKILL.md +0 -109
- package/.cursor/skills/aw-plan/SKILL.md +0 -368
- package/.cursor/skills/aw-prepare/SKILL.md +0 -118
- package/.cursor/skills/aw-review/SKILL.md +0 -118
- package/.cursor/skills/aw-ship/SKILL.md +0 -115
- package/.cursor/skills/aw-spec/SKILL.md +0 -104
- package/.cursor/skills/aw-tasks/SKILL.md +0 -138
- package/.cursor/skills/aw-test/SKILL.md +0 -118
- package/.cursor/skills/aw-verify/SKILL.md +0 -51
- package/.cursor/skills/aw-yolo/SKILL.md +0 -111
- package/.cursor/skills/browser-testing-with-devtools/SKILL.md +0 -81
- package/.cursor/skills/bun-runtime/SKILL.md +0 -84
- package/.cursor/skills/ci-cd-and-automation/SKILL.md +0 -71
- package/.cursor/skills/code-simplification/SKILL.md +0 -74
- package/.cursor/skills/content-engine/SKILL.md +0 -88
- package/.cursor/skills/context-engineering/SKILL.md +0 -74
- package/.cursor/skills/deprecation-and-migration/SKILL.md +0 -75
- package/.cursor/skills/documentation-and-adrs/SKILL.md +0 -75
- package/.cursor/skills/documentation-lookup/SKILL.md +0 -90
- package/.cursor/skills/frontend-slides/SKILL.md +0 -184
- package/.cursor/skills/frontend-slides/STYLE_PRESETS.md +0 -330
- package/.cursor/skills/frontend-ui-engineering/SKILL.md +0 -68
- package/.cursor/skills/git-workflow-and-versioning/SKILL.md +0 -75
- package/.cursor/skills/idea-refine/SKILL.md +0 -84
- package/.cursor/skills/incremental-implementation/SKILL.md +0 -75
- package/.cursor/skills/investor-materials/SKILL.md +0 -96
- package/.cursor/skills/investor-outreach/SKILL.md +0 -76
- package/.cursor/skills/market-research/SKILL.md +0 -75
- package/.cursor/skills/mcp-server-patterns/SKILL.md +0 -67
- package/.cursor/skills/nextjs-turbopack/SKILL.md +0 -44
- package/.cursor/skills/performance-optimization/SKILL.md +0 -77
- package/.cursor/skills/security-and-hardening/SKILL.md +0 -70
- package/.cursor/skills/using-aw-skills/SKILL.md +0 -290
- package/.cursor/skills/using-aw-skills/evals/skill-trigger-cases.tsv +0 -25
- package/.cursor/skills/using-aw-skills/evals/test-skill-triggers.sh +0 -171
- package/.cursor/skills/using-aw-skills/hooks/hooks.json +0 -9
- package/.cursor/skills/using-aw-skills/hooks/session-start.sh +0 -67
- package/.cursor/skills/using-platform-skills/SKILL.md +0 -163
- package/.cursor/skills/using-platform-skills/evals/platform-selection-cases.json +0 -52
- /package/.cursor/rules/{golang-coding-style.md → golang-coding-style.mdc} +0 -0
- /package/.cursor/rules/{golang-hooks.md → golang-hooks.mdc} +0 -0
- /package/.cursor/rules/{golang-patterns.md → golang-patterns.mdc} +0 -0
- /package/.cursor/rules/{golang-security.md → golang-security.mdc} +0 -0
- /package/.cursor/rules/{golang-testing.md → golang-testing.mdc} +0 -0
- /package/.cursor/rules/{kotlin-coding-style.md → kotlin-coding-style.mdc} +0 -0
- /package/.cursor/rules/{kotlin-hooks.md → kotlin-hooks.mdc} +0 -0
- /package/.cursor/rules/{kotlin-patterns.md → kotlin-patterns.mdc} +0 -0
- /package/.cursor/rules/{kotlin-security.md → kotlin-security.mdc} +0 -0
- /package/.cursor/rules/{kotlin-testing.md → kotlin-testing.mdc} +0 -0
- /package/.cursor/rules/{php-coding-style.md → php-coding-style.mdc} +0 -0
- /package/.cursor/rules/{php-hooks.md → php-hooks.mdc} +0 -0
- /package/.cursor/rules/{php-patterns.md → php-patterns.mdc} +0 -0
- /package/.cursor/rules/{php-security.md → php-security.mdc} +0 -0
- /package/.cursor/rules/{php-testing.md → php-testing.mdc} +0 -0
- /package/.cursor/rules/{python-coding-style.md → python-coding-style.mdc} +0 -0
- /package/.cursor/rules/{python-hooks.md → python-hooks.mdc} +0 -0
- /package/.cursor/rules/{python-patterns.md → python-patterns.mdc} +0 -0
- /package/.cursor/rules/{python-security.md → python-security.mdc} +0 -0
- /package/.cursor/rules/{python-testing.md → python-testing.mdc} +0 -0
- /package/.cursor/rules/{swift-coding-style.md → swift-coding-style.mdc} +0 -0
- /package/.cursor/rules/{swift-hooks.md → swift-hooks.mdc} +0 -0
- /package/.cursor/rules/{swift-patterns.md → swift-patterns.mdc} +0 -0
- /package/.cursor/rules/{swift-security.md → swift-security.mdc} +0 -0
- /package/.cursor/rules/{swift-testing.md → swift-testing.mdc} +0 -0
- /package/.cursor/rules/{typescript-coding-style.md → typescript-coding-style.mdc} +0 -0
- /package/.cursor/rules/{typescript-hooks.md → typescript-hooks.mdc} +0 -0
- /package/.cursor/rules/{typescript-patterns.md → typescript-patterns.mdc} +0 -0
- /package/.cursor/rules/{typescript-security.md → typescript-security.mdc} +0 -0
- /package/.cursor/rules/{typescript-testing.md → typescript-testing.mdc} +0 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Detached sender — receives a single usage telemetry event as argv[2],
|
|
4
|
+
* POSTs it to the telemetry API, then exits.
|
|
5
|
+
*
|
|
6
|
+
* Spawned by sendAsync() in aw-usage-telemetry.js with { detached: true }.
|
|
7
|
+
* This script runs independently after the parent hook process exits.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const https = require('https');
|
|
13
|
+
const http = require('http');
|
|
14
|
+
|
|
15
|
+
const DEFAULT_URL = 'https://services.leadconnectorhq.com/agentic-workspace/api/telemetry/usage-events';
|
|
16
|
+
const TIMEOUT_MS = 10_000;
|
|
17
|
+
|
|
18
|
+
function post(url, body) {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const parsed = new URL(url);
|
|
21
|
+
const transport = parsed.protocol === 'https:' ? https : http;
|
|
22
|
+
|
|
23
|
+
const req = transport.request(
|
|
24
|
+
{
|
|
25
|
+
hostname: parsed.hostname,
|
|
26
|
+
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
27
|
+
path: parsed.pathname + parsed.search,
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
'Content-Length': Buffer.byteLength(body),
|
|
32
|
+
},
|
|
33
|
+
timeout: TIMEOUT_MS,
|
|
34
|
+
},
|
|
35
|
+
(res) => {
|
|
36
|
+
// Drain response
|
|
37
|
+
res.resume();
|
|
38
|
+
res.on('end', () => resolve(res.statusCode));
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
req.on('timeout', () => {
|
|
43
|
+
req.destroy();
|
|
44
|
+
reject(new Error('timeout'));
|
|
45
|
+
});
|
|
46
|
+
req.on('error', reject);
|
|
47
|
+
req.write(body);
|
|
48
|
+
req.end();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
const eventJson = process.argv[2];
|
|
54
|
+
if (!eventJson) {
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Resolve telemetry URL: env var → config file → production default.
|
|
59
|
+
// Config file fallback covers Cursor GUI which doesn't inherit shell env (Bug #9).
|
|
60
|
+
let baseUrl = process.env.AW_TELEMETRY_URL;
|
|
61
|
+
if (!baseUrl) {
|
|
62
|
+
try {
|
|
63
|
+
const fs = require('fs');
|
|
64
|
+
const path = require('path');
|
|
65
|
+
const configPath = path.join(process.env.HOME || require('os').homedir(), '.aw', 'telemetry', 'config.json');
|
|
66
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
67
|
+
baseUrl = config.telemetry_url || null;
|
|
68
|
+
} catch { /* no config file — use default */ }
|
|
69
|
+
}
|
|
70
|
+
const url = baseUrl
|
|
71
|
+
? `${baseUrl.replace(/\/+$/, '')}/telemetry/usage-events`
|
|
72
|
+
: DEFAULT_URL;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await post(url, eventJson);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
// Non-blocking — log and exit cleanly.
|
|
78
|
+
process.stderr.write(`[aw-telemetry] send failed: ${err.message}\n`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main();
|
|
@@ -13,32 +13,11 @@ const {
|
|
|
13
13
|
appendFile,
|
|
14
14
|
getClaudeDir,
|
|
15
15
|
} = require('../lib/utils');
|
|
16
|
+
const { estimateCost, toNumber } = require('../lib/aw-pricing');
|
|
16
17
|
|
|
17
18
|
const MAX_STDIN = 1024 * 1024;
|
|
18
19
|
let raw = '';
|
|
19
20
|
|
|
20
|
-
function toNumber(value) {
|
|
21
|
-
const n = Number(value);
|
|
22
|
-
return Number.isFinite(n) ? n : 0;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function estimateCost(model, inputTokens, outputTokens) {
|
|
26
|
-
// Approximate per-1M-token blended rates. Conservative defaults.
|
|
27
|
-
const table = {
|
|
28
|
-
'haiku': { in: 0.8, out: 4.0 },
|
|
29
|
-
'sonnet': { in: 3.0, out: 15.0 },
|
|
30
|
-
'opus': { in: 15.0, out: 75.0 },
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const normalized = String(model || '').toLowerCase();
|
|
34
|
-
let rates = table.sonnet;
|
|
35
|
-
if (normalized.includes('haiku')) rates = table.haiku;
|
|
36
|
-
if (normalized.includes('opus')) rates = table.opus;
|
|
37
|
-
|
|
38
|
-
const cost = (inputTokens / 1_000_000) * rates.in + (outputTokens / 1_000_000) * rates.out;
|
|
39
|
-
return Math.round(cost * 1e6) / 1e6;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
21
|
process.stdin.setEncoding('utf8');
|
|
43
22
|
process.stdin.on('data', chunk => {
|
|
44
23
|
if (raw.length < MAX_STDIN) {
|
|
@@ -60,13 +39,14 @@ process.stdin.on('end', () => {
|
|
|
60
39
|
const metricsDir = path.join(getClaudeDir(), 'metrics');
|
|
61
40
|
ensureDir(metricsDir);
|
|
62
41
|
|
|
42
|
+
const cost = estimateCost(model, inputTokens, outputTokens);
|
|
63
43
|
const row = {
|
|
64
44
|
timestamp: new Date().toISOString(),
|
|
65
45
|
session_id: sessionId,
|
|
66
46
|
model,
|
|
67
47
|
input_tokens: inputTokens,
|
|
68
48
|
output_tokens: outputTokens,
|
|
69
|
-
estimated_cost_usd:
|
|
49
|
+
estimated_cost_usd: cost,
|
|
70
50
|
};
|
|
71
51
|
|
|
72
52
|
appendFile(path.join(metricsDir, 'costs.jsonl'), `${JSON.stringify(row)}\n`);
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
const PHASE_NAMES = Object.freeze({
|
|
2
|
+
SESSION_START: 'session-start',
|
|
3
|
+
USER_PROMPT_SUBMIT: 'user-prompt-submit',
|
|
4
|
+
PRE_TOOL_USE_SHELL: 'pre-tool-use-shell',
|
|
5
|
+
PRE_TOOL_USE_MCP: 'pre-tool-use-mcp',
|
|
6
|
+
POST_TOOL_USE_SHELL: 'post-tool-use-shell',
|
|
7
|
+
POST_TOOL_USE_FILE_EDIT: 'post-tool-use-file-edit',
|
|
8
|
+
POST_TOOL_USE_MCP: 'post-tool-use-mcp',
|
|
9
|
+
PRE_COMPACT: 'pre-compact',
|
|
10
|
+
SESSION_END: 'session-end',
|
|
11
|
+
STOP: 'stop',
|
|
12
|
+
});
|
|
13
|
+
|
|
1
14
|
const SHARED_AW_PHASE_STEPS = Object.freeze({
|
|
2
15
|
'session-start': [
|
|
3
16
|
{
|
|
@@ -8,6 +21,13 @@ const SHARED_AW_PHASE_STEPS = Object.freeze({
|
|
|
8
21
|
payloadMode: 'raw',
|
|
9
22
|
outputMode: 'cursor-session-start',
|
|
10
23
|
},
|
|
24
|
+
{
|
|
25
|
+
hookId: 'telemetry:session-start',
|
|
26
|
+
allowedProfiles: ['minimal', 'standard', 'strict'],
|
|
27
|
+
runner: 'node',
|
|
28
|
+
relativeScriptPath: 'scripts/hooks/aw-usage-session-start.js',
|
|
29
|
+
payloadMode: 'claude',
|
|
30
|
+
},
|
|
11
31
|
],
|
|
12
32
|
'user-prompt-submit': [
|
|
13
33
|
{
|
|
@@ -15,6 +35,13 @@ const SHARED_AW_PHASE_STEPS = Object.freeze({
|
|
|
15
35
|
relativeScriptPath: '.cursor/hooks/shared/user-prompt-submit.sh',
|
|
16
36
|
payloadMode: 'raw',
|
|
17
37
|
},
|
|
38
|
+
{
|
|
39
|
+
hookId: 'telemetry:prompt-submit',
|
|
40
|
+
allowedProfiles: ['minimal', 'standard', 'strict'],
|
|
41
|
+
runner: 'node',
|
|
42
|
+
relativeScriptPath: 'scripts/hooks/aw-usage-prompt-submit.js',
|
|
43
|
+
payloadMode: 'claude',
|
|
44
|
+
},
|
|
18
45
|
],
|
|
19
46
|
'pre-tool-use-shell': [
|
|
20
47
|
{
|
|
@@ -70,6 +97,13 @@ const SHARED_AW_PHASE_STEPS = Object.freeze({
|
|
|
70
97
|
relativeScriptPath: 'scripts/hooks/post-bash-build-complete.js',
|
|
71
98
|
payloadMode: 'claude',
|
|
72
99
|
},
|
|
100
|
+
{
|
|
101
|
+
hookId: 'telemetry:post-tool-use',
|
|
102
|
+
allowedProfiles: ['minimal', 'standard', 'strict'],
|
|
103
|
+
runner: 'node',
|
|
104
|
+
relativeScriptPath: 'scripts/hooks/aw-usage-post-tool-use.js',
|
|
105
|
+
payloadMode: 'claude',
|
|
106
|
+
},
|
|
73
107
|
],
|
|
74
108
|
'post-tool-use-file-edit': [
|
|
75
109
|
{
|
|
@@ -100,6 +134,13 @@ const SHARED_AW_PHASE_STEPS = Object.freeze({
|
|
|
100
134
|
relativeScriptPath: 'scripts/hooks/post-edit-console-warn.js',
|
|
101
135
|
payloadMode: 'claude',
|
|
102
136
|
},
|
|
137
|
+
{
|
|
138
|
+
hookId: 'telemetry:post-tool-use',
|
|
139
|
+
allowedProfiles: ['minimal', 'standard', 'strict'],
|
|
140
|
+
runner: 'node',
|
|
141
|
+
relativeScriptPath: 'scripts/hooks/aw-usage-post-tool-use.js',
|
|
142
|
+
payloadMode: 'claude',
|
|
143
|
+
},
|
|
103
144
|
],
|
|
104
145
|
'post-tool-use-mcp': [
|
|
105
146
|
{
|
|
@@ -109,6 +150,13 @@ const SHARED_AW_PHASE_STEPS = Object.freeze({
|
|
|
109
150
|
relativeScriptPath: 'scripts/hooks/post-mcp-log.js',
|
|
110
151
|
payloadMode: 'raw',
|
|
111
152
|
},
|
|
153
|
+
{
|
|
154
|
+
hookId: 'telemetry:post-tool-use',
|
|
155
|
+
allowedProfiles: ['minimal', 'standard', 'strict'],
|
|
156
|
+
runner: 'node',
|
|
157
|
+
relativeScriptPath: 'scripts/hooks/aw-usage-post-tool-use.js',
|
|
158
|
+
payloadMode: 'claude',
|
|
159
|
+
},
|
|
112
160
|
],
|
|
113
161
|
'pre-compact': [
|
|
114
162
|
{
|
|
@@ -125,6 +173,8 @@ const SHARED_AW_PHASE_STEPS = Object.freeze({
|
|
|
125
173
|
relativeScriptPath: 'scripts/hooks/session-end-marker.js',
|
|
126
174
|
payloadMode: 'claude',
|
|
127
175
|
},
|
|
176
|
+
// telemetry:session-end removed — afterAgentResponse hook now emits
|
|
177
|
+
// response_completed per turn with direct token/cost data from Cursor.
|
|
128
178
|
],
|
|
129
179
|
stop: [
|
|
130
180
|
{
|
|
@@ -155,6 +205,8 @@ const SHARED_AW_PHASE_STEPS = Object.freeze({
|
|
|
155
205
|
relativeScriptPath: 'scripts/hooks/cost-tracker.js',
|
|
156
206
|
payloadMode: 'claude',
|
|
157
207
|
},
|
|
208
|
+
// telemetry:stop removed — Cursor fires both sessionEnd and stop,
|
|
209
|
+
// causing duplicate session_ended events. Telemetry is in session-end phase only.
|
|
158
210
|
],
|
|
159
211
|
});
|
|
160
212
|
|
|
@@ -167,6 +219,7 @@ function getSharedAwPhaseSteps(phaseName) {
|
|
|
167
219
|
}
|
|
168
220
|
|
|
169
221
|
module.exports = {
|
|
222
|
+
PHASE_NAMES,
|
|
170
223
|
SHARED_AW_PHASE_STEPS,
|
|
171
224
|
getSharedAwPhaseSteps,
|
|
172
225
|
};
|
|
@@ -39,7 +39,9 @@ function formatCursorSessionStartOutput(stdout, fallbackRaw) {
|
|
|
39
39
|
if (typeof additionalContext === 'string' && additionalContext.trim() !== '') {
|
|
40
40
|
return `${JSON.stringify({ additional_context: additionalContext }, null, 2)}\n`;
|
|
41
41
|
}
|
|
42
|
-
} catch {
|
|
42
|
+
} catch (_error) {
|
|
43
|
+
return fallbackRaw;
|
|
44
|
+
}
|
|
43
45
|
|
|
44
46
|
return fallbackRaw;
|
|
45
47
|
}
|
|
@@ -32,7 +32,7 @@ const AW_HOOK_PHASES = [
|
|
|
32
32
|
tier: 'core',
|
|
33
33
|
claudeEvent: 'PostToolUse',
|
|
34
34
|
codexEvent: 'PostToolUse',
|
|
35
|
-
cursorEvents: ['afterShellExecution', 'afterFileEdit', 'afterMCPExecution'],
|
|
35
|
+
cursorEvents: ['afterShellExecution', 'afterFileEdit', 'afterMCPExecution', 'postToolUse'],
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
phase: 'Stop',
|
|
@@ -53,7 +53,7 @@ const AW_HOOK_PHASES = [
|
|
|
53
53
|
tier: 'extended',
|
|
54
54
|
claudeEvent: 'PostToolUseFailure',
|
|
55
55
|
codexEvent: null,
|
|
56
|
-
cursorEvents: [],
|
|
56
|
+
cursorEvents: ['postToolUseFailure'],
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
phase: 'PreCompact',
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AW Pricing — dynamic LLM cost estimation.
|
|
3
|
+
*
|
|
4
|
+
* Resolution order:
|
|
5
|
+
* 1. In-process memory cache
|
|
6
|
+
* 2. Disk cache (~/.aw/telemetry/pricing-cache.json, 24h TTL)
|
|
7
|
+
* 3. OpenRouter /api/v1/models fetch (3s timeout, no auth)
|
|
8
|
+
* 4. Hardcoded FALLBACK_PRICING (last resort)
|
|
9
|
+
* 5. null (model not found anywhere)
|
|
10
|
+
*
|
|
11
|
+
* CJS module — consistent with existing aw-ecc hook ecosystem.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const https = require('https');
|
|
20
|
+
|
|
21
|
+
const CACHE_PATH = path.join(os.homedir(), '.aw', 'telemetry', 'pricing-cache.json');
|
|
22
|
+
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
23
|
+
const FETCH_TIMEOUT = 3000; // 3 seconds
|
|
24
|
+
const OPENROUTER_URL = 'https://openrouter.ai/api/v1/models';
|
|
25
|
+
|
|
26
|
+
// ── Hardcoded fallback (last resort when API + cache both fail) ──────
|
|
27
|
+
|
|
28
|
+
const FALLBACK_PRICING = {
|
|
29
|
+
// Anthropic Claude (latest generation pricing)
|
|
30
|
+
'haiku': { in: 1.00, out: 5.00 },
|
|
31
|
+
'sonnet': { in: 3.00, out: 15.00 },
|
|
32
|
+
'opus': { in: 5.00, out: 25.00 },
|
|
33
|
+
// OpenAI — GPT
|
|
34
|
+
'gpt-5': { in: 1.25, out: 10.00 },
|
|
35
|
+
'gpt-5-mini': { in: 0.25, out: 2.00 },
|
|
36
|
+
'gpt-4o': { in: 2.50, out: 10.00 },
|
|
37
|
+
'gpt-4o-mini': { in: 0.15, out: 0.60 },
|
|
38
|
+
'gpt-4.1': { in: 2.00, out: 8.00 },
|
|
39
|
+
'gpt-4.1-mini': { in: 0.40, out: 1.60 },
|
|
40
|
+
'gpt-4.1-nano': { in: 0.10, out: 0.40 },
|
|
41
|
+
// OpenAI — reasoning
|
|
42
|
+
'o1': { in: 15.00, out: 60.00 },
|
|
43
|
+
'o1-mini': { in: 1.10, out: 4.40 },
|
|
44
|
+
'o3': { in: 2.00, out: 8.00 },
|
|
45
|
+
'o3-mini': { in: 1.10, out: 4.40 },
|
|
46
|
+
'o4-mini': { in: 1.10, out: 4.40 },
|
|
47
|
+
// OpenAI — Codex CLI
|
|
48
|
+
'codex-mini': { in: 1.50, out: 6.00 },
|
|
49
|
+
'codex-1': { in: 1.50, out: 6.00 },
|
|
50
|
+
// OpenAI — GPT Codex (agentic coding)
|
|
51
|
+
'gpt-5.1-codex-mini': { in: 0.25, out: 2.00 },
|
|
52
|
+
'gpt-5.1-codex': { in: 1.25, out: 10.00 },
|
|
53
|
+
'gpt-5.2-codex': { in: 1.75, out: 14.00 },
|
|
54
|
+
'gpt-5.3-codex': { in: 1.75, out: 14.00 },
|
|
55
|
+
// Google Gemini
|
|
56
|
+
'gemini-2.5-pro': { in: 1.25, out: 10.00 },
|
|
57
|
+
'gemini-2.5-flash': { in: 0.30, out: 2.50 },
|
|
58
|
+
'gemini-2.5-flash-lite': { in: 0.10, out: 0.40 },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
function toNumber(value) {
|
|
64
|
+
const n = Number(value);
|
|
65
|
+
return Number.isFinite(n) ? n : 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Memory cache ─────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
let _pricingCache = null; // { models: { [key]: { in, out } }, fetched_at: string }
|
|
71
|
+
|
|
72
|
+
// ── Disk cache ───────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
function readDiskCache() {
|
|
75
|
+
try {
|
|
76
|
+
const raw = fs.readFileSync(CACHE_PATH, 'utf8');
|
|
77
|
+
const data = JSON.parse(raw);
|
|
78
|
+
if (data && data.models && data.fetched_at) {
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
} catch { /* missing or corrupt — non-blocking */ }
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function writeDiskCache(data) {
|
|
86
|
+
try {
|
|
87
|
+
fs.mkdirSync(path.dirname(CACHE_PATH), { recursive: true });
|
|
88
|
+
fs.writeFileSync(CACHE_PATH, JSON.stringify(data, null, 2) + '\n');
|
|
89
|
+
} catch { /* best effort */ }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isCacheStale(cacheData) {
|
|
93
|
+
if (!cacheData || !cacheData.fetched_at) return true;
|
|
94
|
+
const age = Date.now() - new Date(cacheData.fetched_at).getTime();
|
|
95
|
+
return age > CACHE_TTL;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── OpenRouter fetch ─────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
function normalizeOpenRouterResponse(data) {
|
|
101
|
+
const models = {};
|
|
102
|
+
if (!Array.isArray(data)) return models;
|
|
103
|
+
|
|
104
|
+
for (const entry of data) {
|
|
105
|
+
if (!entry.id || !entry.pricing) continue;
|
|
106
|
+
const prompt = parseFloat(entry.pricing.prompt);
|
|
107
|
+
const completion = parseFloat(entry.pricing.completion);
|
|
108
|
+
if (!Number.isFinite(prompt) || !Number.isFinite(completion)) continue;
|
|
109
|
+
|
|
110
|
+
// Per-token → per-1M-token
|
|
111
|
+
const inRate = prompt * 1_000_000;
|
|
112
|
+
const outRate = completion * 1_000_000;
|
|
113
|
+
|
|
114
|
+
// Store under full OpenRouter id (e.g. "anthropic/claude-3.5-sonnet")
|
|
115
|
+
models[entry.id] = { in: inRate, out: outRate };
|
|
116
|
+
|
|
117
|
+
// Also store under the model slug after the slash (e.g. "claude-3.5-sonnet")
|
|
118
|
+
const slash = entry.id.indexOf('/');
|
|
119
|
+
if (slash > 0) {
|
|
120
|
+
const slug = entry.id.substring(slash + 1);
|
|
121
|
+
// Don't overwrite if slug already exists (first provider wins)
|
|
122
|
+
if (!models[slug]) {
|
|
123
|
+
models[slug] = { in: inRate, out: outRate };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return models;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Fetch pricing from OpenRouter. Returns a Promise that resolves to
|
|
133
|
+
* { models, fetched_at } or null on failure.
|
|
134
|
+
*/
|
|
135
|
+
function fetchPricing() {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
const req = https.get(OPENROUTER_URL, { timeout: FETCH_TIMEOUT }, (res) => {
|
|
138
|
+
if (res.statusCode !== 200) {
|
|
139
|
+
res.resume(); // drain
|
|
140
|
+
resolve(null);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let body = '';
|
|
145
|
+
res.setEncoding('utf8');
|
|
146
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
147
|
+
res.on('end', () => {
|
|
148
|
+
try {
|
|
149
|
+
const json = JSON.parse(body);
|
|
150
|
+
const models = normalizeOpenRouterResponse(json.data);
|
|
151
|
+
if (Object.keys(models).length === 0) {
|
|
152
|
+
resolve(null);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const cacheData = {
|
|
156
|
+
fetched_at: new Date().toISOString(),
|
|
157
|
+
model_count: Object.keys(models).length,
|
|
158
|
+
models,
|
|
159
|
+
};
|
|
160
|
+
resolve(cacheData);
|
|
161
|
+
} catch {
|
|
162
|
+
resolve(null);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
req.on('error', () => resolve(null));
|
|
168
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Non-blocking background refresh. Fetches pricing and updates both
|
|
174
|
+
* disk and memory caches. Never throws, never blocks the caller.
|
|
175
|
+
*/
|
|
176
|
+
function refreshPricingAsync() {
|
|
177
|
+
fetchPricing().then((data) => {
|
|
178
|
+
if (data) {
|
|
179
|
+
writeDiskCache(data);
|
|
180
|
+
_pricingCache = data;
|
|
181
|
+
}
|
|
182
|
+
}).catch(() => { /* swallow — non-blocking */ });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Load pricing (sync) ─────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Returns cached pricing data or null. Never fetches synchronously.
|
|
189
|
+
* Triggers a background refresh when cache is stale.
|
|
190
|
+
*/
|
|
191
|
+
function loadPricingSync() {
|
|
192
|
+
// 1. Memory cache
|
|
193
|
+
if (_pricingCache && !isCacheStale(_pricingCache)) {
|
|
194
|
+
return _pricingCache;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 2. Disk cache
|
|
198
|
+
const diskData = readDiskCache();
|
|
199
|
+
if (diskData) {
|
|
200
|
+
_pricingCache = diskData;
|
|
201
|
+
if (isCacheStale(diskData)) {
|
|
202
|
+
// Use stale data now, refresh in background for next time
|
|
203
|
+
refreshPricingAsync();
|
|
204
|
+
}
|
|
205
|
+
return _pricingCache;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 3. No cache at all — trigger background fetch for next time
|
|
209
|
+
refreshPricingAsync();
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Model matching ───────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Fuzzy-match a model string against a pricing map.
|
|
217
|
+
* Tries exact match first, then substring includes.
|
|
218
|
+
*/
|
|
219
|
+
function findRates(model, pricingMap) {
|
|
220
|
+
if (!model || !pricingMap) return null;
|
|
221
|
+
const normalized = String(model).toLowerCase();
|
|
222
|
+
|
|
223
|
+
// Exact match
|
|
224
|
+
if (pricingMap[normalized]) return pricingMap[normalized];
|
|
225
|
+
|
|
226
|
+
// Match against keys using includes (both directions)
|
|
227
|
+
for (const [key, rates] of Object.entries(pricingMap)) {
|
|
228
|
+
const lowerKey = key.toLowerCase();
|
|
229
|
+
if (normalized.includes(lowerKey) || lowerKey.includes(normalized)) {
|
|
230
|
+
return rates;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// ── estimateCost ─────────────────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Estimate the cost in USD for a model call.
|
|
241
|
+
*
|
|
242
|
+
* @param {string} model - Model identifier (e.g. "claude-sonnet-4-20250514", "gpt-4o")
|
|
243
|
+
* @param {number} inputTokens - Total input/prompt token count (includes cache tokens)
|
|
244
|
+
* @param {number} outputTokens - Output/completion token count
|
|
245
|
+
* @param {object} [opts] - Optional cache token breakdown
|
|
246
|
+
* @param {number} [opts.cacheReadTokens] - Tokens served from cache (billed at ~10% of input rate)
|
|
247
|
+
* @param {number} [opts.cacheWriteTokens] - Tokens written to cache (billed at ~125% of input rate)
|
|
248
|
+
* @returns {number|null} Estimated cost in USD, or null if unknown model or no tokens
|
|
249
|
+
*/
|
|
250
|
+
function estimateCost(model, inputTokens, outputTokens, opts) {
|
|
251
|
+
if (!inputTokens && !outputTokens) return null;
|
|
252
|
+
|
|
253
|
+
const cacheRead = (opts && opts.cacheReadTokens) || 0;
|
|
254
|
+
const cacheWrite = (opts && opts.cacheWriteTokens) || 0;
|
|
255
|
+
|
|
256
|
+
function computeCost(rates) {
|
|
257
|
+
if (!rates) return null;
|
|
258
|
+
const inRate = rates.in;
|
|
259
|
+
const outRate = rates.out;
|
|
260
|
+
|
|
261
|
+
if (cacheRead || cacheWrite) {
|
|
262
|
+
const nonCached = Math.max(0, inputTokens - cacheRead - cacheWrite);
|
|
263
|
+
const cost =
|
|
264
|
+
(nonCached / 1_000_000) * inRate +
|
|
265
|
+
(cacheRead / 1_000_000) * (inRate * 0.1) +
|
|
266
|
+
(cacheWrite / 1_000_000) * (inRate * 1.25) +
|
|
267
|
+
(outputTokens / 1_000_000) * outRate;
|
|
268
|
+
return Math.round(cost * 1e6) / 1e6;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const cost = (inputTokens / 1_000_000) * inRate + (outputTokens / 1_000_000) * outRate;
|
|
272
|
+
return Math.round(cost * 1e6) / 1e6;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Try dynamic pricing first (OpenRouter cache)
|
|
276
|
+
const cached = loadPricingSync();
|
|
277
|
+
if (cached && cached.models) {
|
|
278
|
+
const result = computeCost(findRates(model, cached.models));
|
|
279
|
+
if (result !== null) return result;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Fallback to hardcoded pricing
|
|
283
|
+
const result = computeCost(findRates(model, FALLBACK_PRICING));
|
|
284
|
+
if (result !== null) return result;
|
|
285
|
+
|
|
286
|
+
// Model not found anywhere
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
module.exports = {
|
|
291
|
+
estimateCost,
|
|
292
|
+
toNumber,
|
|
293
|
+
loadPricingSync,
|
|
294
|
+
refreshPricingAsync,
|
|
295
|
+
FALLBACK_PRICING,
|
|
296
|
+
// Exposed for testing
|
|
297
|
+
_test: {
|
|
298
|
+
readDiskCache,
|
|
299
|
+
writeDiskCache,
|
|
300
|
+
isCacheStale,
|
|
301
|
+
normalizeOpenRouterResponse,
|
|
302
|
+
findRates,
|
|
303
|
+
CACHE_PATH,
|
|
304
|
+
CACHE_TTL,
|
|
305
|
+
},
|
|
306
|
+
};
|