File size: 3,821 Bytes
67d18ac | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | /**
* Async (off-peak) types: ticket states, server response shapes, error classes.
*
* Server-side response fields are snake_case (e.g. `ticket_id`, `next_poll_after`).
* The client converts them to camelCase on the way out; callers never see the
* raw server shape.
*
* @see _reverse/NOTEPAD.md "Off-Peak / Idle Plan" section for upstream protocol.
*/
/** Ticket lifecycle states reported by the server. */
export type TicketState = "queued" | "ready" | "active" | "settled" | "expired" | "not_found";
/** Non-terminal states β ticket may still become `ready`. */
export const TICKET_PENDING_STATES: readonly TicketState[] = ["queued"] as const;
/** Terminal states β no further transitions; ticket can be settled. */
export const TICKET_TERMINAL_STATES: readonly TicketState[] = ["settled", "expired", "not_found"] as const;
export function isTicketReady(s: TicketState): boolean {
return s === "ready" || s === "active";
}
export function isTicketExpired(s: TicketState): boolean {
return s === "expired" || s === "not_found";
}
/** Result of `GET /ticket/availability`. */
export interface AvailabilityResult {
canTakeNumber: boolean;
/** Unix seconds β earliest time the user can retry taking a number. Present when `canTakeNumber === false`. */
nextTakeAt?: number;
}
/** Result of `POST /ticket` (take a number). */
export interface TakeTicketResult {
ticketId: string;
state: TicketState;
/** Queue position (1-indexed). Present while `state === "queued"`. */
position?: number;
/** Server-suggested next poll delay in ms. Present when server has a backoff recommendation. */
nextPollAfterMs?: number;
/** Local timestamp (ms) when the ticket was registered. */
registeredAt: number;
}
/** Status of a single ticket from `POST /ticket/status`. */
export interface TicketStatusResult {
ticketId: string;
state: TicketState;
position?: number;
/** Unix seconds β deadline by which the client must use a `ready` ticket. Present on `ready`. */
activeDeadline?: number;
}
/** Result of `POST /ticket/status` (batch poll). */
export interface BatchStatusResult {
/** Server-suggested next poll delay in ms (applies to all returned tickets). */
nextPollAfterMs?: number;
tickets: TicketStatusResult[];
}
/** Credentials needed to authenticate with the off-peak backend. */
export interface OffPeakCredentials {
/** ZCode plan JWT β goes in `Authorization: Bearer ${jwt}`. Source: `Credential.jwt`. */
jwt: string;
/** Coding-plan API key β goes in `X-Coding-Plan-Api-Key`. Source: `Credential.apiKey`. */
codingPlanApiKey: string;
/** Optional Bigmodel-team org/project headers. */
bigmodelOrganization?: string;
bigmodelProject?: string;
}
/** HTTP-status-bearing error from the off-peak server. */
export class OffPeakServerError extends Error {
readonly httpStatus: number;
readonly bizCode?: string;
constructor(message: string, httpStatus: number, bizCode?: string) {
super(message);
this.name = "OffPeakServerError";
this.httpStatus = httpStatus;
this.bizCode = bizCode;
}
}
/** Thrown when credentials lack the JWT required for off-peak auth. */
export class OffPeakCredentialsUnavailableError extends Error {
constructor(message: string = "off-peak requires a logged-in oauth credential (jwt missing)") {
super(message);
this.name = "OffPeakCredentialsUnavailableError";
}
}
/** Detects the upstream "off-peak-ticket-expired" error signal in any error message. */
export function isOffPeakTicketExpiredError(e: unknown): boolean {
if (e == null) return false;
if (typeof e === "string") return e.includes("off-peak-ticket-expired");
if (e instanceof Error) {
return e.message.includes("off-peak-ticket-expired") || e.name === "OffPeakTicketExpiredError";
}
return false;
}
|