/ai/models route

This commit is contained in:
2026-01-31 14:16:12 +00:00
parent 8196fb0bf6
commit 95beddaa6c
7 changed files with 45 additions and 1 deletions

View File

@@ -33,11 +33,13 @@ const withGlobalAuthed = <T extends BunRequest>(handler: RouteHandler<T>) =>
const main = async () => {
const server = Bun.serve({
port: Number(PORT),
idleTimeout: 60, // 1 minute for AI chat responses
routes: {
"/": withGlobal(() => new Response(`title: tnirps\ndev-mode: ${DEV}\nport: ${PORT}`)),
"/health": withGlobal(() => new Response("OK")),
"/ai/chat": withGlobalAuthed(withAuth(routes.aiChat)),
"/ai/models": withGlobalAuthed(withAuth(routes.aiModels)),
// routes that modify state require withCSRF middleware
"/auth/register": withGlobal(routes.authRegister),

View File

@@ -0,0 +1,8 @@
import type { AuthedRequest } from "../../auth/middleware";
import { getCachedFreeModels } from "./opencode";
// GET /ai/models - returns cached free models
export default function aiModels(_req: AuthedRequest) {
const models = getCachedFreeModels();
return Response.json(models);
}

View File

@@ -1,4 +1,5 @@
import aiChat from "./ai/chat";
import aiModels from "./ai/models";
import authLogin from "./auth/login";
import authLogout from "./auth/logout";
import authMe from "./auth/me";
@@ -58,6 +59,7 @@ import userUploadAvatar from "./user/upload-avatar";
export const routes = {
aiChat,
aiModels,
authRegister,
authLogin,

View File

@@ -1,4 +1,4 @@
import type { ChatRequest, ChatResponse } from "@sprint/shared";
import type { ChatRequest, ChatResponse, ModelsResponse } from "@sprint/shared";
import { useMutation } from "@tanstack/react-query";
import { apiClient } from "@/lib/server";
@@ -13,3 +13,15 @@ export function useChatMutation() {
},
});
}
export function useModels() {
return useMutation<ModelsResponse, Error>({
mutationKey: ["ai", "models"],
mutationFn: async () => {
const { data, error } = await apiClient.aiModels();
if (error) throw new Error(error);
if (!data) throw new Error("failed to get models");
return data as ModelsResponse;
},
});
}

View File

@@ -683,3 +683,11 @@ export const ChatResponseSchema = z.object({
});
export type ChatResponse = z.infer<typeof ChatResponseSchema>;
export const ModelsResponseSchema = z.array(
z.object({
name: z.string(),
id: z.string(),
}),
);
export type ModelsResponse = z.infer<typeof ModelsResponseSchema>;

View File

@@ -27,6 +27,7 @@ import {
IssuesTypeCountQuerySchema,
IssueUpdateRequestSchema,
LoginRequestSchema,
ModelsResponseSchema,
OrgAddMemberRequestSchema,
OrganisationMemberRecordSchema,
OrganisationMemberResponseSchema,
@@ -696,6 +697,15 @@ export const apiContract = c.router({
404: ApiErrorSchema,
},
},
aiModels: {
method: "GET",
path: "/ai/models",
responses: {
200: ModelsResponseSchema,
400: ApiErrorSchema,
404: ApiErrorSchema,
},
},
});
export type ApiContract = typeof apiContract;

View File

@@ -23,6 +23,7 @@ export type {
IssuesTypeCountQuery,
IssueUpdateRequest,
LoginRequest,
ModelsResponse,
OrgAddMemberRequest,
OrganisationMemberRecordType,
OrganisationMemberResponse,
@@ -95,6 +96,7 @@ export {
IssuesTypeCountQuerySchema,
IssueUpdateRequestSchema,
LoginRequestSchema,
ModelsResponseSchema,
OrgAddMemberRequestSchema,
OrganisationMemberRecordSchema,
OrganisationMemberResponseSchema,