@causyne/agent 0.0.0-stage → 1.10.1
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/LICENSE +102 -0
- package/README.md +31 -2
- package/SECURITY.md +24 -0
- package/bin/causyne.js +13 -0
- package/docs/ARCHITECTURE.md +63 -0
- package/docs/PROVENANCE.md +29 -0
- package/docs/SECURITY_REVIEW.md +89 -0
- package/docs/THIRD_PARTY_NOTICES.md +49 -0
- package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
- package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
- package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
- package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
- package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
- package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
- package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
- package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
- package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
- package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
- package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
- package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
- package/native-skills/android/play-store-release/SKILL.md +43 -0
- package/native-skills/android/room-and-persistence/SKILL.md +40 -0
- package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
- package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
- package/native-skills/backend/caching-strategy/SKILL.md +44 -0
- package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
- package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
- package/native-skills/backend/rest-api-design/SKILL.md +40 -0
- package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
- package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
- package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
- package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
- package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
- package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
- package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
- package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
- package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
- package/native-skills/data/metric-definition/SKILL.md +42 -0
- package/native-skills/data/notebook-to-production/SKILL.md +43 -0
- package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
- package/native-skills/database/connection-pooling/SKILL.md +42 -0
- package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
- package/native-skills/database/query-optimisation/SKILL.md +38 -0
- package/native-skills/database/safe-migrations/SKILL.md +47 -0
- package/native-skills/database/schema-design/SKILL.md +38 -0
- package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
- package/native-skills/design/colour-system-construction/SKILL.md +38 -0
- package/native-skills/design/design-direction-brief/SKILL.md +41 -0
- package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
- package/native-skills/design/responsive-strategy/SKILL.md +39 -0
- package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
- package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
- package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
- package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
- package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
- package/native-skills/devops/incident-triage/SKILL.md +38 -0
- package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
- package/native-skills/devops/observability-baseline/SKILL.md +38 -0
- package/native-skills/devops/secret-management/SKILL.md +40 -0
- package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
- package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
- package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
- package/native-skills/git/branch-strategy/SKILL.md +39 -0
- package/native-skills/git/commit-hygiene/SKILL.md +41 -0
- package/native-skills/git/history-recovery/SKILL.md +44 -0
- package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
- package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
- package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
- package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
- package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
- package/native-skills/mobile/deep-linking/SKILL.md +39 -0
- package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
- package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
- package/native-skills/mobile/push-notifications/SKILL.md +38 -0
- package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
- package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
- package/native-skills/performance/load-testing/SKILL.md +42 -0
- package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
- package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
- package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
- package/native-skills/python/async-correctness/SKILL.md +40 -0
- package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
- package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
- package/native-skills/python/environment-isolation/SKILL.md +41 -0
- package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
- package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
- package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
- package/native-skills/security/authorization-model/SKILL.md +42 -0
- package/native-skills/security/crypto-usage/SKILL.md +48 -0
- package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
- package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
- package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
- package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
- package/native-skills/security/threat-modelling/SKILL.md +46 -0
- package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
- package/native-skills/testing/property-based-testing/SKILL.md +46 -0
- package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
- package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
- package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
- package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
- package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
- package/native-skills/web/accessible-components/SKILL.md +45 -0
- package/native-skills/web/api-client-resilience/SKILL.md +39 -0
- package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
- package/native-skills/web/core-web-vitals/SKILL.md +40 -0
- package/native-skills/web/css-layout-systems/SKILL.md +41 -0
- package/native-skills/web/form-validation-ux/SKILL.md +39 -0
- package/native-skills/web/react-state-architecture/SKILL.md +41 -0
- package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
- package/package.json +68 -4
- package/src/agentLoop.js +2458 -0
- package/src/appChannel.js +109 -0
- package/src/approvalGrants.js +66 -0
- package/src/auditLog.js +52 -0
- package/src/autoUpdate.js +12 -0
- package/src/capabilities.js +36 -0
- package/src/checkpoints.js +1199 -0
- package/src/cliMode.js +638 -0
- package/src/commands.js +805 -0
- package/src/compiledSkills.js +540 -0
- package/src/config.js +130 -0
- package/src/contextManager.js +498 -0
- package/src/cortex/autoCondenser.js +192 -0
- package/src/cortex/provisioner.js +27 -0
- package/src/cortex/vectorDb.js +447 -0
- package/src/designPalettes.js +99 -0
- package/src/devMode.js +6 -0
- package/src/embodimentSession.js +180 -0
- package/src/entrypoint.js +194 -0
- package/src/execSafe.js +21 -0
- package/src/gitSnapshot.js +378 -0
- package/src/mcp/jsonRpc.js +165 -0
- package/src/mcp/mcpClient.js +136 -0
- package/src/mcp/mcpRegistry.js +207 -0
- package/src/memory.js +62 -0
- package/src/memoryManager.js +124 -0
- package/src/modelCatalogue.js +132 -0
- package/src/modelIds.js +42 -0
- package/src/modelRouter.js +145 -0
- package/src/outputStore.js +246 -0
- package/src/perception.js +124 -0
- package/src/planCache.js +437 -0
- package/src/procSignal.js +32 -0
- package/src/providers/anthropic.js +408 -0
- package/src/providers/base.js +328 -0
- package/src/providers/dashscope-byok.js +86 -0
- package/src/providers/deepseek.js +80 -0
- package/src/providers/factory.js +37 -0
- package/src/providers/gemini.js +346 -0
- package/src/providers/moonshot.js +73 -0
- package/src/providers/muse.js +169 -0
- package/src/providers/ollama.js +104 -0
- package/src/providers/openai-compatible.js +84 -0
- package/src/providers/openai.js +20 -0
- package/src/providers/qwen-byok.js +76 -0
- package/src/providers/xai.js +73 -0
- package/src/provisioning/capabilityConsent.js +24 -0
- package/src/provisioning/capabilityProvisioner.js +271 -0
- package/src/provisioning/capabilityTools.js +29 -0
- package/src/reporting.js +373 -0
- package/src/sandbox.js +529 -0
- package/src/sessionStore.js +279 -0
- package/src/skillsManager.js +288 -0
- package/src/slopDetector.js +307 -0
- package/src/taskBuffer.js +45 -0
- package/src/taskShape.js +241 -0
- package/src/textDiff.js +150 -0
- package/src/tokenTracker.js +560 -0
- package/src/tool-scripts/ax_snapshot.applescript +39 -0
- package/src/tool-scripts/ax_snapshot.ps1 +10 -0
- package/src/tool-scripts/desktop_act.applescript +58 -0
- package/src/tool-scripts/resolve_automator.py +119 -0
- package/src/toolPacks.js +524 -0
- package/src/tools/archive.js +63 -0
- package/src/tools/astryxOracle.js +78 -0
- package/src/tools/axSnapshot.js +165 -0
- package/src/tools/blenderAutomator.js +349 -0
- package/src/tools/browserAutomator.js +252 -0
- package/src/tools/checkSystemSpecs.js +64 -0
- package/src/tools/clipboard.js +36 -0
- package/src/tools/codeSearchAst.js +101 -0
- package/src/tools/codeSearcher.js +230 -0
- package/src/tools/codeTools.js +262 -0
- package/src/tools/codebaseSearch.js +75 -0
- package/src/tools/createSkill.js +129 -0
- package/src/tools/deferredMcpSearch.js +246 -0
- package/src/tools/desktopAct.js +208 -0
- package/src/tools/desktopOpen.js +34 -0
- package/src/tools/dynamicLoader.js +44 -0
- package/src/tools/executeProceduralMedia.js +93 -0
- package/src/tools/fsDelete.js +51 -0
- package/src/tools/fsEdit.js +105 -0
- package/src/tools/fsFind.js +128 -0
- package/src/tools/fsList.js +77 -0
- package/src/tools/fsMove.js +57 -0
- package/src/tools/fsRead.js +87 -0
- package/src/tools/fsSearch.js +198 -0
- package/src/tools/fsWrite.js +41 -0
- package/src/tools/gameAiBehaviorTree.js +208 -0
- package/src/tools/gameAnimationRig.js +194 -0
- package/src/tools/gameAssetFetcher.js +372 -0
- package/src/tools/gameAssetHarvester.js +210 -0
- package/src/tools/gameAudioSynth.js +267 -0
- package/src/tools/gameBuilder.js +886 -0
- package/src/tools/gameCanvasAudit.js +101 -0
- package/src/tools/gameCinematicSequencer.js +151 -0
- package/src/tools/gameDataForge.js +124 -0
- package/src/tools/gameDialogueTreeGen.js +166 -0
- package/src/tools/gameEconomyBalancer.js +161 -0
- package/src/tools/gameEntitySpawner.js +207 -0
- package/src/tools/gameLoreForge.js +153 -0
- package/src/tools/gameQuestArcBuilder.js +152 -0
- package/src/tools/gameSceneBuilder.js +158 -0
- package/src/tools/gameShaderForge.js +181 -0
- package/src/tools/gameTilemapGenerator.js +164 -0
- package/src/tools/gameUiHudBuilder.js +292 -0
- package/src/tools/gameVfxEmitterGen.js +108 -0
- package/src/tools/gameWebRuntimeInspector.js +91 -0
- package/src/tools/generateMediaViaApi.js +358 -0
- package/src/tools/gitExec.js +54 -0
- package/src/tools/godotAutomator.js +437 -0
- package/src/tools/manageLocalAiDependencies.js +220 -0
- package/src/tools/memoryWrite.js +99 -0
- package/src/tools/multiEdit.js +107 -0
- package/src/tools/palmierAutomator.js +104 -0
- package/src/tools/pdfReader.js +112 -0
- package/src/tools/persistentShell.js +148 -0
- package/src/tools/printer.js +316 -0
- package/src/tools/procTerm.js +91 -0
- package/src/tools/processList.js +52 -0
- package/src/tools/readNativeSkill.js +39 -0
- package/src/tools/registry.js +277 -0
- package/src/tools/resolveAutomator.js +102 -0
- package/src/tools/screenshot.js +75 -0
- package/src/tools/subagentOrchestrator.js +305 -0
- package/src/tools/sysRun.js +125 -0
- package/src/tools/todoWrite.js +75 -0
- package/src/tools/toolDispatch.js +127 -0
- package/src/tools/toolForge.js +124 -0
- package/src/tools/updatePlan.js +88 -0
- package/src/tools/viewImage.js +82 -0
- package/src/tools/visionInspector.js +154 -0
- package/src/tools/webFetch.js +74 -0
- package/src/tools/webSearch.js +82 -0
- package/src/tui/ask.js +164 -0
- package/src/tui/banner.js +158 -0
- package/src/tui/block.js +152 -0
- package/src/tui/chatRenderer.js +953 -0
- package/src/tui/completion.js +424 -0
- package/src/tui/dialogs.js +606 -0
- package/src/tui/diff.js +296 -0
- package/src/tui/history.js +135 -0
- package/src/tui/home.js +217 -0
- package/src/tui/invocation.js +105 -0
- package/src/tui/keys.js +443 -0
- package/src/tui/lineEditor.js +256 -0
- package/src/tui/overlay.js +256 -0
- package/src/tui/prompt.js +1258 -0
- package/src/tui/selectList.js +335 -0
- package/src/tui/stage.js +330 -0
- package/src/tui/stageSurface.js +609 -0
- package/src/tui/statusLine.js +254 -0
- package/src/tui/text.js +345 -0
- package/src/tui/theme.js +393 -0
- package/src/tui/toolDisplay.js +176 -0
- package/src/tui/transcript.js +235 -0
- package/src/tui/viewport.js +337 -0
- package/src/uncertainty.js +271 -0
- package/src/userSkills.js +265 -0
- package/src/utils/cli_ui.js +0 -0
- package/src/utils/inquirer.js +12 -0
- package/src/utils/paths.js +37 -0
- package/src/version.js +38 -0
- package/src/vfeTracker.js +220 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { loadConfig } from './config.js';
|
|
5
|
+
|
|
6
|
+
import { causyneHome } from './utils/paths.js';
|
|
7
|
+
|
|
8
|
+
const USAGE_DIR = path.join(causyneHome(), 'usage');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The date key for "today", in the USER'S timezone.
|
|
12
|
+
*
|
|
13
|
+
* This was `new Date().toISOString().split('T')[0]`, which is UTC. A daily
|
|
14
|
+
* budget bucketed in UTC does not reset at the user's midnight: in WIB (UTC+7)
|
|
15
|
+
* it rolls over at 07:00 local, in NZDT (UTC+13) at 13:00, in PST (UTC-8) the
|
|
16
|
+
* previous evening. So a user's morning work was charged to "yesterday" and
|
|
17
|
+
* their daily cap could be exhausted before their day had started — and the
|
|
18
|
+
* reset landed in the middle of it.
|
|
19
|
+
*
|
|
20
|
+
* A limit the user sets as "per day" must mean their day. Anything else is a
|
|
21
|
+
* limit they cannot reason about.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
let usageQueue = Promise.resolve();
|
|
25
|
+
function withUsageLock(fn) {
|
|
26
|
+
const run = usageQueue.then(fn, fn);
|
|
27
|
+
usageQueue = run.then(() => {}, () => {});
|
|
28
|
+
return run;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Write a usage file the way the journal does: temp file, then rename.
|
|
33
|
+
*
|
|
34
|
+
* A direct writeFileSync that is interrupted leaves truncated JSON, and
|
|
35
|
+
* getDailyUsage() answers a parse failure with a fresh zeroed record — so a
|
|
36
|
+
* torn write does not surface as an error, it silently ERASES the day's budget.
|
|
37
|
+
* rename() is atomic on the same filesystem, so a reader sees either the whole
|
|
38
|
+
* previous file or the whole new one.
|
|
39
|
+
*/
|
|
40
|
+
function writeUsageFile(file, payload) {
|
|
41
|
+
const temp = `${file}.tmp_${process.pid}`;
|
|
42
|
+
fs.writeFileSync(temp, JSON.stringify(payload, null, 2), { encoding: 'utf8', mode: 0o600 });
|
|
43
|
+
fs.renameSync(temp, file);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function localDateKey(when = new Date()) {
|
|
47
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
48
|
+
return `${when.getFullYear()}-${pad(when.getMonth() + 1)}-${pad(when.getDate())}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Input-token count above which GPT-6 Astra bills on its second rate card
|
|
52
|
+
// ($20/$75 instead of $10/$50). The higher card applies to the whole request,
|
|
53
|
+
// not to the overage alone.
|
|
54
|
+
const ASTRA_LONG_CONTEXT_THRESHOLD = 272000;
|
|
55
|
+
|
|
56
|
+
export class TokenTracker {
|
|
57
|
+
constructor() {
|
|
58
|
+
this.config = loadConfig();
|
|
59
|
+
this.budget = this.config.tokenBudget || { enabled: false };
|
|
60
|
+
this.taskTokens = 0;
|
|
61
|
+
this.taskCostUsd = 0;
|
|
62
|
+
this.largestStepTokens = 0;
|
|
63
|
+
this.ensureUsageDir();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
// Cost per 1M tokens [prompt, completion] (2026 Pricing)
|
|
69
|
+
//
|
|
70
|
+
// `promptTokens` is optional and only consulted by providers that publish a
|
|
71
|
+
// SECOND rate card above a context threshold. GPT-6 Astra is the first: past
|
|
72
|
+
// 272,000 input tokens the same request bills at $20 in / $75 out instead of
|
|
73
|
+
// $10 / $50. Ignoring that under-reported long-context runs by 2x — and
|
|
74
|
+
// under-reporting is the worse direction, because the user's real invoice
|
|
75
|
+
// then exceeds the cost shown by Causyne.
|
|
76
|
+
getPricing(promptTokens = 0) {
|
|
77
|
+
const provider = this.config.provider?.toLowerCase();
|
|
78
|
+
const model = this.config.model?.toLowerCase() || '';
|
|
79
|
+
|
|
80
|
+
if (provider === 'deepseek') {
|
|
81
|
+
if (model.includes('pro')) return [0.435, 0.87];
|
|
82
|
+
if (model.includes('flash')) return [0.14, 0.28];
|
|
83
|
+
return [0.14, 0.28]; // Default
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Meta Model API — Muse. Published rates per 1M tokens, read off the
|
|
87
|
+
// console: Standard $1.25 in / $4.25 out; Contributor $0.10 in / $0.20 out,
|
|
88
|
+
// which is the "up to 95% less than Standard" the tier advertises in
|
|
89
|
+
// exchange for training on your prompts and completions.
|
|
90
|
+
//
|
|
91
|
+
// 1.2 and 1.1 are NOT separately published. They fall back to the 1.3 rate
|
|
92
|
+
// of their own tier, which over-estimates if they are cheaper — the safe
|
|
93
|
+
// direction for something that stops a run when the budget is spent.
|
|
94
|
+
if (provider === 'muse' || provider === 'meta') {
|
|
95
|
+
if (model.includes('-contributor')) return [0.10, 0.20];
|
|
96
|
+
return [1.25, 4.25];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (provider === 'qwen' || provider === 'dashscope') {
|
|
100
|
+
if (model.includes('max')) return [1.25, 3.75];
|
|
101
|
+
if (model.includes('plus')) return [0.32, 1.28];
|
|
102
|
+
if (model.includes('flash')) return [0.19, 1.13];
|
|
103
|
+
return [0.32, 1.28]; // Default
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (provider === 'openai') {
|
|
107
|
+
// GPT-6 Astra (2026-09-03). Two rate cards: $10/$50 up to 272K input
|
|
108
|
+
// tokens, $20/$75 for a request that crosses it — the tier applies to
|
|
109
|
+
// the WHOLE request, not just the overage.
|
|
110
|
+
if (model.includes('astra')) {
|
|
111
|
+
return promptTokens > ASTRA_LONG_CONTEXT_THRESHOLD ? [20.00, 75.00] : [10.00, 50.00];
|
|
112
|
+
}
|
|
113
|
+
if (model.includes('sol')) return [5.00, 30.00];
|
|
114
|
+
if (model.includes('terra')) return [2.50, 15.00];
|
|
115
|
+
if (model.includes('luna')) return [1.00, 1.00];
|
|
116
|
+
if (model.includes('mini')) return [0.75, 4.50];
|
|
117
|
+
return [5.00, 30.00]; // Default OpenAI (Sol)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (provider === 'anthropic') {
|
|
121
|
+
// Mythos shares Fable's tier and price. Without its own branch it fell
|
|
122
|
+
// through to the Sonnet default and was billed at a FIFTH of its rate.
|
|
123
|
+
if (model.includes('fable') || model.includes('mythos')) return [10.00, 50.00];
|
|
124
|
+
if (model.includes('opus')) return [5.00, 25.00];
|
|
125
|
+
// Sonnet 4.6 is $3/$15; Sonnet 5 is $2/$10. Checked in that order
|
|
126
|
+
// because 'sonnet-4-6' also matches the generic 'sonnet' test below.
|
|
127
|
+
if (model.includes('sonnet-4-6')) return [3.00, 15.00];
|
|
128
|
+
if (model.includes('sonnet')) return [2.00, 10.00];
|
|
129
|
+
if (model.includes('haiku')) return [1.00, 5.00];
|
|
130
|
+
return [2.00, 10.00]; // Default Anthropic (Sonnet 5)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (provider === 'gemini') {
|
|
134
|
+
if (model.includes('pro')) return [2.00, 12.00];
|
|
135
|
+
if (model.includes('flash-lite')) return [0.25, 1.50];
|
|
136
|
+
// 3.6 Flash holds 3.5's $1.50 input but cuts output from $9.00 to $7.50.
|
|
137
|
+
// Checked before the generic 'flash' test, which it would also match.
|
|
138
|
+
if (model.includes('3.6-flash') || model.includes('3-6-flash')) return [1.50, 7.50];
|
|
139
|
+
if (model.includes('flash')) return [1.50, 9.00];
|
|
140
|
+
return [1.50, 9.00]; // Default Gemini
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Local Ollama cost the user $0.00 in BYOK APIs
|
|
144
|
+
if (provider === 'ollama') {
|
|
145
|
+
return [0.00, 0.00];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return [5.00, 15.00]; // Unknown BYOK provider fallback
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Prompt-cache multipliers, applied against the base input price.
|
|
152
|
+
// Anthropic: cache read 0.1x, cache write (5m) 1.25x.
|
|
153
|
+
// OpenAI / xAI: cached input 0.5x. DeepSeek disk cache: roughly 0.1x.
|
|
154
|
+
cachedInputMultiplier() {
|
|
155
|
+
const provider = this.config.provider?.toLowerCase();
|
|
156
|
+
if (provider === 'anthropic') {
|
|
157
|
+
// Fable 5.1 reprices cache reads to $0.25/MTok against a $10 base — a
|
|
158
|
+
// 0.025x discount, not the family's usual 0.1x. Charging 0.1x here
|
|
159
|
+
|
|
160
|
+
// counter. Same shape as the Muse case below: the discount is a property
|
|
161
|
+
// of the model, not the vendor.
|
|
162
|
+
const model = this.config.model?.toLowerCase() || '';
|
|
163
|
+
if (model.includes('fable-5-1')) return 0.025;
|
|
164
|
+
return 0.1;
|
|
165
|
+
}
|
|
166
|
+
if (provider === 'deepseek') return 0.1;
|
|
167
|
+
|
|
168
|
+
// Muse is the one provider whose cache discount differs by MODEL, not just
|
|
169
|
+
// by vendor: $0.15 against $1.25 on Standard is 0.12x, while $0.002 against
|
|
170
|
+
// $0.10 on Contributor is 0.02x. Collapsing both into one vendor number
|
|
171
|
+
// would over-charge a Contributor run by six times on every cached token.
|
|
172
|
+
if (provider === 'muse' || provider === 'meta') {
|
|
173
|
+
const model = this.config.model?.toLowerCase() || '';
|
|
174
|
+
return model.includes('-contributor') ? 0.02 : 0.12;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// GPT-6 Astra publishes cached input at $1.00/MTok against a $10.00 base —
|
|
178
|
+
// a 90% discount, not the 0.5x the rest of this vendor falls back to.
|
|
179
|
+
// Found while verifying the getPricing() rows above; charging 0.5x
|
|
180
|
+
// overstated every cached Astra token FIVEFOLD.
|
|
181
|
+
if (provider === 'openai') {
|
|
182
|
+
const model = this.config.model?.toLowerCase() || '';
|
|
183
|
+
if (model.includes('astra')) return 0.1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 0.5x is the long-standing OpenAI/xAI cached-input discount. It is NOT
|
|
187
|
+
// verified against the GPT-5.6 family's current rate card; left as-is
|
|
188
|
+
// rather than guessed at, since overstating is the safer error direction
|
|
189
|
+
// for a number the user reads as a bill.
|
|
190
|
+
return 0.5;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {number} promptTokens Total input tokens (cached + uncached).
|
|
195
|
+
* @param {number} completionTokens Output tokens.
|
|
196
|
+
* @param {object} usageDetail Optional { cachedPromptTokens, cacheWriteTokens }.
|
|
197
|
+
*/
|
|
198
|
+
calculateCost(promptTokens, completionTokens, usageDetail = null) {
|
|
199
|
+
// promptTokens decides the rate card on providers with a context tier.
|
|
200
|
+
const [promptPrice, completionPrice] = this.getPricing(promptTokens);
|
|
201
|
+
|
|
202
|
+
const cachedTokens = Math.min(usageDetail?.cachedPromptTokens || 0, promptTokens);
|
|
203
|
+
const writeTokens = Math.min(usageDetail?.cacheWriteTokens || 0, promptTokens - cachedTokens);
|
|
204
|
+
const fullPriceTokens = Math.max(0, promptTokens - cachedTokens - writeTokens);
|
|
205
|
+
|
|
206
|
+
const writeMultiplier = usageDetail?.cacheWriteMultiplier || 1.25;
|
|
207
|
+
const promptCost =
|
|
208
|
+
(fullPriceTokens / 1_000_000) * promptPrice +
|
|
209
|
+
(cachedTokens / 1_000_000) * promptPrice * this.cachedInputMultiplier() +
|
|
210
|
+
(writeTokens / 1_000_000) * promptPrice * writeMultiplier;
|
|
211
|
+
|
|
212
|
+
const completionCost = (completionTokens / 1_000_000) * completionPrice;
|
|
213
|
+
return promptCost + completionCost;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Cost per item (image) or per second (video_animation) in USD (2026 Pricing)
|
|
217
|
+
getMediaPricing(provider = '', model = '', mediaType = 'image') {
|
|
218
|
+
const p = provider.toLowerCase();
|
|
219
|
+
const m = model.toLowerCase();
|
|
220
|
+
|
|
221
|
+
// Muse Image is a flat per-image rate; there is no video model in the
|
|
222
|
+
// family, so a video request must not silently bill at the image rate.
|
|
223
|
+
if (p === 'muse' || p === 'meta') {
|
|
224
|
+
if (mediaType === 'video_animation') return 0;
|
|
225
|
+
return 0.01; // $0.01 per image
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (p === 'gemini') {
|
|
229
|
+
if (mediaType === 'video_animation' || m.includes('veo')) return 0.05; // $0.05 per second of Veo 2.0 video
|
|
230
|
+
if (m.includes('imagen')) return 0.03; // $0.03 per image
|
|
231
|
+
return 0.03;
|
|
232
|
+
}
|
|
233
|
+
if (p === 'openai') {
|
|
234
|
+
if (mediaType === 'video_animation' || m.includes('sora')) return 0.10; // $0.10 per second
|
|
235
|
+
if (m.includes('dall-e-3')) return 0.08; // $0.08 per HD image
|
|
236
|
+
return 0.04; // $0.04 per standard image
|
|
237
|
+
}
|
|
238
|
+
if (p === 'qwen' || p === 'dashscope') {
|
|
239
|
+
if (mediaType === 'video_animation' || m.includes('wanx') || m.includes('wan2')) return 0.04; // $0.04 per second
|
|
240
|
+
return 0.02; // $0.02 per image
|
|
241
|
+
}
|
|
242
|
+
if (p === 'pollinations' || p === 'local') {
|
|
243
|
+
return 0.00; // Free Mode D / Mode A
|
|
244
|
+
}
|
|
245
|
+
return 0.03; // Default generic media cost
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async trackMediaUsage(provider, mediaType, model, quantityUnits = 1, onWarning = null) {
|
|
249
|
+
this.config = loadConfig();
|
|
250
|
+
this.budget = this.config.tokenBudget || { enabled: false };
|
|
251
|
+
|
|
252
|
+
const unitPrice = this.getMediaPricing(provider, model, mediaType);
|
|
253
|
+
const cost = unitPrice * (quantityUnits || 1);
|
|
254
|
+
|
|
255
|
+
// Multimedia Equivalent Units (MEU): convert dollar cost to equivalent tokens ($0.000002/token ~ $2/1M tokens)
|
|
256
|
+
const equivalentTokens = Math.round(cost / 0.000002);
|
|
257
|
+
|
|
258
|
+
this.taskTokens += equivalentTokens;
|
|
259
|
+
this.taskCostUsd += cost;
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
const daily = await this.getDailyUsage();
|
|
264
|
+
daily.totalTokens += equivalentTokens;
|
|
265
|
+
daily.totalCalls += 1;
|
|
266
|
+
daily.totalCostUsd = (daily.totalCostUsd || 0) + cost;
|
|
267
|
+
await this.saveDailyUsage(daily);
|
|
268
|
+
|
|
269
|
+
const cumulative = await this.getCumulativeUsage();
|
|
270
|
+
cumulative.totalTokens += equivalentTokens;
|
|
271
|
+
cumulative.totalCalls += 1;
|
|
272
|
+
cumulative.totalCostUsd = (cumulative.totalCostUsd || 0) + cost;
|
|
273
|
+
await this.saveCumulativeUsage(cumulative);
|
|
274
|
+
|
|
275
|
+
if (!this.budget.enabled) {
|
|
276
|
+
return { allowed: true, used: daily.totalTokens, costUsd: daily.totalCostUsd, taskCostUsd: this.taskCostUsd, equivalentTokens };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Monetary Kill Switch
|
|
280
|
+
if (this.budget.maxCostUsd && daily.totalCostUsd > this.budget.maxCostUsd) {
|
|
281
|
+
if (this.budget.hardStop) {
|
|
282
|
+
return {
|
|
283
|
+
allowed: false,
|
|
284
|
+
reason: `BYOK Monetary Kill-Switch Activated: Media generation cost ($${cost.toFixed(2)}) pushes total spend to $${daily.totalCostUsd.toFixed(2)}, exceeding your $${this.budget.maxCostUsd.toFixed(2)} limit.`,
|
|
285
|
+
used: daily.totalTokens,
|
|
286
|
+
costUsd: daily.totalCostUsd,
|
|
287
|
+
taskCostUsd: this.taskCostUsd,
|
|
288
|
+
equivalentTokens
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await this.checkAndTriggerAlerts(daily, onWarning);
|
|
294
|
+
return { allowed: true, used: daily.totalTokens, costUsd: daily.totalCostUsd, taskCostUsd: this.taskCostUsd, equivalentTokens };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async checkAndTriggerAlerts(daily, onWarning = null) {
|
|
298
|
+
if (!this.budget.enabled) return;
|
|
299
|
+
|
|
300
|
+
const alertThreshold = this.budget.alertThresholdTokens
|
|
301
|
+
? Number(this.budget.alertThresholdTokens)
|
|
302
|
+
: (this.budget.maxTokensPerDay && this.budget.warningThreshold ? this.budget.maxTokensPerDay * this.budget.warningThreshold : null);
|
|
303
|
+
|
|
304
|
+
if (alertThreshold && alertThreshold > 0 && daily.totalTokens >= alertThreshold && !daily.alertSent) {
|
|
305
|
+
daily.alertSent = true;
|
|
306
|
+
await this.saveDailyUsage(daily);
|
|
307
|
+
|
|
308
|
+
const msg = `⚠️ Token Budget Alert: Your BYOK usage has reached ${daily.totalTokens.toLocaleString()} tokens (Threshold: ${alertThreshold.toLocaleString()}).`;
|
|
309
|
+
if (onWarning) {
|
|
310
|
+
onWarning(msg);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
ensureUsageDir() {
|
|
316
|
+
if (!fs.existsSync(USAGE_DIR)) {
|
|
317
|
+
fs.mkdirSync(USAGE_DIR, { recursive: true });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
getDailyFile() {
|
|
322
|
+
return path.join(USAGE_DIR, `${localDateKey()}.json`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
getCumulativeFile() {
|
|
326
|
+
return path.join(USAGE_DIR, 'byok_cumulative.json');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
async getDailyUsage() {
|
|
331
|
+
const file = this.getDailyFile();
|
|
332
|
+
if (fs.existsSync(file)) {
|
|
333
|
+
try {
|
|
334
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
335
|
+
} catch(e) {}
|
|
336
|
+
}
|
|
337
|
+
return { date: localDateKey(), totalTokens: 0, totalCalls: 0, totalCostUsd: 0 };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async getCumulativeUsage() {
|
|
341
|
+
const file = this.getCumulativeFile();
|
|
342
|
+
if (fs.existsSync(file)) {
|
|
343
|
+
try {
|
|
344
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
345
|
+
} catch(e) {}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Automatic Historical Backfill Migration (v1.1.50):
|
|
349
|
+
// Scan all past `${date}.json` files across USAGE_DIR and sum them up so past 5M+ tokens are never lost!
|
|
350
|
+
let totalTokens = 0;
|
|
351
|
+
let totalCalls = 0;
|
|
352
|
+
let totalCostUsd = 0;
|
|
353
|
+
try {
|
|
354
|
+
if (fs.existsSync(USAGE_DIR)) {
|
|
355
|
+
const files = fs.readdirSync(USAGE_DIR);
|
|
356
|
+
for (const f of files) {
|
|
357
|
+
if (/^\d{4}-\d{2}-\d{2}\.json$/.test(f)) {
|
|
358
|
+
try {
|
|
359
|
+
const data = JSON.parse(fs.readFileSync(path.join(USAGE_DIR, f), 'utf8'));
|
|
360
|
+
totalTokens += (data.totalTokens || 0);
|
|
361
|
+
totalCalls += (data.totalCalls || 0);
|
|
362
|
+
totalCostUsd += (data.totalCostUsd || 0);
|
|
363
|
+
} catch (err) {}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} catch (err) {}
|
|
368
|
+
|
|
369
|
+
const initialCumulative = { totalTokens, totalCalls, totalCostUsd, backfilled: true, lastUpdated: new Date().toISOString() };
|
|
370
|
+
try {
|
|
371
|
+
writeUsageFile(file, initialCumulative);
|
|
372
|
+
} catch (err) {}
|
|
373
|
+
return initialCumulative;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
async saveDailyUsage(usage) {
|
|
378
|
+
writeUsageFile(this.getDailyFile(), usage);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async saveCumulativeUsage(usage) {
|
|
382
|
+
writeUsageFile(this.getCumulativeFile(), usage);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
async resetDailyUsage() {
|
|
387
|
+
const file = this.getDailyFile();
|
|
388
|
+
if (fs.existsSync(file)) {
|
|
389
|
+
fs.unlinkSync(file);
|
|
390
|
+
}
|
|
391
|
+
// DO NOT reset cumulative usage here, keeping lifetime tracking separate from fresh budget.
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async resetCumulativeUsage() {
|
|
395
|
+
const file = this.getCumulativeFile();
|
|
396
|
+
if (fs.existsSync(file)) {
|
|
397
|
+
fs.unlinkSync(file);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async getReportedUsage() {
|
|
402
|
+
const daily = await this.getDailyUsage();
|
|
403
|
+
const cumulative = await this.getCumulativeUsage();
|
|
404
|
+
return {
|
|
405
|
+
usedTokens: daily.totalTokens, // UI budget tracks against daily "Fresh" setup
|
|
406
|
+
dailyTokens: daily.totalTokens,
|
|
407
|
+
taskTokens: this.taskTokens,
|
|
408
|
+
lifetimeTokens: cumulative.totalTokens,
|
|
409
|
+
costUsd: daily.totalCostUsd, // UI estimated cost tracks against daily "Fresh" setup
|
|
410
|
+
taskCostUsd: this.taskCostUsd,
|
|
411
|
+
lifetimeCostUsd: cumulative.totalCostUsd,
|
|
412
|
+
isDemo: false
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Can the loop afford ANOTHER step without breaching the task ceiling?
|
|
418
|
+
*
|
|
419
|
+
* WHY THIS EXISTS. trackUsage() checks `taskTokens > maxTokensPerTask` AFTER
|
|
420
|
+
* a call has already been paid for, so the run always overshoots by one step.
|
|
421
|
+
* With ~9,000-token steps a 299,000 ceiling lands at ~308,000 — which breaks
|
|
422
|
+
* the only promise a token cap makes. Asked BEFORE the call, using the
|
|
423
|
+
* largest step seen so far, the ceiling actually holds.
|
|
424
|
+
*
|
|
425
|
+
* The largest step rather than the average, deliberately: a cap that holds
|
|
426
|
+
* on average is a cap that fails on the step that matters.
|
|
427
|
+
*
|
|
428
|
+
* @returns {{allowed:boolean, reason?:string, used:number, limit:number, projected:number}}
|
|
429
|
+
*/
|
|
430
|
+
canAffordAnotherStep() {
|
|
431
|
+
const limit = this.budget && this.budget.enabled && this.budget.hardStop
|
|
432
|
+
? Number(this.budget.maxTokensPerTask || 0)
|
|
433
|
+
: 0;
|
|
434
|
+
if (!limit) return { allowed: true, used: this.taskTokens, limit: 0, projected: this.taskTokens };
|
|
435
|
+
|
|
436
|
+
// Before the first step there is nothing to extrapolate from; let it run.
|
|
437
|
+
const step = this.largestStepTokens || 0;
|
|
438
|
+
const projected = this.taskTokens + step;
|
|
439
|
+
if (step > 0 && projected > limit) {
|
|
440
|
+
return {
|
|
441
|
+
allowed: false,
|
|
442
|
+
reason: `Stopping before the next step would breach the task ceiling: ${this.taskTokens.toLocaleString()} used, `
|
|
443
|
+
+ `the largest step so far was ${step.toLocaleString()}, and ${projected.toLocaleString()} would exceed the ${limit.toLocaleString()} limit.`,
|
|
444
|
+
used: this.taskTokens, limit, projected
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
return { allowed: true, used: this.taskTokens, limit, projected };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
resetTask() {
|
|
451
|
+
this.taskTokens = 0;
|
|
452
|
+
this.taskCostUsd = 0;
|
|
453
|
+
this.largestStepTokens = 0;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
settle(extra, daily, cumulative) {
|
|
458
|
+
return {
|
|
459
|
+
// Daily scope — what the budget bar and the daily limiter both measure.
|
|
460
|
+
usedTokens: daily.totalTokens,
|
|
461
|
+
dailyTokens: daily.totalTokens,
|
|
462
|
+
dailyCostUsd: daily.totalCostUsd || 0,
|
|
463
|
+
dailyLimit: Number(this.budget && this.budget.maxTokensPerDay) || 0,
|
|
464
|
+
// Task scope — what this run has spent.
|
|
465
|
+
taskTokens: this.taskTokens,
|
|
466
|
+
taskCostUsd: this.taskCostUsd,
|
|
467
|
+
taskLimit: Number(this.budget && this.budget.maxTokensPerTask) || 0,
|
|
468
|
+
// Lifetime scope.
|
|
469
|
+
lifetimeTokens: cumulative.totalTokens,
|
|
470
|
+
lifetimeCostUsd: cumulative.totalCostUsd,
|
|
471
|
+
// Retained for clients built against the older frame. It has always been
|
|
472
|
+
// the daily cost on the allowed path, so it keeps that meaning here
|
|
473
|
+
// rather than acquiring a third one.
|
|
474
|
+
costUsd: daily.totalCostUsd || 0,
|
|
475
|
+
used: this.taskTokens,
|
|
476
|
+
...extra
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async trackUsage(promptTokens, completionTokens, onWarning = null, usageDetail = null) {
|
|
481
|
+
// Dynamically reload config to apply live budget updates from UI
|
|
482
|
+
this.config = loadConfig();
|
|
483
|
+
this.budget = this.config.tokenBudget || { enabled: false };
|
|
484
|
+
|
|
485
|
+
const total = (promptTokens || 0) + (completionTokens || 0);
|
|
486
|
+
this.taskTokens += total;
|
|
487
|
+
// Feeds canAffordAnotherStep(). The LARGEST step, not the average: a
|
|
488
|
+
// ceiling that holds on average is a ceiling that fails on the step that
|
|
489
|
+
// matters, and every step is bigger than the last as the window grows.
|
|
490
|
+
this.largestStepTokens = Math.max(this.largestStepTokens || 0, total);
|
|
491
|
+
|
|
492
|
+
const cost = this.calculateCost(promptTokens, completionTokens, usageDetail);
|
|
493
|
+
this.taskCostUsd += cost;
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
// Normal BYOK Tracking.
|
|
498
|
+
//
|
|
499
|
+
// Read-modify-write, so it runs inside the lock. Outside it, a condenser
|
|
500
|
+
// pass firing concurrently with the loop silently subtracts its own tokens
|
|
501
|
+
// from the day's total.
|
|
502
|
+
const { daily, cumulative } = await withUsageLock(async () => {
|
|
503
|
+
const daily = await this.getDailyUsage();
|
|
504
|
+
daily.totalTokens += total;
|
|
505
|
+
daily.totalCalls += 1;
|
|
506
|
+
daily.totalCostUsd = (daily.totalCostUsd || 0) + cost;
|
|
507
|
+
await this.saveDailyUsage(daily);
|
|
508
|
+
|
|
509
|
+
const cumulative = await this.getCumulativeUsage();
|
|
510
|
+
cumulative.totalTokens += total;
|
|
511
|
+
cumulative.totalCalls += 1;
|
|
512
|
+
cumulative.totalCostUsd = (cumulative.totalCostUsd || 0) + cost;
|
|
513
|
+
await this.saveCumulativeUsage(cumulative);
|
|
514
|
+
|
|
515
|
+
return { daily, cumulative };
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
if (!this.budget.enabled) {
|
|
519
|
+
return this.settle({ allowed: true }, daily, cumulative);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// BYOK Monetary Kill Switch
|
|
523
|
+
if (this.budget.maxCostUsd && daily.totalCostUsd > this.budget.maxCostUsd) {
|
|
524
|
+
if (this.budget.hardStop) {
|
|
525
|
+
return this.settle({
|
|
526
|
+
allowed: false,
|
|
527
|
+
reason: `BYOK Monetary Kill-Switch Activated: You have spent $${daily.totalCostUsd.toFixed(2)}, which exceeds your $${this.budget.maxCostUsd.toFixed(2)} limit.`
|
|
528
|
+
}, daily, cumulative);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Check task budget
|
|
533
|
+
if (this.budget.maxTokensPerTask && this.taskTokens > this.budget.maxTokensPerTask) {
|
|
534
|
+
if (this.budget.hardStop) {
|
|
535
|
+
return this.settle({
|
|
536
|
+
allowed: false,
|
|
537
|
+
reason: `Task token budget reached: ${this.taskTokens.toLocaleString()} of ${Number(this.budget.maxTokensPerTask).toLocaleString()} tokens for this task.`,
|
|
538
|
+
limit: this.budget.maxTokensPerTask
|
|
539
|
+
}, daily, cumulative);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Check daily budget. This limit was previously accepted from the UI and
|
|
544
|
+
// used only to derive an alert threshold — it never actually stopped
|
|
545
|
+
// anything, so a runaway task could blow straight through it.
|
|
546
|
+
if (this.budget.maxTokensPerDay && daily.totalTokens > this.budget.maxTokensPerDay) {
|
|
547
|
+
if (this.budget.hardStop) {
|
|
548
|
+
return this.settle({
|
|
549
|
+
allowed: false,
|
|
550
|
+
reason: `Daily token budget reached: ${daily.totalTokens.toLocaleString()} of ${Number(this.budget.maxTokensPerDay).toLocaleString()} tokens used today.`,
|
|
551
|
+
limit: this.budget.maxTokensPerDay
|
|
552
|
+
}, daily, cumulative);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
await this.checkAndTriggerAlerts(daily, onWarning);
|
|
557
|
+
|
|
558
|
+
return this.settle({ allowed: true }, daily, cumulative);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
on run argv
|
|
2
|
+
set targetApp to item 1 of argv
|
|
3
|
+
set maxNodes to (item 2 of argv) as integer
|
|
4
|
+
set out to ""
|
|
5
|
+
set n to 0
|
|
6
|
+
tell application "System Events"
|
|
7
|
+
if targetApp is "" then
|
|
8
|
+
set proc to first application process whose frontmost is true
|
|
9
|
+
else
|
|
10
|
+
set proc to first application process whose name is targetApp
|
|
11
|
+
end if
|
|
12
|
+
set out to "app|" & (name of proc) & linefeed
|
|
13
|
+
repeat with w in (windows of proc)
|
|
14
|
+
if n is greater than or equal to maxNodes then exit repeat
|
|
15
|
+
set out to out & "window|" & (name of w) & "||" & linefeed
|
|
16
|
+
set n to n + 1
|
|
17
|
+
repeat with e in (entire contents of w)
|
|
18
|
+
if n is greater than or equal to maxNodes then exit repeat
|
|
19
|
+
set r to ""
|
|
20
|
+
set t to ""
|
|
21
|
+
set v to ""
|
|
22
|
+
try
|
|
23
|
+
set r to (class of e) as string
|
|
24
|
+
end try
|
|
25
|
+
try
|
|
26
|
+
set t to (name of e) as string
|
|
27
|
+
end try
|
|
28
|
+
try
|
|
29
|
+
set v to (value of e) as string
|
|
30
|
+
end try
|
|
31
|
+
if t is not "" or v is not "" then
|
|
32
|
+
set out to out & r & "|" & t & "|" & v & linefeed
|
|
33
|
+
set n to n + 1
|
|
34
|
+
end if
|
|
35
|
+
end repeat
|
|
36
|
+
end repeat
|
|
37
|
+
end tell
|
|
38
|
+
return out
|
|
39
|
+
end run
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Add-Type -AssemblyName UIAutomationClient,UIAutomationTypes
|
|
2
|
+
$max = [int]$args[0]
|
|
3
|
+
$root = [System.Windows.Automation.AutomationElement]::RootElement
|
|
4
|
+
$cond = [System.Windows.Automation.Condition]::TrueCondition
|
|
5
|
+
$n = 0
|
|
6
|
+
foreach ($e in $root.FindAll([System.Windows.Automation.TreeScope]::Descendants, $cond)) {
|
|
7
|
+
if ($n -ge $max) { break }
|
|
8
|
+
$name = $e.Current.Name
|
|
9
|
+
if ($name) { Write-Output ("" + $e.Current.ControlType.ProgrammaticName + "|" + $name + "|"); $n++ }
|
|
10
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
on run argv
|
|
2
|
+
set appName to item 1 of argv
|
|
3
|
+
set report to ""
|
|
4
|
+
tell application "System Events"
|
|
5
|
+
if appName is not "" then
|
|
6
|
+
set proc to first application process whose name is appName
|
|
7
|
+
set frontmost of proc to true
|
|
8
|
+
delay 0.2
|
|
9
|
+
else
|
|
10
|
+
set proc to first application process whose frontmost is true
|
|
11
|
+
end if
|
|
12
|
+
repeat with i from 2 to (count of argv)
|
|
13
|
+
set step to item i of argv
|
|
14
|
+
set AppleScript's text item delimiters to "::"
|
|
15
|
+
set parts to text items of step
|
|
16
|
+
set AppleScript's text item delimiters to ""
|
|
17
|
+
set verb to item 1 of parts
|
|
18
|
+
if verb is "press" then
|
|
19
|
+
set elemName to item 2 of parts
|
|
20
|
+
set hit to false
|
|
21
|
+
repeat with w in (windows of proc)
|
|
22
|
+
repeat with e in (entire contents of w)
|
|
23
|
+
if hit is false then
|
|
24
|
+
try
|
|
25
|
+
if (name of e) is elemName then
|
|
26
|
+
perform action "AXPress" of e
|
|
27
|
+
set hit to true
|
|
28
|
+
end if
|
|
29
|
+
end try
|
|
30
|
+
end if
|
|
31
|
+
end repeat
|
|
32
|
+
end repeat
|
|
33
|
+
if hit then
|
|
34
|
+
set report to report & "ok|press|" & elemName & linefeed
|
|
35
|
+
else
|
|
36
|
+
set report to report & "miss|press|" & elemName & linefeed
|
|
37
|
+
end if
|
|
38
|
+
else if verb is "clickxy" then
|
|
39
|
+
set px to (item 2 of parts) as integer
|
|
40
|
+
set py to (item 3 of parts) as integer
|
|
41
|
+
click at {px, py}
|
|
42
|
+
set report to report & "ok|clickxy|" & px & "," & py & linefeed
|
|
43
|
+
else if verb is "type" then
|
|
44
|
+
keystroke (item 2 of parts)
|
|
45
|
+
set report to report & "ok|type|" & (length of (item 2 of parts)) & " chars" & linefeed
|
|
46
|
+
else if verb is "key" then
|
|
47
|
+
key code ((item 2 of parts) as integer)
|
|
48
|
+
set report to report & "ok|key|" & (item 2 of parts) & linefeed
|
|
49
|
+
else if verb is "wait" then
|
|
50
|
+
delay ((item 2 of parts) as number)
|
|
51
|
+
set report to report & "ok|wait|" & (item 2 of parts) & linefeed
|
|
52
|
+
else
|
|
53
|
+
set report to report & "err|unknown verb|" & verb & linefeed
|
|
54
|
+
end if
|
|
55
|
+
end repeat
|
|
56
|
+
end tell
|
|
57
|
+
return report
|
|
58
|
+
end run
|