Files
OmniRoute/open-sse/services/usage/firecrawl.ts
Praveen K Palaniswamy 65e81158ab fix(ollama): route models by advertised capability (#11088)
Landed with the design call resolved per the owner's pick — **option 1**: the synced store is now endpoint-agnostic (persistDiscoveredModels and managedModelImport no longer drop non-chat models at write time), and chat selectability moved to read time (auto-pool expansion in autoStrategy applies filterChatSelectableModels; the models-route projection already had its chatOnly filter). Your discovery test now passes end-to-end (3/3): /api/show capabilities persist per connection and image/embedding requests route through the advertising host.

Reconciliation notes: conflicted areas merged onto the current tip (adobe discovery import, requestedModel preflight signature, resolvedProvider fast-path coexists with the synced-route override — explicit resolution wins); carried base-red drains (#10055 memoization, #11071 test variants) dropped as already-landed; the managed-model-import exclusion test was propagated to the new contract (image/video models persist; the read filter still hides them from chat pickers — pinned by a new assertion). Full battery: 205/206 focused (the one red is a confirmed periodic-timer timing flake on the loaded devbox — 20/20 isolated), autoCombo vitest 30/30, combo suites 46/46, gates + typecheck clean.

Thank you @yourspraveen — the capability probe + routing design was right; it just needed the store contract opened up. Fixes #11087.
2026-08-23 11:45:01 -03:00

83 lines
2.5 KiB
TypeScript

/**
* usage/firecrawl.ts — Firecrawl team credit usage for Provider Limits.
*
* GET /v2/team/credit-usage via firecrawlQuotaFetcher; shapes remaining/plan
* credits into the standard `{ plan, quotas }` response.
*/
import {
fetchFirecrawlQuota,
getFirecrawlBaseUrl,
type FirecrawlQuota,
} from "../firecrawlQuotaFetcher.ts";
import { createQuotaFromUsage, parseResetTime } from "./quota.ts";
function createFirecrawlPlanQuota(q: FirecrawlQuota) {
if (q.overPlan) {
return {
used: 0,
total: q.planCredits,
remaining: q.remainingCredits,
remainingPercentage: q.planCredits > 0 ? (q.remainingCredits / q.planCredits) * 100 : 100,
resetAt: parseResetTime(q.resetAt),
unlimited: false,
extraCreditsInferred: q.extraCreditsInferred,
overPlan: true,
};
}
return {
...createQuotaFromUsage(q.used, q.total, q.resetAt),
extraCreditsInferred: 0,
overPlan: false,
};
}
export async function getFirecrawlUsage(
connectionId: string,
apiKey?: string,
connection?: Record<string, unknown>
) {
if (!connectionId) {
return { message: "Firecrawl: connection id unavailable." };
}
const customBase = getFirecrawlBaseUrl(connection);
if (customBase) {
return {
plan: "Firecrawl · Self-Hosted Local",
quotas: {},
message: `Connected to self-hosted Firecrawl instance (${customBase})`,
};
}
try {
// The explicit `apiKey` argument was silently dropped when #91bb6aa619 moved
// this to fetchFirecrawlQuota(connectionId, connection): the fetcher reads the
// key off the connection record, so a caller that passes the key directly —
// without a connection carrying it — always got "API key not available".
const resolvedConnection = apiKey ? { ...(connection || {}), apiKey } : connection;
const live = await fetchFirecrawlQuota(connectionId, resolvedConnection);
if (!live) {
return { message: "Firecrawl API key not available or credit usage unavailable." };
}
const q = live as FirecrawlQuota;
const monthly = createFirecrawlPlanQuota(q);
return {
plan: "Firecrawl · Monthly credits",
quotas: {
monthly,
},
remainingCredits: q.remainingCredits,
planCredits: q.planCredits,
extraCreditsInferred: q.extraCreditsInferred,
overPlan: q.overPlan,
limitReached: q.limitReached,
};
} catch (error) {
return { message: `Firecrawl usage error: ${(error as Error).message}` };
}
}