fix(budget): add Fireworks model pricing and auto-unpause after limit increase

Three fixes for the non-functional budget system:

1. Fireworks models hardcoded cost {input: 0, output: 0}, making all
   Fireworks/Fire Pass runs report $0 and budget enforcement useless.
   Added per-model pricing to FIREWORKS_CURATED_MODELS with a fallback
   for unknown models. Pi-ai known providers (Anthropic, OpenAI, etc.)
   already had correct per-model pricing via getModels().

2. saveAgent IPC handler passed the form's submitted status through
   without checking whether the agent was budget-paused and the new
   budget exceeds spent. Now auto-resolves paused → idle when the user
   raises budget_monthly_usd above spent_monthly_usd.

3. checkAutoResolve in budget-enforcement-service resolved policy
   incidents but never touched agents.status. Added
   unpauseBudgetBlockedAgents() that scans for paused agents whose
   legacy budget now has headroom and flips them to idle.
This commit is contained in:
ZacharyZhang-NY committed 2026-04-29 20:31:48 -04:00
1 parent b3da6dbf8a
commit db3cd3c8b8
3 files changed
+48 -16

No files matched your search

+15 -2
View File
@@ -157,13 +157,11 @@ export function createBudgetEnforcementService({
const spent = db.core.budget.getSpentInWindow(policy);
let resolved = false;
// If spend is below hard-stop threshold, resolve all open incidents
if (spent < policy.amount) {
const count = db.core.budget.resolveIncidentsForPolicy(policyId, "auto_resolved_limit_increase");
if (count > 0) resolved = true;
}
// If spend is below warn threshold, resolve warn incidents specifically
const warnAmount = Math.floor((policy.amount * policy.warnPercent) / 100);
if (spent < warnAmount) {
const count = db.core.budget.resolveWarnIncidentsForPolicy(policyId, "auto_resolved_limit_increase");
@@ -171,11 +169,26 @@ export function createBudgetEnforcementService({
}
if (resolved) {
unpauseBudgetBlockedAgents(companyId);
logger.info(`Budget auto-resolve: policy ${policyId} incidents resolved (spent=${spent}, limit=${policy.amount})`);
publishDomainChanged();
}
}
function unpauseBudgetBlockedAgents(companyId: string): void {
const paused = db.queryAll<{ id: string; budgetMonthlyUsd: number; spentMonthlyUsd: number }>(
`SELECT id, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd
FROM agents WHERE company_id = ? AND status = 'paused' AND budget_monthly_usd > 0`,
companyId,
);
for (const agent of paused) {
if (agent.budgetMonthlyUsd > agent.spentMonthlyUsd) {
db.queryAll("UPDATE agents SET status = 'idle', updated_at = ? WHERE id = ?", new Date().toISOString(), agent.id);
logger.info(`[budget] Auto-unpaused agent ${agent.id}: budget $${agent.budgetMonthlyUsd} > spent $${agent.spentMonthlyUsd}`);
}
}
}
/**
* Closes incidents whose calendar month window has ended.
* Per D-06: window rollover cleanup.
+9 -4
View File
@@ -99,6 +99,14 @@ export function registerCoreAgentGraphHandlers({
if (!modelSelection.ok) {
return fail("CONNECTOR_MODEL_NOT_ALLOWED", modelSelection.error);
}
let resolvedStatus = parsed.status;
if (resolvedStatus === "paused" && existingAgent.status === "paused" && parsed.budgetMonthlyUsd > 0) {
const spent = existingAgent.spentMonthlyUsd ?? 0;
if (parsed.budgetMonthlyUsd > spent) {
resolvedStatus = "idle";
}
}
let id: string;
try {
id = db.core.agents.saveAgent({
@@ -108,7 +116,7 @@ export function registerCoreAgentGraphHandlers({
role: parsed.role,
title: parsed.title,
department: parsed.department ?? null,
status: parsed.status,
status: resolvedStatus,
reportsTo: parsed.reportsTo ?? null,
connectorId: parsed.connectorId,
workspaceId: parsed.workspaceId ?? null,
@@ -116,9 +124,6 @@ export function registerCoreAgentGraphHandlers({
metadataJson: parsed.metadataJson,
capabilities: parsed.capabilities,
budgetMonthlyUsd: parsed.budgetMonthlyUsd,
// User edited this agent through the form and picked a specific
// (connector, model). Respect their intent: pin the strategy to
// `explicit` so the dispatcher never overrides their choice.
modelStrategy: "explicit",
});
} catch (error) {
+24 -10
View File
@@ -37,15 +37,29 @@ const FIREWORKS_DEFAULT_MAX_TOKENS = 8192;
export const FIREWORKS_FIREPASS_MODEL_ID = "accounts/fireworks/routers/kimi-k2p5-turbo";
export const FIREWORKS_FIREPASS_MODEL_NAME = "Kimi K2.5 Turbo (Fire Pass)";
const FIREWORKS_CURATED_MODELS: Array<{ id: string; name: string; contextWindow?: number }> = [
{ id: "accounts/fireworks/models/kimi-k2-instruct", name: "Kimi K2 Instruct", contextWindow: 131_072 },
{ id: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct", name: "Qwen3 Coder 480B A35B Instruct", contextWindow: 262_144 },
{ id: "accounts/fireworks/models/deepseek-v3p1", name: "DeepSeek V3.1", contextWindow: 163_840 },
{ id: "accounts/fireworks/models/glm-4p5", name: "GLM 4.5", contextWindow: 131_072 },
{ id: "accounts/fireworks/models/llama-v3p3-70b-instruct", name: "Llama 3.3 70B Instruct", contextWindow: 131_072 },
const FIREWORKS_CURATED_MODELS: Array<{
id: string;
name: string;
contextWindow?: number;
cost: { input: number; output: number };
}> = [
{ id: "accounts/fireworks/models/kimi-k2-instruct", name: "Kimi K2 Instruct", contextWindow: 131_072, cost: { input: 0.50, output: 0.50 } },
{ id: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct", name: "Qwen3 Coder 480B A35B Instruct", contextWindow: 262_144, cost: { input: 0.50, output: 0.50 } },
{ id: "accounts/fireworks/models/deepseek-v3p1", name: "DeepSeek V3.1", contextWindow: 163_840, cost: { input: 0.20, output: 0.20 } },
{ id: "accounts/fireworks/models/glm-4p5", name: "GLM 4.5", contextWindow: 131_072, cost: { input: 0.30, output: 0.30 } },
{ id: "accounts/fireworks/models/llama-v3p3-70b-instruct", name: "Llama 3.3 70B Instruct", contextWindow: 131_072, cost: { input: 0.20, output: 0.20 } },
];
function buildFireworksModel(modelId: string, name?: string, contextWindow?: number): Model<"openai-completions"> {
const FIREWORKS_FIREPASS_COST = { input: 0.50, output: 0.50 };
const FIREWORKS_FALLBACK_COST = { input: 0.50, output: 0.50 };
function buildFireworksModel(
modelId: string,
name?: string,
contextWindow?: number,
cost?: { input: number; output: number },
): Model<"openai-completions"> {
const modelCost = cost ?? FIREWORKS_FALLBACK_COST;
return {
id: modelId,
name: name ?? modelId,
@@ -54,7 +68,7 @@ function buildFireworksModel(modelId: string, name?: string, contextWindow?: num
baseUrl: FIREWORKS_OPENAI_BASE_URL,
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
cost: { input: modelCost.input, output: modelCost.output, cacheRead: 0, cacheWrite: 0 },
contextWindow: contextWindow ?? FIREWORKS_DEFAULT_CONTEXT,
maxTokens: FIREWORKS_DEFAULT_MAX_TOKENS,
};
@@ -62,11 +76,11 @@ function buildFireworksModel(modelId: string, name?: string, contextWindow?: num
export function resolveModel(provider: string, modelId: string): Model<Api> | null {
if (provider === "fireworks-firepass") {
return buildFireworksModel(FIREWORKS_FIREPASS_MODEL_ID, FIREWORKS_FIREPASS_MODEL_NAME, 262_144);
return buildFireworksModel(FIREWORKS_FIREPASS_MODEL_ID, FIREWORKS_FIREPASS_MODEL_NAME, 262_144, FIREWORKS_FIREPASS_COST);
}
if (provider === "fireworks") {
const curated = FIREWORKS_CURATED_MODELS.find((entry) => entry.id === modelId);
return buildFireworksModel(modelId, curated?.name, curated?.contextWindow);
return buildFireworksModel(modelId, curated?.name, curated?.contextWindow, curated?.cost);
}
if (!isPiKnownProvider(provider)) {
log.warn(`[model-resolver] Unknown pi provider: ${provider}`);