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:
ZacharyZhang-NY committed 2026-04-17 01:28:37 -04:00
1 parent bee5535349
commit cad17b4d45
2 files changed
+231

No files matched your search

+66
View File
@@ -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)}`;
}