/** * llm-billing — read-only fetch + normalize for LLM-provider cost/usage * admin APIs (OpenAI / Anthropic / Cursor). * * Design notes: * * - This file exports BOTH the minimal skill declarations (id + * requiresIntegration only — these gate marketplace deploys via the * backend's deriveRequiredIntegrations) AND a plain-function * fetch SDK (fetchOpenAICosts / fetchAnthropicCosts / fetchCursorSpend * + fetchAllProviders). Skill objects are registered in index.js; * fetch functions are imported directly by template nodes. * * - We deliberately do NOT expose this as an MCP server. The fetch * surface is small (3 endpoints) and deterministic — workflow code * knows exactly what to ask for. MCP would add a JSON-RPC hop and * LLM-driven tool selection for no gain. See README discussion * re: REST vs MCP for the broader rationale. * * - Output shape is normalized across providers so downstream * analysis (baseline diff, anomaly detection) doesn't need * per-provider branches. See NormalizedSpendRecord JSDoc below. * * - Failure mode is partial: `fetchAllProviders` uses Promise.allSettled * so one provider being down (or not connected) doesn't kill the * whole digest. Each leg returns either { items, ok: true } or * { error, ok: false } so the analyze node can render a degraded * digest. */ /** * Normalized record shape — one entry per (provider × day × dimension) * after each provider's response is unpacked. Optional fields are * provider-specific and may be undefined. * * @typedef {Object} NormalizedSpendRecord * @property {'openai'|'anthropic'|'cursor'} provider * @property {string} day ISO date (YYYY-MM-DD) * @property {number} costUsd USD, positive * @property {string} [projectId] OpenAI project_id * @property {string} [projectName] OpenAI project metadata (joined on lookup) * @property {string} [workspaceId] Anthropic workspace_id * @property {string} [workspaceName] Anthropic workspace metadata * @property {string} [apiKeyId] OpenAI / Anthropic api_key_id * @property {string} [userEmail] Cursor team-member email * @property {string} [model] Model id (when grouped by model) * @property {number} [tokensIn] * @property {number} [tokensOut] * @property {number} [cachedTokens] * @property {number} [requestCount] Cursor * @property {number} [acceptanceRate] Cursor: 0..1 */ /** * Marketplace-gating skill declarations. The id matches the * REQUIRED_INTEGRATION_MAP entry on the backend; the template's node * just declares `skills: [SKILLS.OPENAI_BILLING, ...]` and the bundler * derives required integrations automatically. * * These are intentionally minimal — no MCP tools, no resolve(), no * prompt fragments. The skill object exists ONLY to register the * integration dependency. Runtime behavior lives in the fetch fns below. */ export declare const openaiBillingSkill: Readonly<{ id: "openai_billing"; callsBackend: true; requiresIntegration: "openai_billing"; description: "OpenAI organization billing/usage admin API (paste sk-admin-... key)"; }>; export declare const anthropicBillingSkill: Readonly<{ id: "anthropic_billing"; callsBackend: true; requiresIntegration: "anthropic_billing"; description: "Anthropic organization cost/usage admin API (paste sk-ant-admin-... key)"; }>; export declare const cursorAdminSkill: Readonly<{ id: "cursor_admin"; callsBackend: true; requiresIntegration: "cursor_admin"; description: "Cursor Team/Enterprise admin API (paste admin key)"; }>; /** * Fetch OpenAI org costs + usage for the window. Iterates pagination * (page_token) until exhausted. * * Reference: GET /v1/organization/costs * query: start_time (unix sec), end_time (unix sec), bucket_width=1d, * group_by[]=project_id&group_by[]=line_item, page * response shape: * { * object: 'page', * data: [ * { object: 'bucket', start_time, end_time, * results: [{ amount: { value, currency }, project_id, line_item, ... }] } * ], * has_more: boolean, * next_page: '...' * } * * We normalize each result row into a NormalizedSpendRecord. * * @param {{ startMs: number, endMs: number, groupBy?: string[] }} opts * @returns {Promise<{ ok: true, items: NormalizedSpendRecord[], rawBuckets: number }>} */ export declare function fetchOpenAICosts({ startMs, endMs, groupBy }: any): Promise<{ ok: boolean; items: any[]; rawBuckets: number; }>; /** * Fetch the OpenAI project catalog (id → name). Useful for the digest * to display "acme-prod" instead of "proj_abc123". Cheap — usually * <50 projects. Returns Map. */ export declare function fetchOpenAIProjects(): Promise>; /** * Fetch Anthropic cost report. * * Reference: GET /v1/organizations/cost_report * query: starting_at (ISO), ending_at (ISO), bucket=1d, * group_by[]=workspace_id&group_by[]=model * headers: x-api-key, anthropic-version: 2023-06-01 * response shape (beta): * { * data: [ * { starting_at, ending_at, currency: 'USD', * results: [{ amount: '0.42', workspace_id, model, ... }] } * ], * has_more, next_page * } * * Anthropic returns cost amounts as decimal strings (cents-precision). * Multiply-by-100 then parse-int → exact-cents arithmetic OK; for the * digest we just parseFloat (drift over 28 days is negligible). */ export declare function fetchAnthropicCosts({ startMs, endMs, groupBy }: any): Promise<{ ok: boolean; items: any[]; rawBuckets: number; }>; /** * Fetch Anthropic workspace catalog (id → name). */ export declare function fetchAnthropicWorkspaces(): Promise>; /** * Fetch Cursor team daily-usage data. The endpoint returns per-day, * per-user, per-model rolls — we flatten to NormalizedSpendRecord. * * Reference: GET /teams/daily-usage-data * query: startDate (YYYY-MM-DD), endDate (YYYY-MM-DD) * header: Authorization: Bearer * response shape (Cursor Admin API, 2026): * { * data: [ * { date, totalCents, userMetrics: [ * { email, totalCents, modelUsage: [ * { model, requestCount, acceptedLines, suggestedLines } ] } ] * } * ] * } * * acceptanceRate = acceptedLines / suggestedLines (per model per user * per day). The digest aggregates across users for the team-level rate. */ export declare function fetchCursorSpend({ startMs, endMs }: any): Promise<{ ok: boolean; items: any[]; rawBuckets: number; }>; /** * Pull all three providers in parallel. Returns a per-provider result * map so the analyze node can degrade gracefully when one provider * fails (not connected, key revoked, upstream 500, etc.). * * @param {{ startMs: number, endMs: number }} opts * @returns {Promise<{ * openai: { ok: true, items: NormalizedSpendRecord[] } | { ok: false, error: string }, * anthropic: { ok: true, items: NormalizedSpendRecord[] } | { ok: false, error: string }, * cursor: { ok: true, items: NormalizedSpendRecord[] } | { ok: false, error: string }, * totals: { provider: string, totalUsd: number }[], * }>} */ export declare function fetchAllProviders({ startMs, endMs }: any): Promise<{ openai: any; anthropic: any; cursor: any; totals: { provider: string; totalUsd: any; }[]; }>; /** * Group an array of normalized records by a key + sum costUsd. * Convenience for analyze nodes — saves writing the same reduce 5x. * * @param {NormalizedSpendRecord[]} items * @param {(item: NormalizedSpendRecord) => string} keyFn * @returns {{ key: string, totalUsd: number, count: number }[]} sorted desc by totalUsd */ export declare function groupByKey(items: any, keyFn: any): any[]; /** * Compute mean + stddev over a numeric window. Used by analyze nodes * to flag this-week-vs-baseline anomalies (>2σ). * * @param {number[]} xs * @returns {{ mean: number, stddev: number }} */ export declare function meanStddev(xs: any): { mean: number; stddev: number; };