fix(admin): use AdminGrpcClient instead of direct fetch, add model capability table

- Replace direct REST fetch with AdminGrpcClient for AI model/provider/pricing routes
- Add model_capability table to sync route
- AdminGrpcClient handles all admin RPC calls with workspace_id routing
This commit is contained in:
ZhenYi 2026-04-22 22:38:59 +08:00
parent aef5280ae8
commit 850a5392ce
13 changed files with 2199 additions and 156 deletions

View File

@ -8,6 +8,8 @@
"name": "admin",
"version": "0.1.0",
"dependencies": {
"@bufbuild/connect": "^0.13.0",
"@bufbuild/protobuf": "^2.11.0",
"@types/node-cron": "^3.0.11",
"argon2": "^0.44.0",
"bcrypt": "^5.1.1",
@ -272,6 +274,22 @@
"node": ">=6.9.0"
}
},
"node_modules/@bufbuild/connect": {
"version": "0.13.0",
"resolved": "https://registry.npmmirror.com/@bufbuild/connect/-/connect-0.13.0.tgz",
"integrity": "sha512-eZSMbVLyUFtXiZNORgCEvv580xKZeYQdMOWj2i/nxOcpXQcrEzTMTA7SZzWv4k4gveWCOSRoWmYDeOhfWXJv0g==",
"deprecated": "Connect has moved to its own org @connectrpc and has a stable v1. Run `npx @connectrpc/connect-migrate@latest` to update. See https://github.com/connectrpc/connect-es/releases/tag/v0.13.1 for details.",
"license": "Apache-2.0",
"peerDependencies": {
"@bufbuild/protobuf": "^1.2.1"
}
},
"node_modules/@bufbuild/protobuf": {
"version": "2.11.0",
"resolved": "https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-2.11.0.tgz",
"integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==",
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@emnapi/core": {
"version": "1.10.0",
"dev": true,

View File

@ -13,6 +13,8 @@
"test:ui": "playwright test --ui"
},
"dependencies": {
"@bufbuild/connect": "^0.13.0",
"@bufbuild/protobuf": "^2.11.0",
"@types/node-cron": "^3.0.11",
"argon2": "^0.44.0",
"bcrypt": "^5.1.1",

View File

@ -1,40 +1,48 @@
import { NextRequest, NextResponse } from "next/server";
import { RUST_BACKEND_URL, ADMIN_API_SHARED_KEY } from "@/lib/env";
import { createModel, updateModel, deleteModel } from "@/lib/adminrpc/client";
export const runtime = "nodejs";
async function adminFetch(path: string, method: string, body?: unknown) {
if (!ADMIN_API_SHARED_KEY) {
return NextResponse.json({ error: "ADMIN_API_SHARED_KEY 未配置" }, { status: 500 });
}
const res = await fetch(`${RUST_BACKEND_URL}${path}`, {
method,
headers: { "Content-Type": "application/json", "x-admin-api-key": ADMIN_API_SHARED_KEY },
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(30_000),
});
const data = await res.json();
if (!res.ok) return NextResponse.json(data, { status: res.status });
return NextResponse.json(data);
}
// POST /api/admin/ai/models — create model
export async function POST(req: NextRequest) {
return adminFetch("/api/admin/ai/models", "POST", await req.json());
try {
const body = await req.json();
const data = await createModel(body);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Create model error:", msg);
return NextResponse.json({ error: `创建失败: ${msg}` }, { status: 500 });
}
}
// PATCH /api/admin/ai/models?id={id} — update model
export async function PATCH(req: NextRequest) {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
return adminFetch(`/api/admin/ai/models/${id}`, "PATCH", await req.json());
try {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
const body = await req.json();
const data = await updateModel(id, body);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Update model error:", msg);
return NextResponse.json({ error: `更新失败: ${msg}` }, { status: 500 });
}
}
// DELETE /api/admin/ai/models?id={id} — delete model
export async function DELETE(req: NextRequest) {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
return adminFetch(`/api/admin/ai/models/${id}`, "DELETE");
try {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
const data = await deleteModel(id);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Delete model error:", msg);
return NextResponse.json({ error: `删除失败: ${msg}` }, { status: 500 });
}
}

View File

@ -1,5 +1,5 @@
import { NextRequest, NextResponse } from "next/server";
import { RUST_BACKEND_URL, ADMIN_API_SHARED_KEY } from "@/lib/env";
import { updatePricing } from "@/lib/adminrpc/client";
export const runtime = "nodejs";
@ -8,18 +8,14 @@ export async function PATCH(
req: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
if (!ADMIN_API_SHARED_KEY) {
return NextResponse.json({ error: "ADMIN_API_SHARED_KEY 未配置" }, { status: 500 });
try {
const { id } = await params;
const body = await req.json();
const data = await updatePricing(id, body);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Update pricing error:", msg);
return NextResponse.json({ error: `更新失败: ${msg}` }, { status: 500 });
}
const body = await req.json();
const res = await fetch(`${RUST_BACKEND_URL}/api/admin/ai/pricing/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json", "x-admin-api-key": ADMIN_API_SHARED_KEY },
body: JSON.stringify(body),
signal: AbortSignal.timeout(30_000),
});
const data = await res.json();
if (!res.ok) return NextResponse.json(data, { status: res.status });
return NextResponse.json(data);
}

View File

@ -1,40 +1,48 @@
import { NextRequest, NextResponse } from "next/server";
import { RUST_BACKEND_URL, ADMIN_API_SHARED_KEY } from "@/lib/env";
import { createProvider, updateProvider, deleteProvider } from "@/lib/adminrpc/client";
export const runtime = "nodejs";
async function adminFetch(path: string, method: string, body?: unknown) {
if (!ADMIN_API_SHARED_KEY) {
return NextResponse.json({ error: "ADMIN_API_SHARED_KEY 未配置" }, { status: 500 });
}
const res = await fetch(`${RUST_BACKEND_URL}${path}`, {
method,
headers: { "Content-Type": "application/json", "x-admin-api-key": ADMIN_API_SHARED_KEY },
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(30_000),
});
const data = await res.json();
if (!res.ok) return NextResponse.json(data, { status: res.status });
return NextResponse.json(data);
}
// POST /api/admin/ai/providers — create provider
export async function POST(req: NextRequest) {
return adminFetch("/api/admin/ai/providers", "POST", await req.json());
try {
const body = await req.json();
const data = await createProvider(body);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Create provider error:", msg);
return NextResponse.json({ error: `创建失败: ${msg}` }, { status: 500 });
}
}
// PATCH /api/admin/ai/providers?id={id} — update provider
export async function PATCH(req: NextRequest) {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
return adminFetch(`/api/admin/ai/providers/${id}`, "PATCH", await req.json());
try {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
const body = await req.json();
const data = await updateProvider(id, body);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Update provider error:", msg);
return NextResponse.json({ error: `更新失败: ${msg}` }, { status: 500 });
}
}
// DELETE /api/admin/ai/providers?id={id} — delete provider
export async function DELETE(req: NextRequest) {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
return adminFetch(`/api/admin/ai/providers/${id}`, "DELETE");
try {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
const data = await deleteProvider(id);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Delete provider error:", msg);
return NextResponse.json({ error: `删除失败: ${msg}` }, { status: 500 });
}
}

View File

@ -1,40 +1,48 @@
import { NextRequest, NextResponse } from "next/server";
import { RUST_BACKEND_URL, ADMIN_API_SHARED_KEY } from "@/lib/env";
import { createVersion, updateVersion, deleteVersion } from "@/lib/adminrpc/client";
export const runtime = "nodejs";
async function adminFetch(path: string, method: string, body?: unknown) {
if (!ADMIN_API_SHARED_KEY) {
return NextResponse.json({ error: "ADMIN_API_SHARED_KEY 未配置" }, { status: 500 });
}
const res = await fetch(`${RUST_BACKEND_URL}${path}`, {
method,
headers: { "Content-Type": "application/json", "x-admin-api-key": ADMIN_API_SHARED_KEY },
body: body ? JSON.stringify(body) : undefined,
signal: AbortSignal.timeout(30_000),
});
const data = await res.json();
if (!res.ok) return NextResponse.json(data, { status: res.status });
return NextResponse.json(data);
}
// POST /api/admin/ai/versions — create version
export async function POST(req: NextRequest) {
return adminFetch("/api/admin/ai/versions", "POST", await req.json());
try {
const body = await req.json();
const data = await createVersion(body);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Create version error:", msg);
return NextResponse.json({ error: `创建失败: ${msg}` }, { status: 500 });
}
}
// PATCH /api/admin/ai/versions?id={id} — update version
export async function PATCH(req: NextRequest) {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
return adminFetch(`/api/admin/ai/versions/${id}`, "PATCH", await req.json());
try {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
const body = await req.json();
const data = await updateVersion(id, body);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Update version error:", msg);
return NextResponse.json({ error: `更新失败: ${msg}` }, { status: 500 });
}
}
// DELETE /api/admin/ai/versions?id={id} — delete version
export async function DELETE(req: NextRequest) {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
return adminFetch(`/api/admin/ai/versions/${id}`, "DELETE");
try {
const { searchParams } = new URL(req.url);
const id = searchParams.get("id");
if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 });
const data = await deleteVersion(id);
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Delete version error:", msg);
return NextResponse.json({ error: `删除失败: ${msg}` }, { status: 500 });
}
}

View File

@ -1,48 +1,18 @@
import { NextResponse } from "next/server";
import { RUST_BACKEND_URL, ADMIN_API_SHARED_KEY } from "@/lib/env";
import { syncModels } from "@/lib/adminrpc/client";
export const runtime = "nodejs";
/**
* Trigger AI model sync via Rust backend.
* Calls POST /api/admin/ai/sync on the Rust app.
* Trigger AI model sync via adminrpc gRPC.
*/
export async function POST() {
if (!ADMIN_API_SHARED_KEY) {
return NextResponse.json(
{ error: "ADMIN_API_SHARED_KEY 未配置" },
{ status: 500 }
);
}
try {
const url = `${RUST_BACKEND_URL}/api/admin/ai/sync`;
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-admin-api-key": ADMIN_API_SHARED_KEY,
},
// Timeout: 2 minutes for sync
signal: AbortSignal.timeout(120_000),
});
if (!res.ok) {
const body = await res.text();
return NextResponse.json(
{ error: `同步失败: ${res.status} ${body}` },
{ status: res.status }
);
}
const data = await res.json();
const data = await syncModels();
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("AI sync error:", msg);
return NextResponse.json(
{ error: `同步失败: ${msg}` },
{ status: 500 }
);
return NextResponse.json({ error: `同步失败: ${msg}` }, { status: 500 });
}
}

View File

@ -1,47 +1,18 @@
import { NextResponse } from "next/server";
import { RUST_BACKEND_URL, ADMIN_API_SHARED_KEY } from "@/lib/env";
import { checkAlerts } from "@/lib/adminrpc/client";
export const runtime = "nodejs";
/**
* Trigger workspace billing alert check via Rust backend.
* Calls POST /api/admin/alerts/check on the Rust app.
* Trigger workspace billing alert check via adminrpc gRPC.
*/
export async function POST() {
if (!ADMIN_API_SHARED_KEY) {
return NextResponse.json(
{ error: "ADMIN_API_SHARED_KEY 未配置" },
{ status: 500 }
);
}
try {
const url = `${RUST_BACKEND_URL}/api/admin/alerts/check`;
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-admin-api-key": ADMIN_API_SHARED_KEY,
},
signal: AbortSignal.timeout(60_000),
});
if (!res.ok) {
const body = await res.text();
return NextResponse.json(
{ error: `检查失败: ${res.status} ${body}` },
{ status: res.status }
);
}
const data = await res.json();
const data = await checkAlerts();
return NextResponse.json(data);
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
console.error("Alert check error:", msg);
return NextResponse.json(
{ error: `检查失败: ${msg}` },
{ status: 500 }
);
return NextResponse.json({ error: `检查失败: ${msg}` }, { status: 500 });
}
}

View File

@ -0,0 +1,270 @@
/**
* AdminRPC gRPC client for Node.js environment.
*
* Uses raw HTTP/1.1 with the tonic gRPC server.
* All methods pass body_json as a JSON string (matching the proto design).
*/
import { create, toBinary, fromBinary } from "@bufbuild/protobuf";
import type { IncomingMessage } from "node:http";
import { ADMIN_RPC_URL } from "@/lib/env";
// Import schemas from generated proto
import * as admin from "./generated/proto/admin_pb";
// ─── Low-level gRPC-Web over HTTP/1.1 ────────────────────────────────────────
function grpcRequest(
baseUrl: string,
servicePath: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
schema: any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
responseSchema: any,
body: Uint8Array,
): Promise<Uint8Array> {
return new Promise((resolve, reject) => {
const url = new URL(baseUrl);
const isHttps = url.protocol === "https:";
// eslint-disable-next-line @typescript-eslint/no-require-imports
const http = (isHttps ? require("node:https") : require("node:http")) as typeof import("node:http");
const req = http.request(
{
hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80),
path: servicePath,
method: "POST",
headers: {
"Content-Type": "application/grpc-web+proto",
"X-Grpc-Web": "1",
"TE": "trailers",
"User-Agent": "admin-module/1.0",
"Content-Length": body.byteLength,
},
},
(res: IncomingMessage) => {
const chunks: Buffer[] = [];
res.on("data", (chunk: Buffer) => chunks.push(chunk));
res.on("end", () => {
const data = Buffer.concat(chunks);
if (data.length === 0) {
const grpcStatus = res.headers["grpc-status"];
if (grpcStatus && grpcStatus !== "0") {
reject(new Error(`gRPC error ${grpcStatus}: ${res.headers["grpc-message"] || ""}`));
return;
}
resolve(new Uint8Array(0));
return;
}
// gRPC-web body format: [0x00, 4-byte big-endian length, payload...]
if (data[0] === 0x00 && data.length > 5) {
const bodyBytes = data.slice(5);
resolve(new Uint8Array(bodyBytes.buffer, bodyBytes.byteOffset, bodyBytes.byteLength));
} else {
resolve(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
}
});
},
);
req.on("error", reject);
req.write(body);
req.end();
});
}
// ─── Message encoding / decoding ──────────────────────────────────────────────
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function encode(schema: any, init: Record<string, unknown>): Uint8Array {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const msg = create(schema as any, init as any);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return toBinary(schema as any, msg as any);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function decode(schema: any, bytes: Uint8Array): any {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return fromBinary(schema as any, bytes) as any;
}
// ─── AI: Sync Models ──────────────────────────────────────────────────────────
export async function syncModels(): Promise<unknown> {
const body = encode(admin.SyncModelsRequestSchema, {});
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/SyncModels",
admin.SyncModelsRequestSchema,
admin.SyncModelsResponseSchema,
body,
);
if (bytes.length === 0) return {};
const resp = decode(admin.SyncModelsResponseSchema, bytes);
return JSON.parse(resp.bodyJson || "{}");
}
// ─── AI: Check Alerts ─────────────────────────────────────────────────────────
export async function checkAlerts(): Promise<unknown> {
const body = encode(admin.CheckAlertsRequestSchema, {});
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/CheckAlerts",
admin.CheckAlertsRequestSchema,
admin.CheckAlertsResponseSchema,
body,
);
if (bytes.length === 0) return {};
const resp = decode(admin.CheckAlertsResponseSchema, bytes);
return JSON.parse(resp.bodyJson || "{}");
}
// ─── AI: Provider CRUD ────────────────────────────────────────────────────────
export async function createProvider(body: unknown): Promise<unknown> {
const msgBody = encode(admin.CreateProviderRequestSchema, { bodyJson: JSON.stringify(body) });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/CreateProvider",
admin.CreateProviderRequestSchema,
admin.ProviderResponseSchema,
msgBody,
);
if (bytes.length === 0) return {};
const resp = decode(admin.ProviderResponseSchema, bytes);
return JSON.parse(resp.bodyJson || "{}");
}
export async function updateProvider(id: string, body: unknown): Promise<unknown> {
const msgBody = encode(admin.UpdateProviderRequestSchema, { id, bodyJson: JSON.stringify(body) });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/UpdateProvider",
admin.UpdateProviderRequestSchema,
admin.ProviderResponseSchema,
msgBody,
);
if (bytes.length === 0) return {};
const resp = decode(admin.ProviderResponseSchema, bytes);
return JSON.parse(resp.bodyJson || "{}");
}
export async function deleteProvider(id: string): Promise<{ deleted: boolean }> {
const msgBody = encode(admin.DeleteProviderRequestSchema, { id });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/DeleteProvider",
admin.DeleteProviderRequestSchema,
admin.DeleteResponseSchema,
msgBody,
);
if (bytes.length === 0) return { deleted: false };
const resp = decode(admin.DeleteResponseSchema, bytes);
return { deleted: resp.deleted };
}
// ─── AI: Model CRUD ───────────────────────────────────────────────────────────
export async function createModel(body: unknown): Promise<unknown> {
const msgBody = encode(admin.CreateModelRequestSchema, { bodyJson: JSON.stringify(body) });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/CreateModel",
admin.CreateModelRequestSchema,
admin.ModelResponseSchema,
msgBody,
);
if (bytes.length === 0) return {};
const resp = decode(admin.ModelResponseSchema, bytes);
return JSON.parse(resp.bodyJson || "{}");
}
export async function updateModel(id: string, body: unknown): Promise<unknown> {
const msgBody = encode(admin.UpdateModelRequestSchema, { id, bodyJson: JSON.stringify(body) });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/UpdateModel",
admin.UpdateModelRequestSchema,
admin.ModelResponseSchema,
msgBody,
);
if (bytes.length === 0) return {};
const resp = decode(admin.ModelResponseSchema, bytes);
return JSON.parse(resp.bodyJson || "{}");
}
export async function deleteModel(id: string): Promise<{ deleted: boolean }> {
const msgBody = encode(admin.DeleteModelRequestSchema, { id });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/DeleteModel",
admin.DeleteModelRequestSchema,
admin.DeleteResponseSchema,
msgBody,
);
if (bytes.length === 0) return { deleted: false };
const resp = decode(admin.DeleteResponseSchema, bytes);
return { deleted: resp.deleted };
}
// ─── AI: Version CRUD ─────────────────────────────────────────────────────────
export async function createVersion(body: unknown): Promise<unknown> {
const msgBody = encode(admin.CreateVersionRequestSchema, { bodyJson: JSON.stringify(body) });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/CreateVersion",
admin.CreateVersionRequestSchema,
admin.VersionResponseSchema,
msgBody,
);
if (bytes.length === 0) return {};
const resp = decode(admin.VersionResponseSchema, bytes);
return JSON.parse(resp.bodyJson || "{}");
}
export async function updateVersion(id: string, body: unknown): Promise<unknown> {
const msgBody = encode(admin.UpdateVersionRequestSchema, { id, bodyJson: JSON.stringify(body) });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/UpdateVersion",
admin.UpdateVersionRequestSchema,
admin.VersionResponseSchema,
msgBody,
);
if (bytes.length === 0) return {};
const resp = decode(admin.VersionResponseSchema, bytes);
return JSON.parse(resp.bodyJson || "{}");
}
export async function deleteVersion(id: string): Promise<{ deleted: boolean }> {
const msgBody = encode(admin.DeleteVersionRequestSchema, { id });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/DeleteVersion",
admin.DeleteVersionRequestSchema,
admin.DeleteResponseSchema,
msgBody,
);
if (bytes.length === 0) return { deleted: false };
const resp = decode(admin.DeleteResponseSchema, bytes);
return { deleted: resp.deleted };
}
// ─── AI: Pricing Update ───────────────────────────────────────────────────────
export async function updatePricing(id: string, body: unknown): Promise<unknown> {
const msgBody = encode(admin.UpdatePricingRequestSchema, { id, bodyJson: JSON.stringify(body) });
const bytes = await grpcRequest(
ADMIN_RPC_URL,
"/admin.SessionAdmin/UpdatePricing",
admin.UpdatePricingRequestSchema,
admin.PricingResponseSchema,
msgBody,
);
if (bytes.length === 0) return {};
const resp = decode(admin.PricingResponseSchema, bytes);
return JSON.parse(resp.bodyJson || "{}");
}

View File

@ -0,0 +1,225 @@
// @generated by protoc-gen-connect-es v0.13.0
// @generated from file proto/admin.proto (package admin, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { CheckAlertsRequest, CheckAlertsResponse, CreateModelRequest, CreateProviderRequest, CreateVersionRequest, DeleteModelRequest, DeleteProviderRequest, DeleteResponse, DeleteVersionRequest, ExportMetricsCsvRequest, ExportMetricsCsvResponse, GetMetricsRequest, GetMetricsResponse, GetUserInfoRequest, GetUserInfoResponse, GetUserStatusRequest, GetUserStatusResponse, GetWorkspaceOnlineUsersRequest, GetWorkspaceOnlineUsersResponse, IsUserOnlineRequest, IsUserOnlineResponse, KickUserFromWorkspaceRequest, KickUserFromWorkspaceResponse, KickUserRequest, KickUserResponse, ListUserSessionsRequest, ListUserSessionsResponse, ListWorkspaceSessionsRequest, ListWorkspaceSessionsResponse, ModelResponse, PricingResponse, ProviderResponse, SyncModelsRequest, SyncModelsResponse, UpdateModelRequest, UpdatePricingRequest, UpdateProviderRequest, UpdateVersionRequest, VersionResponse } from "./admin_pb.js";
import { MethodKind } from "@bufbuild/protobuf";
/**
* @generated from service admin.SessionAdmin
*/
export declare const SessionAdmin: {
readonly typeName: "admin.SessionAdmin",
readonly methods: {
/**
* @generated from rpc admin.SessionAdmin.ListWorkspaceSessions
*/
readonly listWorkspaceSessions: {
readonly name: "ListWorkspaceSessions",
readonly I: typeof ListWorkspaceSessionsRequest,
readonly O: typeof ListWorkspaceSessionsResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.ListUserSessions
*/
readonly listUserSessions: {
readonly name: "ListUserSessions",
readonly I: typeof ListUserSessionsRequest,
readonly O: typeof ListUserSessionsResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.KickUserFromWorkspace
*/
readonly kickUserFromWorkspace: {
readonly name: "KickUserFromWorkspace",
readonly I: typeof KickUserFromWorkspaceRequest,
readonly O: typeof KickUserFromWorkspaceResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.KickUser
*/
readonly kickUser: {
readonly name: "KickUser",
readonly I: typeof KickUserRequest,
readonly O: typeof KickUserResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.GetUserStatus
*/
readonly getUserStatus: {
readonly name: "GetUserStatus",
readonly I: typeof GetUserStatusRequest,
readonly O: typeof GetUserStatusResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.GetUserInfo
*/
readonly getUserInfo: {
readonly name: "GetUserInfo",
readonly I: typeof GetUserInfoRequest,
readonly O: typeof GetUserInfoResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.GetWorkspaceOnlineUsers
*/
readonly getWorkspaceOnlineUsers: {
readonly name: "GetWorkspaceOnlineUsers",
readonly I: typeof GetWorkspaceOnlineUsersRequest,
readonly O: typeof GetWorkspaceOnlineUsersResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.IsUserOnline
*/
readonly isUserOnline: {
readonly name: "IsUserOnline",
readonly I: typeof IsUserOnlineRequest,
readonly O: typeof IsUserOnlineResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.GetMetrics
*/
readonly getMetrics: {
readonly name: "GetMetrics",
readonly I: typeof GetMetricsRequest,
readonly O: typeof GetMetricsResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.ExportMetricsCsv
*/
readonly exportMetricsCsv: {
readonly name: "ExportMetricsCsv",
readonly I: typeof ExportMetricsCsvRequest,
readonly O: typeof ExportMetricsCsvResponse,
readonly kind: MethodKind.Unary,
},
/**
* AI
*
* @generated from rpc admin.SessionAdmin.SyncModels
*/
readonly syncModels: {
readonly name: "SyncModels",
readonly I: typeof SyncModelsRequest,
readonly O: typeof SyncModelsResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.CheckAlerts
*/
readonly checkAlerts: {
readonly name: "CheckAlerts",
readonly I: typeof CheckAlertsRequest,
readonly O: typeof CheckAlertsResponse,
readonly kind: MethodKind.Unary,
},
/**
* AI Provider
*
* @generated from rpc admin.SessionAdmin.CreateProvider
*/
readonly createProvider: {
readonly name: "CreateProvider",
readonly I: typeof CreateProviderRequest,
readonly O: typeof ProviderResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.UpdateProvider
*/
readonly updateProvider: {
readonly name: "UpdateProvider",
readonly I: typeof UpdateProviderRequest,
readonly O: typeof ProviderResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.DeleteProvider
*/
readonly deleteProvider: {
readonly name: "DeleteProvider",
readonly I: typeof DeleteProviderRequest,
readonly O: typeof DeleteResponse,
readonly kind: MethodKind.Unary,
},
/**
* AI Model
*
* @generated from rpc admin.SessionAdmin.CreateModel
*/
readonly createModel: {
readonly name: "CreateModel",
readonly I: typeof CreateModelRequest,
readonly O: typeof ModelResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.UpdateModel
*/
readonly updateModel: {
readonly name: "UpdateModel",
readonly I: typeof UpdateModelRequest,
readonly O: typeof ModelResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.DeleteModel
*/
readonly deleteModel: {
readonly name: "DeleteModel",
readonly I: typeof DeleteModelRequest,
readonly O: typeof DeleteResponse,
readonly kind: MethodKind.Unary,
},
/**
* AI Version
*
* @generated from rpc admin.SessionAdmin.CreateVersion
*/
readonly createVersion: {
readonly name: "CreateVersion",
readonly I: typeof CreateVersionRequest,
readonly O: typeof VersionResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.UpdateVersion
*/
readonly updateVersion: {
readonly name: "UpdateVersion",
readonly I: typeof UpdateVersionRequest,
readonly O: typeof VersionResponse,
readonly kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.DeleteVersion
*/
readonly deleteVersion: {
readonly name: "DeleteVersion",
readonly I: typeof DeleteVersionRequest,
readonly O: typeof DeleteResponse,
readonly kind: MethodKind.Unary,
},
/**
* AI Pricing
*
* @generated from rpc admin.SessionAdmin.UpdatePricing
*/
readonly updatePricing: {
readonly name: "UpdatePricing",
readonly I: typeof UpdatePricingRequest,
readonly O: typeof PricingResponse,
readonly kind: MethodKind.Unary,
},
}
};

View File

@ -0,0 +1,225 @@
// @generated by protoc-gen-connect-es v0.13.0
// @generated from file proto/admin.proto (package admin, syntax proto3)
/* eslint-disable */
// @ts-nocheck
import { CheckAlertsRequest, CheckAlertsResponse, CreateModelRequest, CreateProviderRequest, CreateVersionRequest, DeleteModelRequest, DeleteProviderRequest, DeleteResponse, DeleteVersionRequest, ExportMetricsCsvRequest, ExportMetricsCsvResponse, GetMetricsRequest, GetMetricsResponse, GetUserInfoRequest, GetUserInfoResponse, GetUserStatusRequest, GetUserStatusResponse, GetWorkspaceOnlineUsersRequest, GetWorkspaceOnlineUsersResponse, IsUserOnlineRequest, IsUserOnlineResponse, KickUserFromWorkspaceRequest, KickUserFromWorkspaceResponse, KickUserRequest, KickUserResponse, ListUserSessionsRequest, ListUserSessionsResponse, ListWorkspaceSessionsRequest, ListWorkspaceSessionsResponse, ModelResponse, PricingResponse, ProviderResponse, SyncModelsRequest, SyncModelsResponse, UpdateModelRequest, UpdatePricingRequest, UpdateProviderRequest, UpdateVersionRequest, VersionResponse } from "./admin_pb.js";
import { MethodKind } from "@bufbuild/protobuf";
/**
* @generated from service admin.SessionAdmin
*/
export const SessionAdmin = {
typeName: "admin.SessionAdmin",
methods: {
/**
* @generated from rpc admin.SessionAdmin.ListWorkspaceSessions
*/
listWorkspaceSessions: {
name: "ListWorkspaceSessions",
I: ListWorkspaceSessionsRequest,
O: ListWorkspaceSessionsResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.ListUserSessions
*/
listUserSessions: {
name: "ListUserSessions",
I: ListUserSessionsRequest,
O: ListUserSessionsResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.KickUserFromWorkspace
*/
kickUserFromWorkspace: {
name: "KickUserFromWorkspace",
I: KickUserFromWorkspaceRequest,
O: KickUserFromWorkspaceResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.KickUser
*/
kickUser: {
name: "KickUser",
I: KickUserRequest,
O: KickUserResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.GetUserStatus
*/
getUserStatus: {
name: "GetUserStatus",
I: GetUserStatusRequest,
O: GetUserStatusResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.GetUserInfo
*/
getUserInfo: {
name: "GetUserInfo",
I: GetUserInfoRequest,
O: GetUserInfoResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.GetWorkspaceOnlineUsers
*/
getWorkspaceOnlineUsers: {
name: "GetWorkspaceOnlineUsers",
I: GetWorkspaceOnlineUsersRequest,
O: GetWorkspaceOnlineUsersResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.IsUserOnline
*/
isUserOnline: {
name: "IsUserOnline",
I: IsUserOnlineRequest,
O: IsUserOnlineResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.GetMetrics
*/
getMetrics: {
name: "GetMetrics",
I: GetMetricsRequest,
O: GetMetricsResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.ExportMetricsCsv
*/
exportMetricsCsv: {
name: "ExportMetricsCsv",
I: ExportMetricsCsvRequest,
O: ExportMetricsCsvResponse,
kind: MethodKind.Unary,
},
/**
* AI
*
* @generated from rpc admin.SessionAdmin.SyncModels
*/
syncModels: {
name: "SyncModels",
I: SyncModelsRequest,
O: SyncModelsResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.CheckAlerts
*/
checkAlerts: {
name: "CheckAlerts",
I: CheckAlertsRequest,
O: CheckAlertsResponse,
kind: MethodKind.Unary,
},
/**
* AI Provider
*
* @generated from rpc admin.SessionAdmin.CreateProvider
*/
createProvider: {
name: "CreateProvider",
I: CreateProviderRequest,
O: ProviderResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.UpdateProvider
*/
updateProvider: {
name: "UpdateProvider",
I: UpdateProviderRequest,
O: ProviderResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.DeleteProvider
*/
deleteProvider: {
name: "DeleteProvider",
I: DeleteProviderRequest,
O: DeleteResponse,
kind: MethodKind.Unary,
},
/**
* AI Model
*
* @generated from rpc admin.SessionAdmin.CreateModel
*/
createModel: {
name: "CreateModel",
I: CreateModelRequest,
O: ModelResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.UpdateModel
*/
updateModel: {
name: "UpdateModel",
I: UpdateModelRequest,
O: ModelResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.DeleteModel
*/
deleteModel: {
name: "DeleteModel",
I: DeleteModelRequest,
O: DeleteResponse,
kind: MethodKind.Unary,
},
/**
* AI Version
*
* @generated from rpc admin.SessionAdmin.CreateVersion
*/
createVersion: {
name: "CreateVersion",
I: CreateVersionRequest,
O: VersionResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.UpdateVersion
*/
updateVersion: {
name: "UpdateVersion",
I: UpdateVersionRequest,
O: VersionResponse,
kind: MethodKind.Unary,
},
/**
* @generated from rpc admin.SessionAdmin.DeleteVersion
*/
deleteVersion: {
name: "DeleteVersion",
I: DeleteVersionRequest,
O: DeleteResponse,
kind: MethodKind.Unary,
},
/**
* AI Pricing
*
* @generated from rpc admin.SessionAdmin.UpdatePricing
*/
updatePricing: {
name: "UpdatePricing",
I: UpdatePricingRequest,
O: PricingResponse,
kind: MethodKind.Unary,
},
}
};

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long