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
12 changes: 6 additions & 6 deletions src/api/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,16 @@ describe("YottaClient", () => {
it("scaleEndpointWorkers sends PUT with count query param", async () => {
const fetchMock = mockFetch(null);
const client = await freshClient();
await client.scaleEndpointWorkers("ep-1", 4);
await client.scaleEndpointWorkers(1, 4);
const [url, opts] = fetchMock.mock.calls[0];
expect(url).toBe("https://test.yotta.com/v2/endpoints/ep-1/workers?count=4");
expect(url).toBe("https://test.yotta.com/v2/endpoints/1/workers?count=4");
expect(opts.method).toBe("PUT");
});

it("listEndpointTasks builds query string", async () => {
const fetchMock = mockFetch({ pageNumber: 1, pageSize: 10, totalRow: 0, records: [] });
const client = await freshClient();
await client.listEndpointTasks("ep-1", { status: 2, pageNumber: 1, pageSize: 5 });
await client.listEndpointTasks(1, { status: 2, pageNumber: 1, pageSize: 5 });
const [url] = fetchMock.mock.calls[0];
expect(url).toContain("status=2");
expect(url).toContain("pageNumber=1");
Expand All @@ -172,13 +172,13 @@ describe("YottaClient", () => {

describe("Registry", () => {
it("createRegistryCredential sends POST", async () => {
const fetchMock = mockFetch({ id: 1, name: "test", type: "DOCKER_HUB", createdAt: "2024-01-01T00:00:00Z" });
const fetchMock = mockFetch({ id: 1, name: "test", createdAt: "2024-01-01T00:00:00Z" });
const client = await freshClient();
await client.createRegistryCredential({ name: "test", type: "DOCKER_HUB", username: "u", password: "p" });
await client.createRegistryCredential({ name: "test", username: "u", password: "p" });
const [url, opts] = fetchMock.mock.calls[0];
expect(url).toBe("https://test.yotta.com/v2/container-registry-auths");
expect(opts.method).toBe("POST");
expect(JSON.parse(opts.body)).toMatchObject({ name: "test", type: "DOCKER_HUB" });
expect(JSON.parse(opts.body)).toMatchObject({ name: "test", username: "u" });
});
});
});
22 changes: 9 additions & 13 deletions src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,17 +125,13 @@ class YottaClient {
return this.request<unknown>("POST", `/pods/${id}/resume`);
}

stopPod(id: number) {
return this.request<unknown>("POST", `/pods/${id}/stop`);
}

// --- Endpoints ---

createEndpoint(req: CreateEndpointRequest) {
return this.request<Endpoint>("POST", "/endpoints", req);
}

getEndpoint(id: string) {
getEndpoint(id: number) {
return this.request<Endpoint>("GET", `/endpoints/${id}`);
}

Expand All @@ -144,32 +140,32 @@ class YottaClient {
return this.request<Endpoint[]>("GET", `/endpoints${qs}`);
}

updateEndpoint(id: string, req: UpdateEndpointRequest) {
updateEndpoint(id: number, req: UpdateEndpointRequest) {
return this.request<Endpoint>("PATCH", `/endpoints/${id}`, req);
}

deleteEndpoint(id: string) {
deleteEndpoint(id: number) {
return this.request<boolean>("DELETE", `/endpoints/${id}`);
}

stopEndpoint(id: string) {
stopEndpoint(id: number) {
return this.request<unknown>("POST", `/endpoints/${id}/stop`);
}

startEndpoint(id: string) {
startEndpoint(id: number) {
return this.request<unknown>("POST", `/endpoints/${id}/start`);
}

scaleEndpointWorkers(id: string, count: number) {
scaleEndpointWorkers(id: number, count: number) {
return this.request<unknown>("PUT", `/endpoints/${id}/workers?count=${count}`);
}

listEndpointWorkers(id: string, statusList?: string) {
listEndpointWorkers(id: number, statusList?: string) {
const qs = statusList ? `?statusList=${statusList}` : "";
return this.request<EndpointWorker[]>("GET", `/endpoints/${id}/workers${qs}`);
}

listEndpointTasks(id: string, params?: { status?: number; pageNumber?: number; pageSize?: number }) {
listEndpointTasks(id: number, params?: { status?: number; pageNumber?: number; pageSize?: number }) {
const query = new URLSearchParams();
if (params?.status !== undefined) query.set("status", String(params.status));
if (params?.pageNumber) query.set("pageNumber", String(params.pageNumber));
Expand All @@ -178,7 +174,7 @@ class YottaClient {
return this.request<PaginatedData<EndpointTask>>("GET", `/endpoints/${id}/tasks${qs ? `?${qs}` : ""}`);
}

getEndpointTaskCount(id: string) {
getEndpointTaskCount(id: number) {
return this.request<TaskCount>("GET", `/endpoints/${id}/tasks/count`);
}
}
Expand Down
76 changes: 67 additions & 9 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,11 @@ export interface PaginatedData<T> {
export interface RegistryCredential {
id: number;
name: string;
type: "DOCKER_HUB" | "GCR" | "ECR" | "ACR" | "PRIVATE";
createdAt: string;
}

export interface CreateRegistryCredentialRequest {
name: string;
type: "DOCKER_HUB" | "GCR" | "ECR" | "ACR" | "PRIVATE";
username: string;
password: string;
}
Expand All @@ -52,6 +50,7 @@ export interface Vm {
id: number;
name: string;
status: string;
gpuType?: string;
gpuDisplayName: string;
ipAddress: string;
cpuCores: number;
Expand All @@ -62,7 +61,10 @@ export interface Vm {
storageInGb: string;
sshTemplate: string;
createdAt: string;
updatedAt?: string;
terminatedAt?: string;
isSpot: number;
osInfo?: string;
}

export interface CreateVmRequest {
Expand All @@ -82,40 +84,85 @@ export interface ListVmsRequest {

// --- Pods ---

export interface PodExposePort {
port: number;
protocol?: string;
}

export interface PodExposePortResponse {
port: number;
proxyPort?: number;
protocol?: string;
host?: string;
healthy?: boolean;
ingressUrl?: string;
serviceName?: string;
}

export interface Pod {
id: number;
name: string;
image: string;
imageRegistry?: string;
gpuType: string;
gpuDisplayName: string;
gpuCount: number;
resourceType?: string;
region?: string;
cloudType?: string;
containerVolumeInGb?: number;
shmInGb?: number;
singleCardVramInGb?: number;
singleCardRamInGb?: number;
singleCardVcpu?: number;
singleCardPrice?: number;
environmentVars?: { key: string; value: string }[];
expose?: PodExposePortResponse[];
initializationCommand?: string;
sshCmd?: string;
internalIp?: string;
status: string;
createdAt: string;
sshCmd?: string;
updatedAt?: string;
}

export interface CreatePodRequest {
name: string;
image: string;
gpuType: string;
gpuCount: number;
region?: string;
regionList?: string[];
containerRegistryAuthId?: number;
imageRegistry?: string;
containerVolumeInGb?: number;
envVars?: { key: string; value: string }[];
ports?: number[];
initializationCommand?: string;
environmentVars?: { key: string; value: string }[];
expose?: PodExposePort[];
}

// --- Endpoints ---

export interface Endpoint {
id: string;
id: number;
name: string;
image: string;
status: string;
imageRegistry?: string;
resources?: EndpointResource[];
containerVolumeInGb?: number;
environmentVars?: { key: string; value: string }[];
expose?: { port: number; protocol?: string };
totalWorkers: number;
runningWorkers: number;
cost: number;
serviceMode: "ALB" | "QUEUE" | "CUSTOM";
perHourPrice?: number;
perSecondPrice?: number;
serviceMode: string;
status: string;
domain?: string;
creator?: string;
createdAt: string;
updatedAt?: string;
}

export interface EndpointResource {
Expand All @@ -137,7 +184,9 @@ export interface CreateEndpointRequest {
protocol: string;
proxyPort?: number;
};
serviceMode: "ALB" | "QUEUE" | "CUSTOM";
serviceMode: string;
initializationCommand?: string;
credentialId?: number;
}

export interface UpdateEndpointRequest {
Expand All @@ -148,6 +197,15 @@ export interface UpdateEndpointRequest {

export interface EndpointWorker {
id: string;
region?: string;
gpuType?: string;
gpuDisplayName?: string;
gpuCount?: number;
singleCardVramInGb?: number;
singleCardVcpu?: number;
singleCardRamInGb?: number;
uptime?: number;
cost?: number;
status: string;
}

Expand Down
2 changes: 1 addition & 1 deletion src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("getConfig", () => {
delete process.env.YOTTA_API_BASE_URL;
const { getConfig } = await import("./config.js");
const config = getConfig();
expect(config.apiBaseUrl).toBe("https://api.yotta.com");
expect(config.apiBaseUrl).toBe("https://api.test.yottalabs.ai");
});

it("uses custom base URL from env", async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function getConfig(): Config {
if (!apiKey) throw new Error("YOTTA_API_KEY environment variable is required");

_config = {
apiBaseUrl: (process.env.YOTTA_API_BASE_URL || "https://api.yotta.com").replace(/\/$/, ""),
apiBaseUrl: (process.env.YOTTA_API_BASE_URL || "https://api.test.yottalabs.ai").replace(/\/$/, ""),
apiKey,
};
return _config;
Expand Down
18 changes: 9 additions & 9 deletions src/tools/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function registerEndpointTools(server: McpServer): void {
server.tool(
"endpoint_get",
"Get details of a specific endpoint by ID",
{ id: z.string().describe("Endpoint ID") },
{ id: z.number().describe("Endpoint ID") },
async ({ id }) => {
const ep = await getClient().getEndpoint(id);
return { content: [{ type: "text" as const, text: JSON.stringify(ep, null, 2) }] };
Expand All @@ -61,7 +61,7 @@ export function registerEndpointTools(server: McpServer): void {
"endpoint_update",
"Update an endpoint (name, workers, env vars). Cannot change image.",
{
id: z.string().describe("Endpoint ID"),
id: z.number().describe("Endpoint ID"),
name: z.string().optional().describe("New name"),
workers: z.number().optional().describe("New worker count"),
envVars: z.array(envVarSchema).optional().describe("New environment variables"),
Expand All @@ -75,7 +75,7 @@ export function registerEndpointTools(server: McpServer): void {
server.tool(
"endpoint_delete",
"Delete an endpoint. This action is irreversible.",
{ id: z.string().describe("Endpoint ID") },
{ id: z.number().describe("Endpoint ID") },
async ({ id }) => {
await getClient().deleteEndpoint(id);
return { content: [{ type: "text" as const, text: `Endpoint ${id} deleted.` }] };
Expand All @@ -85,7 +85,7 @@ export function registerEndpointTools(server: McpServer): void {
server.tool(
"endpoint_stop",
"Stop a running endpoint",
{ id: z.string().describe("Endpoint ID") },
{ id: z.number().describe("Endpoint ID") },
async ({ id }) => {
await getClient().stopEndpoint(id);
return { content: [{ type: "text" as const, text: `Endpoint ${id} stopped.` }] };
Expand All @@ -95,7 +95,7 @@ export function registerEndpointTools(server: McpServer): void {
server.tool(
"endpoint_start",
"Start a stopped endpoint",
{ id: z.string().describe("Endpoint ID") },
{ id: z.number().describe("Endpoint ID") },
async ({ id }) => {
await getClient().startEndpoint(id);
return { content: [{ type: "text" as const, text: `Endpoint ${id} started.` }] };
Expand All @@ -106,7 +106,7 @@ export function registerEndpointTools(server: McpServer): void {
"endpoint_scale",
"Scale the number of workers for an endpoint",
{
id: z.string().describe("Endpoint ID"),
id: z.number().describe("Endpoint ID"),
count: z.number().describe("Target worker count"),
},
async ({ id, count }) => {
Expand All @@ -119,7 +119,7 @@ export function registerEndpointTools(server: McpServer): void {
"endpoint_list_workers",
"List workers for an endpoint",
{
id: z.string().describe("Endpoint ID"),
id: z.number().describe("Endpoint ID"),
statusList: z.string().optional().describe("Filter by worker status (comma-separated)"),
},
async ({ id, statusList }) => {
Expand All @@ -132,7 +132,7 @@ export function registerEndpointTools(server: McpServer): void {
"endpoint_list_tasks",
"List tasks for a QUEUE-mode endpoint",
{
id: z.string().describe("Endpoint ID"),
id: z.number().describe("Endpoint ID"),
status: z.number().optional().describe("Filter: 0=PROCESSING, 1=DELIVERED, 2=SUCCESS, 3=FAILED"),
pageNumber: z.number().optional().describe("Page number"),
pageSize: z.number().optional().describe("Page size"),
Expand All @@ -146,7 +146,7 @@ export function registerEndpointTools(server: McpServer): void {
server.tool(
"endpoint_task_count",
"Get task status counts for a QUEUE-mode endpoint",
{ id: z.string().describe("Endpoint ID") },
{ id: z.number().describe("Endpoint ID") },
async ({ id }) => {
const counts = await getClient().getEndpointTaskCount(id);
return { content: [{ type: "text" as const, text: JSON.stringify(counts, null, 2) }] };
Expand Down
Loading