Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions src/helpers/fetchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,3 @@ export async function getContract(contractId: string) {
}
return data;
}

export async function getOrder(orderId: string) {
const api = await apiClient();
const { data, response, error } = await api.GET("/v0/orders/{id}", {
params: {
path: { id: orderId },
},
});
if (!response.ok) {
// @ts-expect-error -- TODO: FIXME: include error in OpenAPI schema output
if (error?.code === "order.not_found") {
return null;
}
return logAndQuit(`Failed to get order: ${response.statusText}`);
}
return data;
}
29 changes: 29 additions & 0 deletions src/helpers/format-date.ts → src/helpers/format-time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
startOfDay,
} from "date-fns";
import dayjs, { type Dayjs } from "dayjs";
import duration from "dayjs/plugin/duration";
import utc from "dayjs/plugin/utc";
import { formatDateRange } from "little-date";

dayjs.extend(utc);
dayjs.extend(duration);

const shortenAmPm = (text: string): string => {
const shortened = (text || "").replace(/ AM/g, "am").replace(/ PM/g, "pm");
Expand Down Expand Up @@ -133,3 +135,30 @@ export const formatDateAsUTC = (date: Dayjs): string => {

return `${utcDate.format("MMM D")}, ${timeStr} UTC`;
};

/**
* Formats a duration in milliseconds to a compact string like "1d2h30m".
*/
export function formatDuration(ms: number) {
const d = dayjs.duration(ms);

const years = Math.floor(d.asYears());
const weeks = Math.floor(d.asWeeks()) % 52;
const days = d.days();
const hours = d.hours();
const minutes = d.minutes();
const seconds = d.seconds();
const milliseconds = d.milliseconds();

let result = "";

if (years > 0) result += `${years}y`;
if (weeks > 0) result += `${weeks}w`;
if (days > 0) result += `${days}d`;
if (hours > 0) result += `${hours}h`;
if (minutes > 0) result += `${minutes}m`;
if (seconds > 0) result += `${seconds}s`;
if (milliseconds > 0) result += `${milliseconds}ms`;

return result || "0ms";
}
98 changes: 98 additions & 0 deletions src/helpers/quote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import dayjs from "dayjs";
import { apiClient } from "../apiClient.ts";
import { GPUS_PER_NODE } from "../lib/constants.ts";
import type { components } from "../schema.ts";
import { logAndQuit, logSessionTokenExpiredAndQuit } from "./errors.ts";
import { parseStartDateOrNow, roundDateUpToNextMinute } from "./units.ts";

export function getPricePerGpuHourFromQuote(
quote: Pick<NonNullable<Quote>, "start_at" | "end_at" | "price" | "quantity">,
) {
const startTimeOrNow = parseStartDateOrNow(quote.start_at);

// from the market's perspective, "NOW" means at the beginning of the next minute.
// when the order duration is very short, this can cause the rate to be computed incorrectly
// if we implicitly assume it to mean `new Date()`.
const coercedStartTime =
startTimeOrNow === "NOW"
? roundDateUpToNextMinute(new Date())
: startTimeOrNow;
const durationSeconds = dayjs(quote.end_at).diff(dayjs(coercedStartTime));
const durationHours = durationSeconds / 3600 / 1000;

return quote.price / GPUS_PER_NODE / quote.quantity / durationHours;
}

export async function getQuote(options: {
instanceType?: string;
quantity: number;
minStartTime: Date | "NOW";
maxStartTime: Date | "NOW";
minDurationSeconds: number;
maxDurationSeconds: number;
cluster?: string;
colocateWith?: string;
}) {
const api = await apiClient();

const params = {
query: {
side: "buy",
instance_type: options.instanceType,
quantity: options.quantity,
min_start_date:
options.minStartTime === "NOW"
? ("NOW" as const)
: options.minStartTime.toISOString(),
max_start_date:
options.maxStartTime === "NOW"
? ("NOW" as const)
: options.maxStartTime.toISOString(),
min_duration: options.minDurationSeconds,
max_duration: options.maxDurationSeconds,
cluster: options.cluster,
colocate_with: options.colocateWith,
},
} as const;

const { data, error, response } = await api.GET("/v0/quote", {
params,
// timeout after 600 seconds
signal: AbortSignal.timeout(600 * 1000),
});

if (!response.ok) {
switch (response.status) {
case 400:
return logAndQuit(`Bad Request: ${JSON.stringify(error, null, 2)}`);
case 401:
return await logSessionTokenExpiredAndQuit();
case 500:
return logAndQuit(
`Failed to get quote: ${JSON.stringify(error, null, 2)}`,
);
default:
return logAndQuit(`Failed to get quote: ${response.statusText}`);
}
}

if (!data) {
return logAndQuit(
`Failed to get quote: Unexpected response from server: ${response}`,
);
}

if (!data.quote) {
return null;
}

return {
...data.quote,
price: Number(data.quote.price),
quantity: Number(data.quote.quantity),
start_at: data.quote.start_at,
end_at: data.quote.end_at,
};
}

export type Quote = components["schemas"]["quoter_ApiQuoteDetails"] | null;
2 changes: 1 addition & 1 deletion src/helpers/units.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import type { Nullable } from "../types/empty.ts";
import { formatDate, formatDateAsUTC } from "./format-date.ts";
import { formatDate, formatDateAsUTC } from "./format-time.ts";

dayjs.extend(utc);
dayjs.extend(timezone);
Expand Down
4 changes: 0 additions & 4 deletions src/helpers/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ const apiPaths: Record<string, Path<IdParams | never>> = {
me: "/v0/me",
ping: "/v0/ping",

orders_list: "/v0/orders",
orders_get: ({ id }: IdParams): string => `/v0/orders/${id}`,
orders_cancel: ({ id }: IdParams): string => `/v0/orders/${id}`,

quote_get: "/v0/quote",

instances_list: "/v0/instances",
Expand Down
8 changes: 0 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,13 @@ import { checkVersion } from "./checkVersion.ts";
import { loadConfig, saveConfig } from "./helpers/config.ts";
import { getAppBanner } from "./lib/app-banner.ts";
import { registerBalance } from "./lib/balance.ts";
import { registerBuy } from "./lib/buy/index.tsx";
import { registerContracts } from "./lib/contracts/index.tsx";
import { registerDev } from "./lib/dev.ts";
import { registerExtend } from "./lib/extend/index.tsx";
import { registerLogin } from "./lib/login.ts";
import { registerMe } from "./lib/me.ts";
import { registerNodes } from "./lib/nodes/index.ts";
import { registerOrders } from "./lib/orders/index.tsx";
import { analytics, IS_TRACKING_DISABLED } from "./lib/posthog.ts";
import { registerScale } from "./lib/scale/index.tsx";
import { registerSell } from "./lib/sell.ts";
import { registerTokens } from "./lib/tokens.ts";
import { registerUpgrade } from "./lib/upgrade.ts";
import { registerVM } from "./lib/vm/index.ts";
Expand All @@ -47,11 +43,7 @@ async function main() {

// commands
registerLogin(program);
registerBuy(program);
registerExtend(program);
registerOrders(program);
registerContracts(program);
registerSell(program);
registerBalance(program);
registerTokens(program);
registerUpgrade(program);
Expand Down
55 changes: 0 additions & 55 deletions src/lib/Quote.tsx

This file was deleted.

Loading