fix: route heartbeat cost recording through costService
Heartbeat runs recorded costs via direct SQL inserts into costEvents and agents.spentMonthlyCents, bypassing costService.createEvent(). This skipped: - companies.spentMonthlyCents update (company budget never incremented) - Agent auto-pause when budget exceeded (enforcement gap) Now calls costService(db).createEvent() which handles all three: insert cost event, update agent spend, update company spend, and auto-pause agent when budgetMonthlyCents is exceeded.
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
|||||||
agentWakeupRequests,
|
agentWakeupRequests,
|
||||||
heartbeatRunEvents,
|
heartbeatRunEvents,
|
||||||
heartbeatRuns,
|
heartbeatRuns,
|
||||||
costEvents,
|
|
||||||
issues,
|
issues,
|
||||||
projectWorkspaces,
|
projectWorkspaces,
|
||||||
} from "@paperclipai/db";
|
} from "@paperclipai/db";
|
||||||
@@ -21,6 +20,7 @@ import { getServerAdapter, runningProcesses } from "../adapters/index.js";
|
|||||||
import type { AdapterExecutionResult, AdapterInvocationMeta, AdapterSessionCodec } from "../adapters/index.js";
|
import type { AdapterExecutionResult, AdapterInvocationMeta, AdapterSessionCodec } from "../adapters/index.js";
|
||||||
import { createLocalAgentJwt } from "../agent-auth-jwt.js";
|
import { createLocalAgentJwt } from "../agent-auth-jwt.js";
|
||||||
import { parseObject, asBoolean, asNumber, appendWithCap, MAX_EXCERPT_BYTES } from "../adapters/utils.js";
|
import { parseObject, asBoolean, asNumber, appendWithCap, MAX_EXCERPT_BYTES } from "../adapters/utils.js";
|
||||||
|
import { costService } from "./costs.js";
|
||||||
import { secretService } from "./secrets.js";
|
import { secretService } from "./secrets.js";
|
||||||
import { resolveDefaultAgentWorkspaceDir } from "../home-paths.js";
|
import { resolveDefaultAgentWorkspaceDir } from "../home-paths.js";
|
||||||
|
|
||||||
@@ -977,8 +977,8 @@ export function heartbeatService(db: Db) {
|
|||||||
.where(eq(agentRuntimeState.agentId, agent.id));
|
.where(eq(agentRuntimeState.agentId, agent.id));
|
||||||
|
|
||||||
if (additionalCostCents > 0 || hasTokenUsage) {
|
if (additionalCostCents > 0 || hasTokenUsage) {
|
||||||
await db.insert(costEvents).values({
|
const costs = costService(db);
|
||||||
companyId: agent.companyId,
|
await costs.createEvent(agent.companyId, {
|
||||||
agentId: agent.id,
|
agentId: agent.id,
|
||||||
provider: result.provider ?? "unknown",
|
provider: result.provider ?? "unknown",
|
||||||
model: result.model ?? "unknown",
|
model: result.model ?? "unknown",
|
||||||
@@ -988,16 +988,6 @@ export function heartbeatService(db: Db) {
|
|||||||
occurredAt: new Date(),
|
occurredAt: new Date(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (additionalCostCents > 0) {
|
|
||||||
await db
|
|
||||||
.update(agents)
|
|
||||||
.set({
|
|
||||||
spentMonthlyCents: sql`${agents.spentMonthlyCents} + ${additionalCostCents}`,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
})
|
|
||||||
.where(eq(agents.id, agent.id));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startNextQueuedRunForAgent(agentId: string) {
|
async function startNextQueuedRunForAgent(agentId: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user