@kinetica/admin-agent 0.1.2 → 0.1.3
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/README.md +22 -13
- package/dist/admin-agent.js +190 -25
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -161,14 +161,15 @@ wasting ~28.7 MB as raw storage. Both issues have been remediated.
|
|
|
161
161
|
|
|
162
162
|
Set environment variables or use a `.env` file. The agent loads `.env` automatically at startup (shell-set variables always take precedence). Any missing values are prompted interactively.
|
|
163
163
|
|
|
164
|
-
| Variable
|
|
165
|
-
|
|
|
166
|
-
| `ANTHROPIC_API_KEY`
|
|
167
|
-
| `
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
164
|
+
| Variable | Description | Required |
|
|
165
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------ | ----------------------------------------------- |
|
|
166
|
+
| `ANTHROPIC_API_KEY` | Anthropic API key for Claude | No — OAuth login used if unset |
|
|
167
|
+
| `ADMIN_AGENT_MAX_BUDGET` | Per-session budget cap in USD for API-key billing (overridden by `--max-budget`; default `5.00`) | No |
|
|
168
|
+
| `KINETICA_URL` | Kinetica instance URL (e.g. `http://host:9191` or bare `host:9191`) | Prompted if unset |
|
|
169
|
+
| `KINETICA_USER` | Kinetica username | Prompted if unset |
|
|
170
|
+
| `KINETICA_PASS` | Kinetica password | Prompted if unset (masked, never saved to .env) |
|
|
171
|
+
| `KINETICA_HTTPS_ONLY` | Set to `1` to refuse plaintext HTTP fallback entirely — strict mode for production clusters | No |
|
|
172
|
+
| `DEBUG` | Set to `1` to log HTTP requests and the assembled system-prompt token size to stderr | No |
|
|
172
173
|
|
|
173
174
|
```bash
|
|
174
175
|
cp .env.example .env # fill in values — or let the agent create it for you
|
|
@@ -208,7 +209,12 @@ npm run dev -- --logout
|
|
|
208
209
|
|
|
209
210
|
### Session Budget
|
|
210
211
|
|
|
211
|
-
Each session
|
|
212
|
+
Each session has a **budget guard** to prevent runaway spend. Its form depends on how you authenticate with Anthropic:
|
|
213
|
+
|
|
214
|
+
- **API-key billing** — the session enforces a dollar cap (default **$5.00**). Raise it with the `--max-budget=<USD>` flag or the `ADMIN_AGENT_MAX_BUDGET` environment variable (the flag wins when both are set). When estimated spend crosses ~80% of the cap, the agent warns on stderr and is instructed to save a partial report and wind down. If the cap is reached, the session ends with a message showing how to re-run with more headroom — and any report saved up to that point remains in `reports/`.
|
|
215
|
+
- **OAuth (Claude Pro/Max subscription)** — no dollar cap is imposed (you are not billed per token). The session is bounded by the **turn limit** (100 turns) instead.
|
|
216
|
+
|
|
217
|
+
The active guard is printed at startup, and the session summary reports per-investigation and total spend (API-key billing only). The dollar cap is enforced precisely by the Claude Agent SDK; the ~80% warning is an estimate from per-turn token usage, so it is approximate by design.
|
|
212
218
|
|
|
213
219
|
### Degraded Mode
|
|
214
220
|
|
|
@@ -225,9 +231,12 @@ admin-agent --login-method=TYPE # Login method: claudeai (Pro/Max) or console
|
|
|
225
231
|
admin-agent --login-org=UUID # Target organization UUID for OAuth
|
|
226
232
|
admin-agent --logout # Log out from Anthropic account and exit
|
|
227
233
|
admin-agent --model=NAME # Override agent model (sonnet | haiku | opus); default: sonnet
|
|
234
|
+
admin-agent --max-budget=USD # Per-session budget cap in USD (API-key billing only); default: 5.00
|
|
228
235
|
```
|
|
229
236
|
|
|
230
|
-
The `--model` flag swaps the primary model for a single session. `haiku` is cheaper and faster for simple triage; `opus` is slower and more expensive but produces deeper reasoning on complex investigations. The fallback model remains `haiku` regardless of the primary choice, so availability is unchanged.
|
|
237
|
+
The `--model` flag swaps the primary model for a single session. `haiku` is cheaper and faster for simple triage; `opus` is slower and more expensive but produces deeper reasoning on complex investigations. The fallback model remains `haiku` regardless of the primary choice, so availability is unchanged. When you omit `--model` in an interactive terminal, the agent shows a startup picker (defaulting to `sonnet`); non-interactive runs use the default without prompting.
|
|
238
|
+
|
|
239
|
+
The `--max-budget` flag sets the per-session dollar cap for API-key billing (see [Session Budget](#session-budget)). It overrides `ADMIN_AGENT_MAX_BUDGET` and has no effect under OAuth subscription billing, which is turn-limited instead.
|
|
231
240
|
|
|
232
241
|
## Tools
|
|
233
242
|
|
|
@@ -306,7 +315,7 @@ The agent is designed with defense-in-depth for database administration:
|
|
|
306
315
|
- **Two-step approval for batch column alter** — `kinetica_alter_table_columns` requires the operator to select columns via a checklist, then confirm the exact SQL preview
|
|
307
316
|
- **Audit trail** — every mutation logs a redacted audit line to stderr (EXECUTED/FAILED + fingerprinted input summary) and appears in the report's "Mutations Applied" table with before/after state
|
|
308
317
|
- **Report scrubbing** — saved reports are scrubbed of URLs, auth headers, Basic/Bearer credentials, cookies, and passwords before writing to disk
|
|
309
|
-
- **Budget
|
|
318
|
+
- **Budget guard** — a per-session dollar cap (default $5.00, configurable via `--max-budget` or `ADMIN_AGENT_MAX_BUDGET`) prevents runaway spend on API-key billing; OAuth subscription sessions are bounded by the turn limit instead
|
|
310
319
|
|
|
311
320
|
To report a security vulnerability, please see [SECURITY.md](SECURITY.md). Do not open a public GitHub issue for security issues.
|
|
312
321
|
|
|
@@ -375,7 +384,7 @@ References provide domain knowledge (not diagnostic runbooks). Create a `.md` fi
|
|
|
375
384
|
- `sql-create-index` — column index syntax, chunk skip index, when to use which
|
|
376
385
|
- `version-quirks-7.2` — endpoint/property differences between 7.2.x and earlier releases
|
|
377
386
|
|
|
378
|
-
> **Heads up — prompt budget:** all playbooks and references are front-loaded into a single system prompt at startup, so its token cost grows with the knowledge corpus. A startup tripwire (`agent/prompt-budget.ts`) prints the assembled prompt size under `DEBUG` and warns on stderr once it exceeds ~
|
|
387
|
+
> **Heads up — prompt budget:** all playbooks and references are front-loaded into a single system prompt at startup, so its token cost grows with the knowledge corpus. A startup tripwire (`agent/prompt-budget.ts`) prints the assembled prompt size under `DEBUG` and warns on stderr once it exceeds ~20,000 estimated tokens. Current baseline is ~13.4k tokens (6 playbooks + 9 references). If you add substantial knowledge and trip that warning, treat it as the cue to switch from "load everything" to keyword-based playbook selection.
|
|
379
388
|
|
|
380
389
|
## Development
|
|
381
390
|
|
|
@@ -472,7 +481,7 @@ admin-agent
|
|
|
472
481
|
|
|
473
482
|
**Agent hits budget cap**
|
|
474
483
|
|
|
475
|
-
-
|
|
484
|
+
- Applies to API-key billing only (default $5.00 per session). Raise it for the next run with `--max-budget=10` or `export ADMIN_AGENT_MAX_BUDGET=10`. The agent warns at ~80% so it can save a partial report before the cap is reached. For complex multi-table investigations, consider running focused sessions per table. OAuth (Pro/Max) sessions are turn-limited rather than dollar-capped.
|
|
476
485
|
|
|
477
486
|
**Empty or missing report**
|
|
478
487
|
|
package/dist/admin-agent.js
CHANGED
|
@@ -111,7 +111,7 @@ function printBanner(model) {
|
|
|
111
111
|
const version = getVersion();
|
|
112
112
|
const subtitle = `admin-agent ${import_picocolors.default.dim(`v${version}`)}`;
|
|
113
113
|
const header = model ? `${subtitle}
|
|
114
|
-
${import_picocolors.default.dim(`
|
|
114
|
+
${import_picocolors.default.dim(`Model: ${model}`)}` : subtitle;
|
|
115
115
|
process.stderr.write("\n\n" + gradientize(LOGO) + "\n\n" + header + "\n");
|
|
116
116
|
return subtitle;
|
|
117
117
|
}
|
|
@@ -3550,6 +3550,13 @@ Monitor your context window usage during long investigations:
|
|
|
3550
3550
|
- If you detect that context is getting full (many rounds, many large tool responses), warn the operator: "The session context is getting long. Consider starting a fresh session after this report to maintain investigation quality. Your reports are saved to disk."
|
|
3551
3551
|
- Do NOT continue investigating when context is too full \u2014 write the report with evidence gathered so far.
|
|
3552
3552
|
|
|
3553
|
+
## Budget & Length Awareness
|
|
3554
|
+
|
|
3555
|
+
The session has a per-session budget guard that can end the run before you finish \u2014 and the operator may see a warning that you are "approaching the budget guard". To make sure a diagnostic always survives an early cutoff:
|
|
3556
|
+
- During a long or expensive investigation (many rounds or many large tool responses), proactively call \`save_report\` with \`partial: true\` to checkpoint your findings so far. A partial report is far better than none.
|
|
3557
|
+
- If the operator warns you that the budget guard is approaching, STOP gathering new evidence: immediately save a \`partial: true\` report with the evidence you have, state your best current hypothesis, and wind down the turn.
|
|
3558
|
+
- Treat the guard as a normal limit, not an error \u2014 never apologize for it; just preserve the work.
|
|
3559
|
+
|
|
3553
3560
|
---
|
|
3554
3561
|
|
|
3555
3562
|
## Output Formatting
|
|
@@ -3678,6 +3685,69 @@ function checkPromptBudget(prompt, opts) {
|
|
|
3678
3685
|
};
|
|
3679
3686
|
}
|
|
3680
3687
|
|
|
3688
|
+
// src/agent/session-budget.ts
|
|
3689
|
+
var DEFAULT_MAX_BUDGET_USD = 5;
|
|
3690
|
+
var DEFAULT_WARN_FRACTION = 0.8;
|
|
3691
|
+
var BUDGET_ENV_VAR = "ADMIN_AGENT_MAX_BUDGET";
|
|
3692
|
+
var MODEL_PRICING = {
|
|
3693
|
+
sonnet: { inputPerMTok: 3, outputPerMTok: 15, cacheReadPerMTok: 0.3, cacheCreationPerMTok: 3.75 },
|
|
3694
|
+
haiku: { inputPerMTok: 1, outputPerMTok: 5, cacheReadPerMTok: 0.1, cacheCreationPerMTok: 1.25 },
|
|
3695
|
+
opus: { inputPerMTok: 15, outputPerMTok: 75, cacheReadPerMTok: 1.5, cacheCreationPerMTok: 18.75 }
|
|
3696
|
+
};
|
|
3697
|
+
function fromSdkUsage(raw) {
|
|
3698
|
+
const u = raw ?? {};
|
|
3699
|
+
return {
|
|
3700
|
+
inputTokens: u.input_tokens,
|
|
3701
|
+
outputTokens: u.output_tokens,
|
|
3702
|
+
cacheReadInputTokens: u.cache_read_input_tokens,
|
|
3703
|
+
cacheCreationInputTokens: u.cache_creation_input_tokens
|
|
3704
|
+
};
|
|
3705
|
+
}
|
|
3706
|
+
function safeCount(value) {
|
|
3707
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
3708
|
+
}
|
|
3709
|
+
function isValidBudget(value) {
|
|
3710
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
3711
|
+
}
|
|
3712
|
+
function estimateTurnCostUsd(usage, model) {
|
|
3713
|
+
if (!usage) return 0;
|
|
3714
|
+
const price = MODEL_PRICING[model];
|
|
3715
|
+
const input5 = safeCount(usage.inputTokens);
|
|
3716
|
+
const output = safeCount(usage.outputTokens);
|
|
3717
|
+
const cacheRead = safeCount(usage.cacheReadInputTokens);
|
|
3718
|
+
const cacheCreation = safeCount(usage.cacheCreationInputTokens);
|
|
3719
|
+
return (input5 * price.inputPerMTok + output * price.outputPerMTok + cacheRead * price.cacheReadPerMTok + cacheCreation * price.cacheCreationPerMTok) / 1e6;
|
|
3720
|
+
}
|
|
3721
|
+
function resolveMaxBudgetUsd(flagValue, env = process.env) {
|
|
3722
|
+
if (isValidBudget(flagValue)) return flagValue;
|
|
3723
|
+
const raw = env[BUDGET_ENV_VAR];
|
|
3724
|
+
if (raw !== void 0 && raw !== "") {
|
|
3725
|
+
const parsed = Number(raw);
|
|
3726
|
+
if (isValidBudget(parsed)) return parsed;
|
|
3727
|
+
}
|
|
3728
|
+
return DEFAULT_MAX_BUDGET_USD;
|
|
3729
|
+
}
|
|
3730
|
+
function createBudgetTracker(opts) {
|
|
3731
|
+
const warnFraction = opts.warnFraction ?? DEFAULT_WARN_FRACTION;
|
|
3732
|
+
const warnAt = opts.maxUsd * warnFraction;
|
|
3733
|
+
let spent = 0;
|
|
3734
|
+
let warned = false;
|
|
3735
|
+
return {
|
|
3736
|
+
add(usage, model) {
|
|
3737
|
+
spent += estimateTurnCostUsd(usage, model);
|
|
3738
|
+
},
|
|
3739
|
+
spentUsd() {
|
|
3740
|
+
return spent;
|
|
3741
|
+
},
|
|
3742
|
+
shouldWarn() {
|
|
3743
|
+
return !warned && spent > warnAt;
|
|
3744
|
+
},
|
|
3745
|
+
markWarned() {
|
|
3746
|
+
warned = true;
|
|
3747
|
+
}
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3681
3751
|
// src/report/save-report.ts
|
|
3682
3752
|
var import_promises3 = require("fs/promises");
|
|
3683
3753
|
var import_node_path4 = require("path");
|
|
@@ -3970,13 +4040,29 @@ function createSpinner() {
|
|
|
3970
4040
|
|
|
3971
4041
|
// src/agent/run-agent.ts
|
|
3972
4042
|
var MCP_SERVER_NAME = "kinetica-diagnostics";
|
|
4043
|
+
var SAVE_REPORT_TOOL_NAME = `mcp__${MCP_SERVER_NAME}__save_report`;
|
|
4044
|
+
function contentCallsSaveReport(content) {
|
|
4045
|
+
if (!Array.isArray(content)) return false;
|
|
4046
|
+
return content.some((block) => {
|
|
4047
|
+
if (typeof block !== "object" || block === null) return false;
|
|
4048
|
+
const { type, name } = block;
|
|
4049
|
+
return type === "tool_use" && name === SAVE_REPORT_TOOL_NAME;
|
|
4050
|
+
});
|
|
4051
|
+
}
|
|
4052
|
+
function formatCostSuffix(costUsd) {
|
|
4053
|
+
return costUsd !== void 0 && costUsd > 0 ? ` Cost: $${costUsd.toFixed(4)}.` : "";
|
|
4054
|
+
}
|
|
4055
|
+
function formatMetricsLine(turns, durationMs, durationApiMs, costUsd) {
|
|
4056
|
+
const durationSec = Math.round(durationMs / 1e3);
|
|
4057
|
+
const apiPct = durationMs > 0 ? Math.round(durationApiMs / durationMs * 100) : 0;
|
|
4058
|
+
return `Turns: ${turns}. Duration: ${durationSec}s (${apiPct}% API).${formatCostSuffix(costUsd)}`;
|
|
4059
|
+
}
|
|
3973
4060
|
var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "end", "q"]);
|
|
3974
4061
|
var SUPPORTED_MODELS = ["sonnet", "haiku", "opus"];
|
|
3975
4062
|
var DEFAULT_AGENT_MODEL = "sonnet";
|
|
3976
|
-
var DEFAULT_MAX_BUDGET_USD = 5;
|
|
3977
4063
|
var ALLOWED_TOOL_NAMES = [
|
|
3978
4064
|
...DIAGNOSTIC_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
|
|
3979
|
-
|
|
4065
|
+
SAVE_REPORT_TOOL_NAME,
|
|
3980
4066
|
`mcp__${MCP_SERVER_NAME}__${ALTER_TABLE_COLUMNS_TOOL_NAME}`
|
|
3981
4067
|
];
|
|
3982
4068
|
var DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "MultiEdit"];
|
|
@@ -4070,7 +4156,10 @@ async function displayDegradedStatus(session2) {
|
|
|
4070
4156
|
}
|
|
4071
4157
|
process.stderr.write("\n");
|
|
4072
4158
|
}
|
|
4073
|
-
async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
4159
|
+
async function runAgent(session2, kineticaVersion, degraded, model, runOptions) {
|
|
4160
|
+
const authMethod = runOptions?.authMethod ?? "api_key";
|
|
4161
|
+
const dollarCapped = authMethod === "api_key";
|
|
4162
|
+
const resolvedBudgetUsd = runOptions?.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD;
|
|
4074
4163
|
const [catalogSchemas, playbooks, references] = await Promise.all([
|
|
4075
4164
|
degraded ? Promise.resolve(void 0) : discoverCatalogSchemas(session2),
|
|
4076
4165
|
loadPlaybooks(),
|
|
@@ -4086,7 +4175,7 @@ async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
|
4086
4175
|
const budget = checkPromptBudget(systemPrompt);
|
|
4087
4176
|
if (process.env.DEBUG) {
|
|
4088
4177
|
process.stderr.write(
|
|
4089
|
-
import_picocolors8.default.dim(`
|
|
4178
|
+
import_picocolors8.default.dim(`System prompt: ~${budget.tokens} tokens (${budget.chars} chars)
|
|
4090
4179
|
`)
|
|
4091
4180
|
);
|
|
4092
4181
|
}
|
|
@@ -4126,23 +4215,28 @@ async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
|
4126
4215
|
fallbackModel: "haiku",
|
|
4127
4216
|
thinking: { type: "adaptive" },
|
|
4128
4217
|
maxTurns: 100,
|
|
4129
|
-
|
|
4218
|
+
// Only impose a dollar cap for per-token billing. For OAuth subscription users
|
|
4219
|
+
// the SDK would otherwise cut them off at a notional dollar figure they never pay;
|
|
4220
|
+
// omitting it leaves the turn limit (maxTurns) as their guard.
|
|
4221
|
+
...dollarCapped ? { maxBudgetUsd: resolvedBudgetUsd } : {},
|
|
4130
4222
|
persistSession: false,
|
|
4131
4223
|
includePartialMessages: true,
|
|
4132
4224
|
abortController,
|
|
4133
4225
|
env: { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" }
|
|
4134
4226
|
};
|
|
4227
|
+
const guardLine = dollarCapped ? import_picocolors8.default.dim(`Budget guard: $${resolvedBudgetUsd.toFixed(2)} (raise with --max-budget)
|
|
4228
|
+
`) : import_picocolors8.default.dim("Budget guard: subscription (Pro/Max) \u2014 turn-limited\n");
|
|
4135
4229
|
if (degraded) {
|
|
4136
4230
|
process.stderr.write("\nKinetica Diagnostic Session Ready (DEGRADED MODE)\n");
|
|
4137
4231
|
process.stderr.write(
|
|
4138
4232
|
"DB engine (port 9191) is unreachable. Only host manager tools are available.\n\n"
|
|
4139
4233
|
);
|
|
4140
4234
|
await displayDegradedStatus(session2);
|
|
4141
|
-
process.stderr.write("Type 'exit' to end the session.\n\n");
|
|
4142
4235
|
} else {
|
|
4143
4236
|
process.stderr.write("\nKinetica Diagnostic Session Ready\n");
|
|
4144
|
-
process.stderr.write("Type 'exit' to end the session.\n\n");
|
|
4145
4237
|
}
|
|
4238
|
+
process.stderr.write(guardLine);
|
|
4239
|
+
process.stderr.write("Type 'exit' to end the session.\n\n");
|
|
4146
4240
|
const turnGate = createTurnGate();
|
|
4147
4241
|
const agentQuery = (0, import_claude_agent_sdk4.query)({
|
|
4148
4242
|
prompt: makeInteractivePrompt(abortController, turnGate, spinner),
|
|
@@ -4163,6 +4257,9 @@ async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
|
4163
4257
|
let lastStreamCharWasNewline = true;
|
|
4164
4258
|
const tableAligner = createStreamingTableAligner();
|
|
4165
4259
|
let hadNonAbortError = false;
|
|
4260
|
+
let reportSavedThisRun = false;
|
|
4261
|
+
let invBase = { turns: 0, duration: 0, api: 0, cost: 0 };
|
|
4262
|
+
const budgetTracker = dollarCapped ? createBudgetTracker({ maxUsd: resolvedBudgetUsd }) : void 0;
|
|
4166
4263
|
try {
|
|
4167
4264
|
for await (const message of agentQuery) {
|
|
4168
4265
|
if (message.type === "stream_event") {
|
|
@@ -4189,6 +4286,23 @@ async function runAgent(session2, kineticaVersion, degraded, model) {
|
|
|
4189
4286
|
process.stderr.write("\n");
|
|
4190
4287
|
lastStreamCharWasNewline = true;
|
|
4191
4288
|
}
|
|
4289
|
+
if (budgetTracker) {
|
|
4290
|
+
budgetTracker.add(fromSdkUsage(assistantMsg.message.usage), effectiveModel);
|
|
4291
|
+
if (budgetTracker.shouldWarn()) {
|
|
4292
|
+
spinner.stop();
|
|
4293
|
+
process.stderr.write(
|
|
4294
|
+
import_picocolors8.default.yellow(
|
|
4295
|
+
`
|
|
4296
|
+
\u26A0 Approaching budget guard (~$${budgetTracker.spentUsd().toFixed(2)} / $${resolvedBudgetUsd.toFixed(2)}) \u2014 wrapping up soon. Save a partial report now if you want to preserve findings.
|
|
4297
|
+
`
|
|
4298
|
+
)
|
|
4299
|
+
);
|
|
4300
|
+
budgetTracker.markWarned();
|
|
4301
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
if (contentCallsSaveReport(assistantMsg.message.content)) {
|
|
4304
|
+
reportSavedThisRun = true;
|
|
4305
|
+
}
|
|
4192
4306
|
if (assistantMsg.message.stop_reason === "end_turn") {
|
|
4193
4307
|
spinner.stop();
|
|
4194
4308
|
turnGate.open();
|
|
@@ -4217,10 +4331,21 @@ API error: ${label}
|
|
|
4217
4331
|
cacheCreationTokens = usages.reduce((sum, u) => sum + (u.cacheCreationInputTokens ?? 0), 0);
|
|
4218
4332
|
if (resultMsg.subtype === "error_max_turns") {
|
|
4219
4333
|
process.stderr.write(
|
|
4220
|
-
|
|
4334
|
+
import_picocolors8.default.yellow(
|
|
4335
|
+
`
|
|
4336
|
+
Reached the turn limit (${numTurns} turns) \u2014 a safety guard, not an error. Any report the agent saved is in reports/. Start a fresh session to continue.
|
|
4337
|
+
`
|
|
4338
|
+
)
|
|
4221
4339
|
);
|
|
4222
4340
|
} else if (resultMsg.subtype === "error_max_budget_usd") {
|
|
4223
|
-
|
|
4341
|
+
const spentStr = totalCostUsd > 0 ? ` ($${totalCostUsd.toFixed(2)} spent)` : "";
|
|
4342
|
+
process.stderr.write(
|
|
4343
|
+
import_picocolors8.default.yellow(
|
|
4344
|
+
`
|
|
4345
|
+
Reached the $${resolvedBudgetUsd.toFixed(2)} budget guard${spentStr} \u2014 a safety limit, not an error. Re-run with --max-budget=<amount> (or set ADMIN_AGENT_MAX_BUDGET) for more headroom. Any report the agent saved is in reports/.
|
|
4346
|
+
`
|
|
4347
|
+
)
|
|
4348
|
+
);
|
|
4224
4349
|
} else if (resultMsg.subtype === "error_during_execution") {
|
|
4225
4350
|
process.stderr.write(
|
|
4226
4351
|
"\nExecution error \u2014 the agent encountered an unrecoverable failure.\n"
|
|
@@ -4236,6 +4361,24 @@ Agent session ended with error: ${resultMsg.subtype}
|
|
|
4236
4361
|
Permission denials: ${denied}
|
|
4237
4362
|
`);
|
|
4238
4363
|
}
|
|
4364
|
+
if (reportSavedThisRun) {
|
|
4365
|
+
const line = formatMetricsLine(
|
|
4366
|
+
numTurns - invBase.turns,
|
|
4367
|
+
durationMs - invBase.duration,
|
|
4368
|
+
durationApiMs - invBase.api,
|
|
4369
|
+
dollarCapped ? totalCostUsd - invBase.cost : void 0
|
|
4370
|
+
);
|
|
4371
|
+
process.stderr.write(`
|
|
4372
|
+
Investigation complete \u2014 ${line}
|
|
4373
|
+
`);
|
|
4374
|
+
invBase = {
|
|
4375
|
+
turns: numTurns,
|
|
4376
|
+
duration: durationMs,
|
|
4377
|
+
api: durationApiMs,
|
|
4378
|
+
cost: totalCostUsd
|
|
4379
|
+
};
|
|
4380
|
+
reportSavedThisRun = false;
|
|
4381
|
+
}
|
|
4239
4382
|
turnGate.open();
|
|
4240
4383
|
} else if (message.type === "system") {
|
|
4241
4384
|
const sysMsg = message;
|
|
@@ -4307,27 +4450,26 @@ Agent error: ${message}
|
|
|
4307
4450
|
process.stderr.write(remaining);
|
|
4308
4451
|
}
|
|
4309
4452
|
turnGate.open();
|
|
4310
|
-
const
|
|
4311
|
-
const apiPct = durationMs > 0 ? Math.round(durationApiMs / durationMs * 100) : 0;
|
|
4312
|
-
const costStr = totalCostUsd > 0 ? ` Cost: $${totalCostUsd.toFixed(4)}.` : "";
|
|
4453
|
+
const sessionCost = dollarCapped ? totalCostUsd : void 0;
|
|
4313
4454
|
if (process.env.DEBUG && (cacheReadTokens > 0 || cacheCreationTokens > 0)) {
|
|
4314
4455
|
process.stderr.write(
|
|
4315
4456
|
import_picocolors8.default.dim(
|
|
4316
|
-
`
|
|
4457
|
+
`Cache: ${cacheReadTokens} read / ${cacheCreationTokens} created input tokens (read > 0 confirms the system prompt is served from cache)
|
|
4317
4458
|
`
|
|
4318
4459
|
)
|
|
4319
4460
|
);
|
|
4320
4461
|
}
|
|
4321
4462
|
if (hadNonAbortError) {
|
|
4322
|
-
process.stderr.write(`
|
|
4323
|
-
Session ended due to error. Turns: ${numTurns}.${costStr}
|
|
4324
|
-
`);
|
|
4325
|
-
} else {
|
|
4326
4463
|
process.stderr.write(
|
|
4327
4464
|
`
|
|
4328
|
-
Session ended. Turns: ${numTurns}
|
|
4465
|
+
Session ended due to error. Turns: ${numTurns}.${formatCostSuffix(sessionCost)}
|
|
4329
4466
|
`
|
|
4330
4467
|
);
|
|
4468
|
+
} else {
|
|
4469
|
+
const line = formatMetricsLine(numTurns, durationMs, durationApiMs, sessionCost);
|
|
4470
|
+
process.stderr.write(`
|
|
4471
|
+
Session ended. ${line}
|
|
4472
|
+
`);
|
|
4331
4473
|
}
|
|
4332
4474
|
}
|
|
4333
4475
|
}
|
|
@@ -4904,12 +5046,14 @@ function printHelp() {
|
|
|
4904
5046
|
" --login-org=UUID Target organization UUID for OAuth",
|
|
4905
5047
|
" --logout Log out from Anthropic account and exit",
|
|
4906
5048
|
` --model=NAME Override agent model (${SUPPORTED_MODELS.join(" | ")}); default: sonnet`,
|
|
5049
|
+
" --max-budget=USD Per-session budget cap in USD (API-key billing only); default: 5.00",
|
|
4907
5050
|
"",
|
|
4908
5051
|
" Environment variables:",
|
|
4909
|
-
" ANTHROPIC_API_KEY
|
|
4910
|
-
"
|
|
4911
|
-
"
|
|
4912
|
-
"
|
|
5052
|
+
" ANTHROPIC_API_KEY Anthropic API key (if not set, OAuth login via browser is used)",
|
|
5053
|
+
" ADMIN_AGENT_MAX_BUDGET Per-session budget cap in USD (overridden by --max-budget)",
|
|
5054
|
+
" KINETICA_URL Kinetica endpoint URL",
|
|
5055
|
+
" KINETICA_USER Admin username",
|
|
5056
|
+
" KINETICA_PASS Admin password",
|
|
4913
5057
|
""
|
|
4914
5058
|
];
|
|
4915
5059
|
process.stdout.write(lines.join("\n") + "\n");
|
|
@@ -4954,13 +5098,30 @@ async function main() {
|
|
|
4954
5098
|
return;
|
|
4955
5099
|
}
|
|
4956
5100
|
}
|
|
5101
|
+
const budgetArg = args.find((a) => a.startsWith("--max-budget="));
|
|
5102
|
+
const budgetValue = budgetArg?.split("=")[1];
|
|
5103
|
+
let maxBudgetFlag;
|
|
5104
|
+
if (budgetValue !== void 0) {
|
|
5105
|
+
const parsed = Number(budgetValue);
|
|
5106
|
+
if (!isValidBudget(parsed)) {
|
|
5107
|
+
process.stderr.write(
|
|
5108
|
+
import_picocolors14.default.red(
|
|
5109
|
+
`Error: invalid --max-budget value "${budgetValue}". Use a positive number, e.g. --max-budget=10
|
|
5110
|
+
`
|
|
5111
|
+
)
|
|
5112
|
+
);
|
|
5113
|
+
process.exitCode = 1;
|
|
5114
|
+
return;
|
|
5115
|
+
}
|
|
5116
|
+
maxBudgetFlag = parsed;
|
|
5117
|
+
}
|
|
4957
5118
|
loadEnvFile();
|
|
4958
5119
|
printBanner();
|
|
4959
5120
|
if (model === void 0 && process.stdin.isTTY) {
|
|
4960
5121
|
model = await selectModel();
|
|
4961
5122
|
}
|
|
4962
5123
|
const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
|
|
4963
|
-
process.stderr.write(import_picocolors14.default.dim(`
|
|
5124
|
+
process.stderr.write(import_picocolors14.default.dim(`Model: ${effectiveModel}
|
|
4964
5125
|
`));
|
|
4965
5126
|
const authResult = await authenticateAnthropic({ forceLogin, loginMethod, loginOrgUUID });
|
|
4966
5127
|
if (authResult.method === "oauth") {
|
|
@@ -4970,9 +5131,13 @@ async function main() {
|
|
|
4970
5131
|
} else {
|
|
4971
5132
|
process.stderr.write(import_picocolors14.default.dim("Authenticated via API key\n"));
|
|
4972
5133
|
}
|
|
5134
|
+
const maxBudgetUsd = resolveMaxBudgetUsd(maxBudgetFlag);
|
|
4973
5135
|
const { session: connectedSession, kineticaVersion, degraded } = await connectWithRetry();
|
|
4974
5136
|
session = connectedSession;
|
|
4975
|
-
await runAgent(session, kineticaVersion, degraded, model
|
|
5137
|
+
await runAgent(session, kineticaVersion, degraded, model, {
|
|
5138
|
+
authMethod: authResult.method,
|
|
5139
|
+
maxBudgetUsd
|
|
5140
|
+
});
|
|
4976
5141
|
}
|
|
4977
5142
|
function getSession() {
|
|
4978
5143
|
return session;
|