gadget/gadget-drone/src/services/platform.ts
Rob Colbert 8333672683 platform.apiKey becomes platform.gadgetKey
gadget-drone now presents an ApiClient _id value as the Gadget Key,
allowing gadget-code to reference the client, determine the associated
User, and invoke logic on the User's behalf as an authenticated and
authorized client.
2026-05-05 08:12:34 -04:00

208 lines
5.2 KiB
TypeScript

// src/services/platform.ts
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
// Licensed under the Apache License, Version 2.0
import env from "../config/env.ts";
import assert from "node:assert";
import path from "node:path";
import os from "node:os";
import { GadgetService } from "../lib/service.ts";
import {
DroneStatus,
IChatSession,
IChatTurn,
IDroneRegistration,
IUser,
Types,
} from "@gadget/api";
interface PlatformApiResponse {
success: boolean;
message?: string;
}
interface PlatformRegistrationResponse extends PlatformApiResponse {
data: IDroneRegistration;
}
interface ChatSessionContextResponse extends PlatformApiResponse {
data: IChatTurn[];
}
class PlatformService extends GadgetService {
registration: IDroneRegistration | undefined;
get name(): string {
return "PlatformService";
}
get slug(): string {
return "svc:platform";
}
async start(): Promise<void> {
this.log.info("started");
}
async stop(): Promise<void> {
this.log.info("stopped");
}
async register(
email: string,
password: string,
workspaceDir: string,
workspaceId: string,
): Promise<IDroneRegistration> {
const url = this.getApiUrl("/drone/registration");
this.log.info("registering with Gadget Code Platform", {
workspaceId,
email,
url,
});
const body = JSON.stringify({
email,
password,
hostname: os.hostname(),
workspaceDir,
workspaceId,
});
const response = await fetch(url, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Content-Length": body.length.toString(),
"X-Gadget-Key": env.platform.gadgetKey,
},
body,
});
const json = (await response.json()) as PlatformRegistrationResponse;
this.log.debug("platform registration response", json);
if (!json.success) {
const error = new Error("failed to register drone with Platform");
error.name = "PlatformError";
error.statusCode = response.status;
throw error;
}
if (!json.data || !json.data._id) {
const error = new Error(
"registration response did not contain required data",
);
error.name = "PlatformError";
throw error;
}
if (!json.data.user || !(json.data.user as IUser)._id) {
const error = new Error(
"registration response did not contain required user account data",
);
error.name = "PlatformError";
throw error;
}
this.registration = json.data;
return json.data;
}
async unregister(): Promise<void> {
if (!this.registration) {
return;
}
const url = this.getApiUrl(`/drone/registration/${this.registration._id}`);
const body = JSON.stringify({ registration: this.registration });
const response = await fetch(url, {
method: "DELETE",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Content-Length": body.length.toString(),
"X-Gadget-Key": env.platform.gadgetKey,
},
body,
});
const json = (await response.json()) as PlatformRegistrationResponse;
if (!json.success) {
const error = new Error("failed to register drone with Platform");
error.name = "PlatformError";
error.statusCode = response.status;
throw error;
}
}
async setStatus(status: DroneStatus): Promise<void> {
assert(
this.registration,
"must register with platform before setting status",
);
const url = this.getApiUrl(
`/drone/registration/${this.registration._id}/status`,
);
const body = JSON.stringify({ status });
const response = await fetch(url, {
method: "PUT",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
"Content-Length": body.length.toString(),
"X-Gadget-Key": env.platform.gadgetKey,
},
body,
});
const json = (await response.json()) as PlatformRegistrationResponse;
if (!json.success) {
const error = new Error("failed to register drone with Platform");
error.name = "PlatformError";
error.statusCode = response.status;
throw error;
}
this.log.info("drone status updated on platform", { status });
}
async getChatSessionContext(
session: IChatSession,
): Promise<ChatSessionContextResponse> {
assert(
this.registration,
"must register with platform before setting status",
);
const url = this.getApiUrl(`/chat-sessions/${session._id}/turns`);
const response = await fetch(url, {
method: "GET",
headers: {
Accept: "application/json",
"X-Gadget-Key": env.platform.gadgetKey,
},
});
const json = (await response.json()) as ChatSessionContextResponse;
if (!json.success) {
const error = new Error("failed to retrieve chat session context");
error.name = "PlatformError";
error.statusCode = response.status;
throw error;
}
this.log.info("chat session context received", {
turnCount: json.data.length,
});
return json;
}
getApiUrl(url: string): string {
return `${env.platform.baseUrl}/api/v1${url}`;
}
}
export default new PlatformService();