repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/gratis/src/router.ts
Rawimport {
type Api,
type AssistantMessage,
type AssistantMessageEvent,
type AssistantMessageEventStream,
createAssistantMessageEventStream,
isContextOverflow,
type ProviderStreams,
type SimpleStreamOptions,
type TranscriptContext,
} from "@earendil-works/pi-ai";
// Pi's extension loader exposes API implementations only via the compat entry.
import {
anthropicMessagesApi,
azureOpenAIResponsesApi,
bedrockConverseStreamApi,
googleGenerativeAIApi,
googleVertexApi,
mistralConversationsApi,
openAICodexResponsesApi,
openAICompletionsApi,
openAIResponsesApi,
piMessagesApi,
} from "@earendil-works/pi-ai/compat";
import {
AUTO_ID,
BACKEND_INFO,
BACKENDS,
type Backend,
backendOf,
type GratisModel,
PROVIDER,
upstreamId,
} from "./catalog.ts";
import { span } from "./debug.ts";
import { createPerf } from "./perf.ts";
/** Every API implementation Pi ships; the catalog entry picks one per model. */
const APIS: Partial<Record<Api, ProviderStreams>> = {
"anthropic-messages": anthropicMessagesApi(),
"azure-openai-responses": azureOpenAIResponsesApi(),
"bedrock-converse-stream": bedrockConverseStreamApi(),
"google-generative-ai": googleGenerativeAIApi(),
"google-vertex": googleVertexApi(),
"mistral-conversations": mistralConversationsApi(),
"openai-codex-responses": openAICodexResponsesApi(),
"openai-completions": openAICompletionsApi(),
"openai-responses": openAIResponsesApi(),
"pi-messages": piMessagesApi(),
};
/** Sent instead of a key to keyless backends; the auth header is dropped. */
const ANONYMOUS_KEY = "grts-anonymous";
export const OVERFLOW_PREFIX = "context_length_exceeded";
/** `grts/auto` gives each hop this long to stream anything before moving on. */
const FIRST_RESPONSE_DEADLINE_MS = 10_000;
/** A candidate that just hit a rate limit or quota is skipped this long. */
const MODEL_COOLDOWN_MS = 60_000;
/** A silent hop means a congested backend (free queues); skip it this long. */
const SLOW_COOLDOWN_MS = 10 * 60_000;
/** A candidate the account cannot use (404, not found) is skipped this long. */
const UNAVAILABLE_COOLDOWN_MS = 60 * 60_000;
type Failure = "rate" | "quota" | "overflow" | "unavailable" | "other";
/** Listed models an account cannot use, e.g. NVIDIA's "Function ...: Not found for account". */
const UNAVAILABLE =
/\b404\b|not found|model_not_found|does not exist|no such model|not available for/i;
const QUOTA =
/quota|resource[_ ]exhausted|exceeded your current|per[- ]day|daily limit|credits|insufficient balance|limit exhausted/i;
const RATE =
/\b429\b|\b503\b|rate[_ -]?limit|too many requests|unavailable|overloaded|high demand|capacity|saturat|timed? ?out|timeout|network|connection|socket hang up|fetch failed|ECONN|ETIMEDOUT/i;
/** Overflow phrasing Pi's own detector may miss on free gateways. */
const OVERFLOW =
/context[_ ](length|window)|prompt is too long|input is too long|too many (input )?tokens|maximum (context|input)/i;
/** True when Pi would not yet treat this error as a context overflow. */
export function isUnrecognizedOverflow(message: AssistantMessage): boolean {
const text = message.errorMessage ?? "";
return (
!text.includes(OVERFLOW_PREFIX) &&
!isContextOverflow(message) &&
OVERFLOW.test(text) &&
!RATE.test(text)
);
}
/** Classify by status and body text together; wrapped 429s carry the reason in text. */
function classifyFailure(
message: AssistantMessage,
contextWindow?: number,
): Failure {
if (isContextOverflow(message, contextWindow)) return "overflow";
if (isUnrecognizedOverflow(message)) return "overflow";
const text = message.errorMessage ?? "";
if (QUOTA.test(text)) return "quota";
if (RATE.test(text)) return "rate";
if (UNAVAILABLE.test(text)) return "unavailable";
return "other";
}
interface RouterState {
/** Resolved key, `undefined` for anonymous keyless use, `null` when unusable. */
key(backend: Backend): string | undefined | null;
/** `grts/auto` candidates for one backend, best first. */
candidates(backend: Backend): readonly GratisModel[];
resolveUrl(url: string): string;
/** Test seam; defaults to FIRST_RESPONSE_DEADLINE_MS. */
firstResponseDeadlineMs?: number;
/** Test seam: transports by API; defaults to every implementation Pi ships. */
apis?: Partial<Record<Api, ProviderStreams>>;
}
/** How a forwarded stream ended; errors after output began still reach the caller. */
type Ending = "ok" | "error" | "aborted";
type Outcome =
| { kind: "streamed"; ending: Ending; served: string }
| { kind: "failed"; message: AssistantMessage; failure: Failure };
/** What the most recent request did, for on-demand inspection. */
interface RouteReport {
route: "pinned" | "auto";
requested: string;
/** `<backend>/<model>` that produced the response, when one did. */
served?: string;
/** The grts model that made the call; differs from `served` behind virtual routers. */
candidate?: string;
hops: number;
outcome: Ending | "exhausted";
notes: readonly string[];
at: number;
}
export function createRouter(state: RouterState) {
const coolingUntil = new Map<Backend, number>();
const perf = createPerf();
let lastRoute: RouteReport | undefined;
function report(
end: ReturnType<NonNullable<typeof span>> | undefined,
route: RouteReport,
backend?: Backend,
): void {
lastRoute = route;
end?.("finish", {
outcome: route.outcome,
...(route.route === "auto" ? { hops: route.hops } : {}),
...(backend ? { backend } : {}),
});
}
const isActive = (until: number | undefined) => (until ?? 0) > Date.now();
const cooling = (backend: Backend) => isActive(coolingUntil.get(backend));
function upstream(
model: GratisModel,
requested: GratisModel,
context: TranscriptContext,
options: SimpleStreamOptions | undefined,
): AssistantMessageEventStream {
const backend = backendOf(model);
const api = (state.apis ?? APIS)[model.api];
const key = backend ? state.key(backend) : null;
if (!backend || !api || key === null)
return failedStream(
requested,
!api
? `grts: no transport for API "${model.api}"`
: `grts: backend for ${model.id} is not available`,
);
const id = upstreamId(model);
// Same-model history keeps provider-native replay state such as thinking signatures.
const messages = context.messages.map((message) =>
message.role === "assistant" &&
message.provider === PROVIDER &&
message.model === model.id &&
requested.id === model.id
? { ...message, model: id }
: message,
);
const maxTokens = Math.min(
options?.maxTokens ?? model.maxTokens,
model.maxTokens,
);
return api.streamSimple(
{ ...model, id, baseUrl: state.resolveUrl(model.baseUrl) },
{ ...context, messages },
{
...options,
maxTokens,
apiKey: key ?? ANONYMOUS_KEY,
headers:
key === undefined
? { ...options?.headers, Authorization: null }
: options?.headers,
},
);
}
/**
* Forward one hop. Before any content arrives an error is returned instead
* of emitted, so the cascade can retry the same prompt elsewhere.
*/
async function forward(
source: AssistantMessageEventStream,
target: AssistantMessageEventStream,
requested: GratisModel,
hop: GratisModel,
holdErrors: boolean,
onProgress?: () => void,
): Promise<Outcome> {
let held: AssistantMessageEvent | undefined;
let committed = !holdErrors;
let ending: Ending = "ok";
const backend = backendOf(hop) ?? "unknown";
let served = `${backend}/${upstreamId(hop)}`;
// Passive measurement of this real hop; nothing is ever sent just to measure.
const startedAt = performance.now();
let firstAt: number | undefined;
let outputTokens = 0;
let failure: Failure | undefined;
for await (const event of source) {
if (event.type === "error") {
ending = event.reason;
failure = classifyFailure(event.error, hop.contextWindow);
} else if (event.type === "done")
outputTokens = event.message.usage.output;
else if (event.type !== "start") firstAt ??= performance.now();
const message = eventMessage(event);
message.provider = PROVIDER;
message.model = requested.id;
// Only the final message: transports fill the upstream's own model id
// (what routers like kilo-auto/free resolved to) while streaming.
if (event.type === "done" || event.type === "error") {
served = `${backend}/${message.responseModel ?? upstreamId(hop)}`;
if (served === requested.id) delete message.responseModel;
else message.responseModel = served;
}
if (!committed) {
if (event.type === "start") {
held = event;
continue;
}
if (event.type === "error") {
if (event.reason !== "aborted" && failure !== "overflow")
perf.record(backend, hop.id, { kind: "fail" });
return {
kind: "failed",
message: event.error,
failure: failure ?? "other",
};
}
committed = true;
onProgress?.();
if (held) target.push(held);
}
target.push(event);
}
target.end();
if (ending === "ok" && firstAt !== undefined) {
const streamingMs = performance.now() - firstAt;
perf.record(backend, hop.id, {
kind: "ok",
firstTokenMs: firstAt - startedAt,
...(outputTokens > 0 && streamingMs >= 50
? { tokensPerSecond: outputTokens / (streamingMs / 1000) }
: {}),
});
} else if (ending === "error" && failure !== "overflow")
perf.record(backend, hop.id, { kind: "fail" });
return { kind: "streamed", ending, served };
}
function pinned(
model: GratisModel,
context: TranscriptContext,
options?: SimpleStreamOptions,
): AssistantMessageEventStream {
const out = createAssistantMessageEventStream();
const end = span?.("request", { route: "pinned" });
const source = safeUpstream(model, () =>
upstream(model, model, context, options),
);
void forward(source, out, model, model, false).then(
(outcome) =>
report(end, {
route: "pinned",
requested: model.id,
...(outcome.kind === "streamed"
? { served: outcome.served, candidate: model.id }
: {}),
hops: 1,
outcome: outcome.kind === "streamed" ? outcome.ending : "error",
notes: [],
at: Date.now(),
}),
(error: unknown) => {
end?.("error", { outcome: "error" });
fail(out, model, errorText(error));
},
);
return out;
}
const modelCoolingUntil = new Map<string, number>();
const modelCooling = (model: GratisModel) =>
isActive(modelCoolingUntil.get(model.id));
// User-requested cooldowns; unlike automatic ones they also skip the last-resort pass.
const manualUntil = new Map<string, number>();
const manuallyCooled = (model: GratisModel) =>
isActive(manualUntil.get(model.id));
const coolModel = (model: GratisModel, ms: number) =>
modelCoolingUntil.set(model.id, Date.now() + ms);
const deadlineMs =
state.firstResponseDeadlineMs ?? FIRST_RESPONSE_DEADLINE_MS;
function auto(
requested: GratisModel,
context: TranscriptContext,
options?: SimpleStreamOptions,
): AssistantMessageEventStream {
const out = createAssistantMessageEventStream();
const end = span?.("request", { route: "auto" });
void (async () => {
const notes: string[] = [];
let hops = 0;
let lastOverflow = false;
const done = (
outcome: RouteReport["outcome"],
backend?: Backend,
served?: string,
candidate?: string,
) =>
report(
end,
{
route: "auto",
requested: requested.id,
...(served ? { served } : {}),
...(candidate ? { candidate } : {}),
hops,
outcome,
notes: [...notes],
at: Date.now(),
},
backend,
);
/** One hop; with a deadline, silence before the first output counts as slow. */
async function attempt(
hop: GratisModel,
deadline: number | undefined,
): Promise<Outcome | "slow"> {
hops++;
const hopAbort = new AbortController();
let timer =
deadline === undefined
? undefined
: setTimeout(() => hopAbort.abort(), deadline);
const signal = options?.signal
? AbortSignal.any([options.signal, hopAbort.signal])
: hopAbort.signal;
const source = safeUpstream(hop, () =>
upstream(hop, requested, context, {
...options,
signal,
// Cascade instead of in-hop backoff; Pi retries the whole turn.
maxRetries: 0,
}),
);
try {
const outcome = await forward(
source,
out,
requested,
hop,
true,
() => {
clearTimeout(timer);
timer = undefined;
},
);
if (
outcome.kind === "failed" &&
hopAbort.signal.aborted &&
!options?.signal?.aborted
)
return "slow";
return outcome;
} finally {
clearTimeout(timer);
}
}
type Step =
| { stop: true }
| { stop: false; failure: Exclude<Failure, "other"> | "slow" };
const abort = (message = "Request was aborted"): Step => {
done("aborted");
fail(out, requested, message, "aborted");
return { stop: true };
};
/** Run one hop and settle the request when it ends it. */
async function step(
hop: GratisModel,
backend: Backend,
deadline: number | undefined,
): Promise<Step> {
if (options?.signal?.aborted) return abort();
const outcome = await attempt(hop, deadline);
if (outcome === "slow") {
perf.record(backend, hop.id, { kind: "fail" });
coolModel(hop, SLOW_COOLDOWN_MS);
return { stop: false, failure: "slow" };
}
if (outcome.kind === "streamed") {
done(outcome.ending, backend, outcome.served, hop.id);
return { stop: true };
}
if (outcome.message.stopReason === "aborted")
return abort(outcome.message.errorMessage);
if (outcome.failure === "other") {
notes.push(`${backend} error`);
done("error", backend);
fail(out, requested, outcome.message.errorMessage ?? "");
return { stop: true };
}
if (outcome.failure === "overflow") {
lastOverflow = true;
return { stop: false, failure: "overflow" };
}
lastOverflow = false;
if (outcome.failure === "unavailable") {
coolModel(hop, UNAVAILABLE_COOLDOWN_MS);
return { stop: false, failure: "unavailable" };
}
coolModel(hop, MODEL_COOLDOWN_MS);
return { stop: false, failure: outcome.failure };
}
// Best skipped or timed-out candidate per backend, for the last-resort pass.
const lastResort = new Map<Backend, GratisModel>();
// Quality order, with chronically failing backends and models behind their peers.
for (const backend of perf.orderBackends(BACKENDS)) {
const candidates = perf
.orderModels(state.candidates(backend))
.filter((hop) => !manuallyCooled(hop));
if (candidates.length === 0) continue;
if (cooling(backend)) {
notes.push(`${backend} cooling`);
lastResort.set(backend, candidates[0] as GratisModel);
continue;
}
let exhausted: "rate" | "quota" | undefined;
let slow = false;
let overflowed = false;
for (const hop of candidates) {
if (modelCooling(hop)) {
if (!lastResort.has(backend)) lastResort.set(backend, hop);
continue;
}
const result = await step(
hop,
backend,
perf.deadline(hop.id, deadlineMs),
);
if (result.stop) return;
if (result.failure === "overflow") {
overflowed = true;
continue;
}
if (result.failure === "unavailable") continue;
if (result.failure === "slow") {
// One silent hop means a congested backend: never wait on its siblings too.
if (!lastResort.has(backend)) lastResort.set(backend, hop);
slow = true;
break;
}
exhausted =
exhausted === "quota" || result.failure === "quota"
? "quota"
: "rate";
}
if (slow) {
coolingUntil.set(backend, Date.now() + SLOW_COOLDOWN_MS);
notes.push(`${backend} slow`);
} else if (exhausted) {
// Every tried candidate failed; cool for the observed limit.
const info = BACKEND_INFO[backend];
coolingUntil.set(
backend,
Date.now() +
(exhausted === "quota"
? info.quotaCooldownMs
: info.rateCooldownMs),
);
notes.push(`${backend} exhausted (${exhausted})`);
} else if (overflowed) {
notes.push(`${backend} context overflow`);
}
}
// Cooling windows are estimates: give skipped and slow candidates one untimed try.
for (const [backend, hop] of lastResort) {
const result = await step(hop, backend, undefined);
if (result.stop) return;
}
if (lastResort.size > 0) notes.push("last resort failed");
done("exhausted");
const summary = `grts/auto: no free backend completed the request: ${notes.join(", ") || "no backend available"}`;
fail(
out,
requested,
lastOverflow ? `${OVERFLOW_PREFIX}: ${summary}` : summary,
);
})().catch((error: unknown) => {
end?.("error", { outcome: "error" });
fail(out, requested, errorText(error));
});
return out;
}
return {
streamSimple(
model: GratisModel,
context: TranscriptContext,
options?: SimpleStreamOptions,
): AssistantMessageEventStream {
return model.id === AUTO_ID
? auto(model, context, options)
: pinned(model, context, options);
},
/** Last route plus backends still cooling, soonest recovery first. */
status() {
const now = Date.now();
const coolingBackends = [...coolingUntil]
.filter(([, until]) => until > now)
.sort((a, b) => a[1] - b[1])
.map(([backend, until]) => ({ backend, until }));
const manual = [...manualUntil]
.filter(([, until]) => until > now)
.sort((a, b) => a[1] - b[1])
.map(([model, until]) => ({ model, until }));
return {
last: lastRoute,
cooling: coolingBackends,
manual,
models: perf.snapshot(),
};
},
/** Skip a grts model in `grts/auto`, including its last-resort pass, for `ms`. */
cool(modelId: string, ms: number): void {
manualUntil.set(modelId, Date.now() + ms);
// Also a quality vote, so a repeatedly rejected model stays behind its peers.
perf.vote(modelId);
},
/** Routing measurements for persistence across restarts. */
learned: {
export: () => perf.export(),
import: (state: unknown) => perf.import(state),
onChange: (listener: () => void) => perf.onChange(listener),
},
/** End manual cooldowns: one model, or all; returns how many ended. */
uncool(modelId?: string): number {
const now = Date.now();
const active = [...manualUntil].filter(
([id, until]) =>
until > now && (modelId === undefined || id === modelId),
);
for (const [id] of active) manualUntil.delete(id);
return active.length;
},
};
}
function safeUpstream(
model: GratisModel,
start: () => AssistantMessageEventStream,
): AssistantMessageEventStream {
try {
return start();
} catch (error) {
return failedStream(model, errorText(error));
}
}
function eventMessage(event: AssistantMessageEvent): AssistantMessage {
return "partial" in event
? event.partial
: event.type === "done"
? event.message
: event.error;
}
function fail(
out: AssistantMessageEventStream,
model: GratisModel,
errorMessage: string,
reason: "error" | "aborted" = "error",
): void {
const message: AssistantMessage = {
role: "assistant",
content: [],
api: model.api,
provider: PROVIDER,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
},
stopReason: reason,
errorMessage,
timestamp: Date.now(),
};
out.push({ type: "error", reason, error: message });
out.end();
}
function failedStream(
model: GratisModel,
errorMessage: string,
): AssistantMessageEventStream {
const out = createAssistantMessageEventStream();
fail(out, model, errorMessage);
return out;
}
function errorText(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}