diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 61dc158..63a37a7 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -33,11 +33,13 @@ const withGlobalAuthed = (handler: RouteHandler) => 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), diff --git a/packages/backend/src/routes/ai/models.ts b/packages/backend/src/routes/ai/models.ts new file mode 100644 index 0000000..9c1e067 --- /dev/null +++ b/packages/backend/src/routes/ai/models.ts @@ -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); +} diff --git a/packages/backend/src/routes/index.ts b/packages/backend/src/routes/index.ts index b2144b3..edebc36 100644 --- a/packages/backend/src/routes/index.ts +++ b/packages/backend/src/routes/index.ts @@ -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, diff --git a/packages/frontend/src/lib/query/hooks/chat.ts b/packages/frontend/src/lib/query/hooks/chat.ts index 78f19bc..4bb162c 100644 --- a/packages/frontend/src/lib/query/hooks/chat.ts +++ b/packages/frontend/src/lib/query/hooks/chat.ts @@ -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({ + 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; + }, + }); +} diff --git a/packages/shared/src/api-schemas.ts b/packages/shared/src/api-schemas.ts index 18b4042..6c37e6f 100644 --- a/packages/shared/src/api-schemas.ts +++ b/packages/shared/src/api-schemas.ts @@ -683,3 +683,11 @@ export const ChatResponseSchema = z.object({ }); export type ChatResponse = z.infer; + +export const ModelsResponseSchema = z.array( + z.object({ + name: z.string(), + id: z.string(), + }), +); +export type ModelsResponse = z.infer; diff --git a/packages/shared/src/contract.ts b/packages/shared/src/contract.ts index 05f3efa..9794f4c 100644 --- a/packages/shared/src/contract.ts +++ b/packages/shared/src/contract.ts @@ -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; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 78d0824..8a756dc 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -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,