/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 main = async () => {
const server = Bun.serve({ const server = Bun.serve({
port: Number(PORT), port: Number(PORT),
idleTimeout: 60, // 1 minute for AI chat responses
routes: { routes: {
"/": withGlobal(() => new Response(`title: tnirps\ndev-mode: ${DEV}\nport: ${PORT}`)), "/": withGlobal(() => new Response(`title: tnirps\ndev-mode: ${DEV}\nport: ${PORT}`)),
"/health": withGlobal(() => new Response("OK")), "/health": withGlobal(() => new Response("OK")),
"/ai/chat": withGlobalAuthed(withAuth(routes.aiChat)), "/ai/chat": withGlobalAuthed(withAuth(routes.aiChat)),
"/ai/models": withGlobalAuthed(withAuth(routes.aiModels)),
// routes that modify state require withCSRF middleware // routes that modify state require withCSRF middleware
"/auth/register": withGlobal(routes.authRegister), "/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 aiChat from "./ai/chat";
import aiModels from "./ai/models";
import authLogin from "./auth/login"; import authLogin from "./auth/login";
import authLogout from "./auth/logout"; import authLogout from "./auth/logout";
import authMe from "./auth/me"; import authMe from "./auth/me";
@@ -58,6 +59,7 @@ import userUploadAvatar from "./user/upload-avatar";
export const routes = { export const routes = {
aiChat, aiChat,
aiModels,
authRegister, authRegister,
authLogin, 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 { useMutation } from "@tanstack/react-query";
import { apiClient } from "@/lib/server"; 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 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, IssuesTypeCountQuerySchema,
IssueUpdateRequestSchema, IssueUpdateRequestSchema,
LoginRequestSchema, LoginRequestSchema,
ModelsResponseSchema,
OrgAddMemberRequestSchema, OrgAddMemberRequestSchema,
OrganisationMemberRecordSchema, OrganisationMemberRecordSchema,
OrganisationMemberResponseSchema, OrganisationMemberResponseSchema,
@@ -696,6 +697,15 @@ export const apiContract = c.router({
404: ApiErrorSchema, 404: ApiErrorSchema,
}, },
}, },
aiModels: {
method: "GET",
path: "/ai/models",
responses: {
200: ModelsResponseSchema,
400: ApiErrorSchema,
404: ApiErrorSchema,
},
},
}); });
export type ApiContract = typeof apiContract; export type ApiContract = typeof apiContract;

View File

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