fix(credentials): close resolver switch and guard missing provider/auth
- credential-resolver.resolve: throw explicit CREDENTIAL_INVALID when the connector has no auth schema, and add a default branch so the switch has a definitive return path for every auth kind - credential-resolver api_key branch: guard definition.provider being unset before calling pi-ai's getEnvApiKey - cost-writer: only attempt cost lookup when definition.provider is defined; adapter-only connectors still write an unattributed row
This commit is contained in:
1 parent
bee5535349
commit
cad17b4d45
2 files changed
+231
No files matched your search
@@ -0,0 +1,66 @@
|
||||
import { calculateCost, getModel, type Usage } from "@mariozechner/pi-ai";
|
||||
import type { AppDatabase } from "@main/database";
|
||||
import { getConnectorDefinition } from "@main/connectors";
|
||||
|
||||
export interface CostWriterDeps {
|
||||
db: AppDatabase;
|
||||
logger: { warn: (...args: unknown[]) => void };
|
||||
}
|
||||
|
||||
interface ObserveInput {
|
||||
runId: string;
|
||||
companyId: string;
|
||||
agentId: string;
|
||||
taskId: string | null;
|
||||
connectorId: string;
|
||||
modelId: string;
|
||||
usage: Usage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a `cost_entries` row for an assistant `message_end` and updates the
|
||||
* cumulative `runs.cost_usd` for the active run.
|
||||
*/
|
||||
export class CostWriter {
|
||||
constructor(private readonly deps: CostWriterDeps) {}
|
||||
|
||||
observeAssistantMessage(input: ObserveInput): void {
|
||||
let amountUsd: number | null = null;
|
||||
try {
|
||||
const definition = getConnectorDefinition(input.connectorId);
|
||||
if (definition.provider) {
|
||||
const model = getModel(definition.provider as never, input.modelId as never);
|
||||
if (model) {
|
||||
const cost = calculateCost(model, input.usage);
|
||||
amountUsd = Number.isFinite(cost.total) ? cost.total : null;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.deps.logger.warn(
|
||||
`[cost-writer] cost calculation failed for ${input.connectorId}/${input.modelId}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
amountUsd = null;
|
||||
}
|
||||
|
||||
this.deps.db.queryAll(
|
||||
"insert into cost_entries (id, company_id, run_id, task_id, agent_id, connector_id, amount_usd, unattributed, created_at) values (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
cryptoRandomId(),
|
||||
input.companyId,
|
||||
input.runId,
|
||||
input.taskId,
|
||||
input.agentId,
|
||||
input.connectorId,
|
||||
amountUsd,
|
||||
amountUsd === null ? 1 : 0,
|
||||
new Date().toISOString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function cryptoRandomId(): string {
|
||||
// Avoid pulling node:crypto at module top-level so this file can be
|
||||
// imported from non-Node contexts during type checks.
|
||||
return globalThis.crypto?.randomUUID
|
||||
? globalThis.crypto.randomUUID()
|
||||
: `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
}
|
||||
Reference in new issue
Block a user