Initial MVP: multiplexer + chat DB
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
dev.db
|
||||||
|
*.db
|
||||||
|
prisma/migrations
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
# llm-backend
|
||||||
|
|
||||||
|
Backend API for:
|
||||||
|
- LLM multiplexer (OpenAI / Anthropic / xAI (Grok))
|
||||||
|
- Personal chat database (chats/messages + LLM call log)
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
- Node.js + TypeScript
|
||||||
|
- Fastify (HTTP)
|
||||||
|
- Prisma + SQLite (dev)
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd llm-backend
|
||||||
|
cp .env.example .env
|
||||||
|
npm run db:migrate
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open docs: `http://localhost:8787/docs`
|
||||||
|
|
||||||
|
## Auth
|
||||||
|
|
||||||
|
Set `ADMIN_TOKEN` and send:
|
||||||
|
|
||||||
|
`Authorization: Bearer <ADMIN_TOKEN>`
|
||||||
|
|
||||||
|
If `ADMIN_TOKEN` is not set, the server runs in open mode (dev).
|
||||||
|
|
||||||
|
## Env
|
||||||
|
- `OPENAI_API_KEY`
|
||||||
|
- `ANTHROPIC_API_KEY`
|
||||||
|
- `XAI_API_KEY`
|
||||||
|
|
||||||
|
## API
|
||||||
|
- `GET /health`
|
||||||
|
- `GET /v1/chats`
|
||||||
|
- `POST /v1/chats`
|
||||||
|
- `GET /v1/chats/:chatId`
|
||||||
|
- `POST /v1/chats/:chatId/messages`
|
||||||
|
- `POST /v1/chat-completions`
|
||||||
|
|
||||||
|
`POST /v1/chat-completions` body example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"chatId": "<optional chat id>",
|
||||||
|
"provider": "openai",
|
||||||
|
"model": "gpt-4.1-mini",
|
||||||
|
"messages": [
|
||||||
|
{"role":"system","content":"You are helpful."},
|
||||||
|
{"role":"user","content":"Say hi"}
|
||||||
|
],
|
||||||
|
"temperature": 0.2,
|
||||||
|
"maxTokens": 256
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next steps (planned)
|
||||||
|
- SSE streaming (`/v1/chat-completions:stream`)
|
||||||
|
- Tool/function calling normalization
|
||||||
|
- User accounts + per-device API keys
|
||||||
|
- Postgres support + migrations for prod
|
||||||
|
- Attachments + embeddings + semantic search
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
||||||
|
* Use it to get access to models, enums, and input types.
|
||||||
|
*
|
||||||
|
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
|
||||||
|
* See `client.ts` for the standard, server-side entry point.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Prisma from './internal/prismaNamespaceBrowser.js'
|
||||||
|
export { Prisma }
|
||||||
|
export * as $Enums from './enums.js'
|
||||||
|
export * from './enums.js';
|
||||||
|
/**
|
||||||
|
* Model User
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model Chat
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Chat = Prisma.ChatModel
|
||||||
|
/**
|
||||||
|
* Model Message
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Message = Prisma.MessageModel
|
||||||
|
/**
|
||||||
|
* Model LlmCall
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type LlmCall = Prisma.LlmCallModel
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
|
||||||
|
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as process from 'node:process'
|
||||||
|
import * as path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import * as $Enums from "./enums.js"
|
||||||
|
import * as $Class from "./internal/class.js"
|
||||||
|
import * as Prisma from "./internal/prismaNamespace.js"
|
||||||
|
|
||||||
|
export * as $Enums from './enums.js'
|
||||||
|
export * from "./enums.js"
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
export const PrismaClient = $Class.getPrismaClientClass()
|
||||||
|
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||||
|
export { Prisma }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model User
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model Chat
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Chat = Prisma.ChatModel
|
||||||
|
/**
|
||||||
|
* Model Message
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Message = Prisma.MessageModel
|
||||||
|
/**
|
||||||
|
* Model LlmCall
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type LlmCall = Prisma.LlmCallModel
|
||||||
@@ -0,0 +1,460 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import * as $Enums from "./enums.js"
|
||||||
|
import type * as Prisma from "./internal/prismaNamespace.js"
|
||||||
|
|
||||||
|
|
||||||
|
export type StringFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SortOrderInput = {
|
||||||
|
sort: Prisma.SortOrder
|
||||||
|
nulls?: Prisma.NullsOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnumMessageRoleFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.MessageRole | Prisma.EnumMessageRoleFieldRefInput<$PrismaModel>
|
||||||
|
in?: $Enums.MessageRole[]
|
||||||
|
notIn?: $Enums.MessageRole[]
|
||||||
|
not?: Prisma.NestedEnumMessageRoleFilter<$PrismaModel> | $Enums.MessageRole
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JsonNullableFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<JsonNullableFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonNullableFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<JsonNullableFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<JsonNullableFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type JsonNullableFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnumMessageRoleWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.MessageRole | Prisma.EnumMessageRoleFieldRefInput<$PrismaModel>
|
||||||
|
in?: $Enums.MessageRole[]
|
||||||
|
notIn?: $Enums.MessageRole[]
|
||||||
|
not?: Prisma.NestedEnumMessageRoleWithAggregatesFilter<$PrismaModel> | $Enums.MessageRole
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedEnumMessageRoleFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedEnumMessageRoleFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JsonNullableWithAggregatesFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<JsonNullableWithAggregatesFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type JsonNullableWithAggregatesFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedJsonNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedJsonNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnumProviderFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.Provider | Prisma.EnumProviderFieldRefInput<$PrismaModel>
|
||||||
|
in?: $Enums.Provider[]
|
||||||
|
notIn?: $Enums.Provider[]
|
||||||
|
not?: Prisma.NestedEnumProviderFilter<$PrismaModel> | $Enums.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JsonFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<JsonFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<JsonFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<JsonFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type JsonFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EnumProviderWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.Provider | Prisma.EnumProviderFieldRefInput<$PrismaModel>
|
||||||
|
in?: $Enums.Provider[]
|
||||||
|
notIn?: $Enums.Provider[]
|
||||||
|
not?: Prisma.NestedEnumProviderWithAggregatesFilter<$PrismaModel> | $Enums.Provider
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedEnumProviderFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedEnumProviderFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JsonWithAggregatesFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<JsonWithAggregatesFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonWithAggregatesFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<JsonWithAggregatesFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<JsonWithAggregatesFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type JsonWithAggregatesFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedJsonFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedJsonFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[]
|
||||||
|
notIn?: string[]
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[]
|
||||||
|
notIn?: number[]
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[]
|
||||||
|
notIn?: Date[] | string[]
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | null
|
||||||
|
notIn?: string[] | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedEnumMessageRoleFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.MessageRole | Prisma.EnumMessageRoleFieldRefInput<$PrismaModel>
|
||||||
|
in?: $Enums.MessageRole[]
|
||||||
|
notIn?: $Enums.MessageRole[]
|
||||||
|
not?: Prisma.NestedEnumMessageRoleFilter<$PrismaModel> | $Enums.MessageRole
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedEnumMessageRoleWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.MessageRole | Prisma.EnumMessageRoleFieldRefInput<$PrismaModel>
|
||||||
|
in?: $Enums.MessageRole[]
|
||||||
|
notIn?: $Enums.MessageRole[]
|
||||||
|
not?: Prisma.NestedEnumMessageRoleWithAggregatesFilter<$PrismaModel> | $Enums.MessageRole
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedEnumMessageRoleFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedEnumMessageRoleFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedJsonNullableFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<NestedJsonNullableFilterBase<$PrismaModel>>, Exclude<keyof Required<NestedJsonNullableFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<NestedJsonNullableFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<NestedJsonNullableFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type NestedJsonNullableFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedEnumProviderFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.Provider | Prisma.EnumProviderFieldRefInput<$PrismaModel>
|
||||||
|
in?: $Enums.Provider[]
|
||||||
|
notIn?: $Enums.Provider[]
|
||||||
|
not?: Prisma.NestedEnumProviderFilter<$PrismaModel> | $Enums.Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedEnumProviderWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: $Enums.Provider | Prisma.EnumProviderFieldRefInput<$PrismaModel>
|
||||||
|
in?: $Enums.Provider[]
|
||||||
|
notIn?: $Enums.Provider[]
|
||||||
|
not?: Prisma.NestedEnumProviderWithAggregatesFilter<$PrismaModel> | $Enums.Provider
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedEnumProviderFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedEnumProviderFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedJsonFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<NestedJsonFilterBase<$PrismaModel>>, Exclude<keyof Required<NestedJsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<NestedJsonFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<NestedJsonFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type NestedJsonFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedFloatNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | null
|
||||||
|
notIn?: number[] | null
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file exports all enum related types from the schema.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const Provider = {
|
||||||
|
openai: 'openai',
|
||||||
|
anthropic: 'anthropic',
|
||||||
|
xai: 'xai'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type Provider = (typeof Provider)[keyof typeof Provider]
|
||||||
|
|
||||||
|
|
||||||
|
export const MessageRole = {
|
||||||
|
system: 'system',
|
||||||
|
user: 'user',
|
||||||
|
assistant: 'assistant',
|
||||||
|
tool: 'tool'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type MessageRole = (typeof MessageRole)[keyof typeof MessageRole]
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* WARNING: This is an internal file that is subject to change!
|
||||||
|
*
|
||||||
|
* 🛑 Under no circumstances should you import this file directly! 🛑
|
||||||
|
*
|
||||||
|
* Please import the `PrismaClient` class from the `client.ts` file instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import type * as Prisma from "./prismaNamespace.js"
|
||||||
|
|
||||||
|
|
||||||
|
const config: runtime.GetPrismaClientConfig = {
|
||||||
|
"previewFeatures": [],
|
||||||
|
"clientVersion": "7.3.0",
|
||||||
|
"engineVersion": "9d6ad21cbbceab97458517b147a6a09ff43aa735",
|
||||||
|
"activeProvider": "sqlite",
|
||||||
|
"inlineSchema": "// Prisma schema for the personal chat DB + LLM call log\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\nenum Provider {\n openai\n anthropic\n xai\n}\n\nenum MessageRole {\n system\n user\n assistant\n tool\n}\n\nmodel User {\n id String @id @default(cuid())\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // minimal for now (single-user is fine). Keep extensible.\n handle String? @unique\n\n chats Chat[]\n}\n\nmodel Chat {\n id String @id @default(cuid())\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n title String?\n\n user User? @relation(fields: [userId], references: [id])\n userId String?\n\n messages Message[]\n calls LlmCall[]\n\n @@index([userId])\n}\n\nmodel Message {\n id String @id @default(cuid())\n createdAt DateTime @default(now())\n\n chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)\n chatId String\n\n role MessageRole\n content String\n\n // for tool messages or attachments later\n name String?\n metadata Json?\n\n @@index([chatId, createdAt])\n}\n\nmodel LlmCall {\n id String @id @default(cuid())\n createdAt DateTime @default(now())\n\n chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)\n chatId String\n\n provider Provider\n model String\n\n // request/response snapshots for debugging + replay\n request Json\n response Json?\n\n // usage/cost basics\n inputTokens Int?\n outputTokens Int?\n totalTokens Int?\n\n latencyMs Int?\n\n error String?\n\n @@index([chatId, createdAt])\n @@index([provider, model, createdAt])\n}\n",
|
||||||
|
"runtimeDataModel": {
|
||||||
|
"models": {},
|
||||||
|
"enums": {},
|
||||||
|
"types": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"handle\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"chats\",\"kind\":\"object\",\"type\":\"Chat\",\"relationName\":\"ChatToUser\"}],\"dbName\":null},\"Chat\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ChatToUser\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"messages\",\"kind\":\"object\",\"type\":\"Message\",\"relationName\":\"ChatToMessage\"},{\"name\":\"calls\",\"kind\":\"object\",\"type\":\"LlmCall\",\"relationName\":\"ChatToLlmCall\"}],\"dbName\":null},\"Message\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"chat\",\"kind\":\"object\",\"type\":\"Chat\",\"relationName\":\"ChatToMessage\"},{\"name\":\"chatId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"MessageRole\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"metadata\",\"kind\":\"scalar\",\"type\":\"Json\"}],\"dbName\":null},\"LlmCall\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"chat\",\"kind\":\"object\",\"type\":\"Chat\",\"relationName\":\"ChatToLlmCall\"},{\"name\":\"chatId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"provider\",\"kind\":\"enum\",\"type\":\"Provider\"},{\"name\":\"model\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"request\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"response\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"inputTokens\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"outputTokens\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"totalTokens\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"latencyMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"error\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||||
|
|
||||||
|
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
|
||||||
|
const { Buffer } = await import('node:buffer')
|
||||||
|
const wasmArray = Buffer.from(wasmBase64, 'base64')
|
||||||
|
return new WebAssembly.Module(wasmArray)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.compilerWasm = {
|
||||||
|
getRuntime: async () => await import("@prisma/client/runtime/query_compiler_fast_bg.sqlite.mjs"),
|
||||||
|
|
||||||
|
getQueryCompilerWasmModule: async () => {
|
||||||
|
const { wasm } = await import("@prisma/client/runtime/query_compiler_fast_bg.sqlite.wasm-base64.mjs")
|
||||||
|
return await decodeBase64AsWasm(wasm)
|
||||||
|
},
|
||||||
|
|
||||||
|
importName: "./query_compiler_fast_bg.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export type LogOptions<ClientOptions extends Prisma.PrismaClientOptions> =
|
||||||
|
'log' extends keyof ClientOptions ? ClientOptions['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition> ? Prisma.GetEvents<ClientOptions['log']> : never : never
|
||||||
|
|
||||||
|
export interface PrismaClientConstructor {
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
|
||||||
|
new <
|
||||||
|
Options extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions,
|
||||||
|
LogOpts extends LogOptions<Options> = LogOptions<Options>,
|
||||||
|
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends { omit: infer U } ? U : Prisma.PrismaClientOptions['omit'],
|
||||||
|
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
|
||||||
|
>(options: Prisma.Subset<Options, Prisma.PrismaClientOptions> ): PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface PrismaClient<
|
||||||
|
in LogOpts extends Prisma.LogLevel = never,
|
||||||
|
in out OmitOpts extends Prisma.PrismaClientOptions['omit'] = undefined,
|
||||||
|
in out ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
|
||||||
|
> {
|
||||||
|
[K: symbol]: { types: Prisma.TypeMap<ExtArgs>['other'] }
|
||||||
|
|
||||||
|
$on<V extends LogOpts>(eventType: V, callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void): PrismaClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect with the database
|
||||||
|
*/
|
||||||
|
$connect(): runtime.Types.Utils.JsPromise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the database
|
||||||
|
*/
|
||||||
|
$disconnect(): runtime.Types.Utils.JsPromise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a prepared raw query and returns the number of affected rows.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$executeRaw`UPDATE User SET cool = ${true} WHERE email = ${'user@email.com'};`
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$executeRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a raw query and returns the number of affected rows.
|
||||||
|
* Susceptible to SQL injections, see documentation.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$executeRawUnsafe('UPDATE User SET cool = $1 WHERE email = $2 ;', true, 'user@email.com')
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$executeRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a prepared raw query and returns the `SELECT` data.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${1} OR email = ${'user@email.com'};`
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$queryRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a raw query and returns the `SELECT` data.
|
||||||
|
* Susceptible to SQL injections, see documentation.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const result = await prisma.$queryRawUnsafe('SELECT * FROM User WHERE id = $1 OR email = $2;', 1, 'user@email.com')
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/raw-queries).
|
||||||
|
*/
|
||||||
|
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<T>;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const [george, bob, alice] = await prisma.$transaction([
|
||||||
|
* prisma.user.create({ data: { name: 'George' } }),
|
||||||
|
* prisma.user.create({ data: { name: 'Bob' } }),
|
||||||
|
* prisma.user.create({ data: { name: 'Alice' } }),
|
||||||
|
* ])
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client/transactions).
|
||||||
|
*/
|
||||||
|
$transaction<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: { isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>
|
||||||
|
|
||||||
|
$transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<R>
|
||||||
|
|
||||||
|
$extends: runtime.Types.Extensions.ExtendsHook<"extends", Prisma.TypeMapCb<OmitOpts>, ExtArgs, runtime.Types.Utils.Call<Prisma.TypeMapCb<OmitOpts>, {
|
||||||
|
extArgs: ExtArgs
|
||||||
|
}>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.user`: Exposes CRUD operations for the **User** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more Users
|
||||||
|
* const users = await prisma.user.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get user(): Prisma.UserDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.chat`: Exposes CRUD operations for the **Chat** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more Chats
|
||||||
|
* const chats = await prisma.chat.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get chat(): Prisma.ChatDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.message`: Exposes CRUD operations for the **Message** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more Messages
|
||||||
|
* const messages = await prisma.message.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get message(): Prisma.MessageDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `prisma.llmCall`: Exposes CRUD operations for the **LlmCall** model.
|
||||||
|
* Example usage:
|
||||||
|
* ```ts
|
||||||
|
* // Fetch zero or more LlmCalls
|
||||||
|
* const llmCalls = await prisma.llmCall.findMany()
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
get llmCall(): Prisma.LlmCallDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPrismaClientClass(): PrismaClientConstructor {
|
||||||
|
return runtime.getPrismaClient(config) as unknown as PrismaClientConstructor
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,171 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* WARNING: This is an internal file that is subject to change!
|
||||||
|
*
|
||||||
|
* 🛑 Under no circumstances should you import this file directly! 🛑
|
||||||
|
*
|
||||||
|
* All exports from this file are wrapped under a `Prisma` namespace object in the browser.ts file.
|
||||||
|
* While this enables partial backward compatibility, it is not part of the stable public API.
|
||||||
|
*
|
||||||
|
* If you are looking for your Models, Enums, and Input Types, please import them from the respective
|
||||||
|
* model files in the `model` directory!
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/index-browser"
|
||||||
|
|
||||||
|
export type * from '../models.js'
|
||||||
|
export type * from './prismaNamespace.js'
|
||||||
|
|
||||||
|
export const Decimal = runtime.Decimal
|
||||||
|
|
||||||
|
|
||||||
|
export const NullTypes = {
|
||||||
|
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
|
||||||
|
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
|
||||||
|
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const DbNull = runtime.DbNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const JsonNull = runtime.JsonNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const AnyNull = runtime.AnyNull
|
||||||
|
|
||||||
|
|
||||||
|
export const ModelName = {
|
||||||
|
User: 'User',
|
||||||
|
Chat: 'Chat',
|
||||||
|
Message: 'Message',
|
||||||
|
LlmCall: 'LlmCall'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const TransactionIsolationLevel = runtime.makeStrictEnum({
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
|
||||||
|
|
||||||
|
|
||||||
|
export const UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt',
|
||||||
|
handle: 'handle'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const ChatScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt',
|
||||||
|
title: 'title',
|
||||||
|
userId: 'userId'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ChatScalarFieldEnum = (typeof ChatScalarFieldEnum)[keyof typeof ChatScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const MessageScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
chatId: 'chatId',
|
||||||
|
role: 'role',
|
||||||
|
content: 'content',
|
||||||
|
name: 'name',
|
||||||
|
metadata: 'metadata'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type MessageScalarFieldEnum = (typeof MessageScalarFieldEnum)[keyof typeof MessageScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const LlmCallScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
chatId: 'chatId',
|
||||||
|
provider: 'provider',
|
||||||
|
model: 'model',
|
||||||
|
request: 'request',
|
||||||
|
response: 'response',
|
||||||
|
inputTokens: 'inputTokens',
|
||||||
|
outputTokens: 'outputTokens',
|
||||||
|
totalTokens: 'totalTokens',
|
||||||
|
latencyMs: 'latencyMs',
|
||||||
|
error: 'error'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type LlmCallScalarFieldEnum = (typeof LlmCallScalarFieldEnum)[keyof typeof LlmCallScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
|
||||||
|
|
||||||
|
|
||||||
|
export const NullableJsonNullValueInput = {
|
||||||
|
DbNull: DbNull,
|
||||||
|
JsonNull: JsonNull
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput]
|
||||||
|
|
||||||
|
|
||||||
|
export const JsonNullValueInput = {
|
||||||
|
JsonNull: JsonNull
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]
|
||||||
|
|
||||||
|
|
||||||
|
export const NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
|
||||||
|
|
||||||
|
|
||||||
|
export const JsonNullValueFilter = {
|
||||||
|
DbNull: DbNull,
|
||||||
|
JsonNull: JsonNull,
|
||||||
|
AnyNull: AnyNull
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter]
|
||||||
|
|
||||||
|
|
||||||
|
export const QueryMode = {
|
||||||
|
default: 'default',
|
||||||
|
insensitive: 'insensitive'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode]
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This is a barrel export file for all models and their related types.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
export type * from './models/User.js'
|
||||||
|
export type * from './models/Chat.js'
|
||||||
|
export type * from './models/Message.js'
|
||||||
|
export type * from './models/LlmCall.js'
|
||||||
|
export type * from './commonInputTypes.js'
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Generated
+2327
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "llm-backend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"description": "LLM multiplexer + personal chat database backend",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx watch src/index.ts",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"build": "tsc -p tsconfig.json",
|
||||||
|
"prisma:generate": "prisma generate",
|
||||||
|
"db:migrate": "prisma migrate dev",
|
||||||
|
"db:studio": "prisma studio"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@anthropic-ai/sdk": "^0.71.2",
|
||||||
|
"@fastify/cors": "^11.2.0",
|
||||||
|
"@fastify/sensible": "^6.0.4",
|
||||||
|
"@fastify/swagger": "^9.6.1",
|
||||||
|
"@fastify/swagger-ui": "^5.2.5",
|
||||||
|
"@prisma/client": "^6.16.1",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"fastify": "^5.7.2",
|
||||||
|
"openai": "^6.16.0",
|
||||||
|
"pino-pretty": "^13.1.3",
|
||||||
|
"zod": "^4.3.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^25.0.10",
|
||||||
|
"prisma": "^6.16.1",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// This file was generated by Prisma, and assumes you have installed the following:
|
||||||
|
// npm install --save-dev prisma dotenv
|
||||||
|
import "dotenv/config";
|
||||||
|
import { defineConfig } from "prisma/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "prisma/schema.prisma",
|
||||||
|
migrations: {
|
||||||
|
path: "prisma/migrations",
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: process.env["DATABASE_URL"],
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
// Prisma schema for the personal chat DB + LLM call log
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "sqlite"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Provider {
|
||||||
|
openai
|
||||||
|
anthropic
|
||||||
|
xai
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MessageRole {
|
||||||
|
system
|
||||||
|
user
|
||||||
|
assistant
|
||||||
|
tool
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
// minimal for now (single-user is fine). Keep extensible.
|
||||||
|
handle String? @unique
|
||||||
|
|
||||||
|
chats Chat[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Chat {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
title String?
|
||||||
|
|
||||||
|
user User? @relation(fields: [userId], references: [id])
|
||||||
|
userId String?
|
||||||
|
|
||||||
|
messages Message[]
|
||||||
|
calls LlmCall[]
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Message {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)
|
||||||
|
chatId String
|
||||||
|
|
||||||
|
role MessageRole
|
||||||
|
content String
|
||||||
|
|
||||||
|
// for tool messages or attachments later
|
||||||
|
name String?
|
||||||
|
metadata Json?
|
||||||
|
|
||||||
|
@@index([chatId, createdAt])
|
||||||
|
}
|
||||||
|
|
||||||
|
model LlmCall {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)
|
||||||
|
chatId String
|
||||||
|
|
||||||
|
provider Provider
|
||||||
|
model String
|
||||||
|
|
||||||
|
// request/response snapshots for debugging + replay
|
||||||
|
request Json
|
||||||
|
response Json?
|
||||||
|
|
||||||
|
// usage/cost basics
|
||||||
|
inputTokens Int?
|
||||||
|
outputTokens Int?
|
||||||
|
totalTokens Int?
|
||||||
|
|
||||||
|
latencyMs Int?
|
||||||
|
|
||||||
|
error String?
|
||||||
|
|
||||||
|
@@index([chatId, createdAt])
|
||||||
|
@@index([provider, model, createdAt])
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
import type { FastifyRequest } from "fastify";
|
||||||
|
import { env } from "./env.js";
|
||||||
|
|
||||||
|
export function requireAdmin(req: FastifyRequest) {
|
||||||
|
// If ADMIN_TOKEN isn't set, run in "open" mode (dev).
|
||||||
|
if (!env.ADMIN_TOKEN) return;
|
||||||
|
|
||||||
|
const auth = req.headers.authorization;
|
||||||
|
if (!auth?.startsWith("Bearer ")) {
|
||||||
|
const err = new Error("missing bearer token");
|
||||||
|
// @ts-expect-error attach status
|
||||||
|
err.statusCode = 401;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
const token = auth.slice("Bearer ".length);
|
||||||
|
if (token !== env.ADMIN_TOKEN) {
|
||||||
|
const err = new Error("invalid bearer token");
|
||||||
|
// @ts-expect-error attach status
|
||||||
|
err.statusCode = 403;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
|
export const prisma = new PrismaClient({
|
||||||
|
log: [
|
||||||
|
{ emit: "event", level: "error" },
|
||||||
|
{ emit: "event", level: "warn" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
prisma.$on("error", (e: any) => {
|
||||||
|
console.error("[prisma:error]", e);
|
||||||
|
});
|
||||||
|
|
||||||
|
prisma.$on("warn", (e: any) => {
|
||||||
|
console.warn("[prisma:warn]", e);
|
||||||
|
});
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import "dotenv/config";
|
||||||
|
|
||||||
|
const EnvSchema = z.object({
|
||||||
|
PORT: z.coerce.number().int().positive().default(8787),
|
||||||
|
HOST: z.string().default("0.0.0.0"),
|
||||||
|
|
||||||
|
// simple bearer-token auth for your personal backend
|
||||||
|
ADMIN_TOKEN: z.string().min(20).optional(),
|
||||||
|
|
||||||
|
// provider keys
|
||||||
|
OPENAI_API_KEY: z.string().optional(),
|
||||||
|
ANTHROPIC_API_KEY: z.string().optional(),
|
||||||
|
XAI_API_KEY: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Env = z.infer<typeof EnvSchema>;
|
||||||
|
|
||||||
|
export const env: Env = EnvSchema.parse(process.env);
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import Fastify from "fastify";
|
||||||
|
import cors from "@fastify/cors";
|
||||||
|
import swagger from "@fastify/swagger";
|
||||||
|
import swaggerUI from "@fastify/swagger-ui";
|
||||||
|
import sensible from "@fastify/sensible";
|
||||||
|
import { env } from "./env.js";
|
||||||
|
import { registerRoutes } from "./routes.js";
|
||||||
|
|
||||||
|
const app = Fastify({
|
||||||
|
logger: {
|
||||||
|
transport: {
|
||||||
|
target: "pino-pretty",
|
||||||
|
options: { colorize: true, translateTime: "SYS:standard" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.register(cors, { origin: true, credentials: true });
|
||||||
|
|
||||||
|
await app.register(swagger, {
|
||||||
|
openapi: {
|
||||||
|
info: {
|
||||||
|
title: "LLM Multiplexer Backend",
|
||||||
|
version: "0.1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.register(swaggerUI, { routePrefix: "/docs" });
|
||||||
|
await app.register(sensible);
|
||||||
|
|
||||||
|
app.setErrorHandler((err, _req, reply) => {
|
||||||
|
const e = err as any;
|
||||||
|
const statusCode = e.statusCode ?? 500;
|
||||||
|
reply.status(statusCode).send({
|
||||||
|
error: true,
|
||||||
|
message: e.message ?? String(e),
|
||||||
|
statusCode,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await registerRoutes(app);
|
||||||
|
|
||||||
|
await app.listen({ port: env.PORT, host: env.HOST });
|
||||||
|
app.log.info(`listening on http://${env.HOST}:${env.PORT}`);
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { performance } from "node:perf_hooks";
|
||||||
|
import { prisma } from "../db.js";
|
||||||
|
import { anthropicClient, openaiClient, xaiClient } from "./providers.js";
|
||||||
|
import type { MultiplexRequest, MultiplexResponse, Provider } from "./types.js";
|
||||||
|
|
||||||
|
function asProviderEnum(p: Provider) {
|
||||||
|
// Prisma enum values match these strings.
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runMultiplex(req: MultiplexRequest): Promise<MultiplexResponse> {
|
||||||
|
const t0 = performance.now();
|
||||||
|
|
||||||
|
// Persist call record early so we can attach errors.
|
||||||
|
const call = await prisma.llmCall.create({
|
||||||
|
data: {
|
||||||
|
chatId: req.chatId ?? (await prisma.chat.create({ data: {} })).id,
|
||||||
|
provider: asProviderEnum(req.provider) as any,
|
||||||
|
model: req.model,
|
||||||
|
request: req as any,
|
||||||
|
},
|
||||||
|
select: { id: true, chatId: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
let outText = "";
|
||||||
|
let usage: MultiplexResponse["usage"] | undefined;
|
||||||
|
let raw: unknown;
|
||||||
|
|
||||||
|
if (req.provider === "openai" || req.provider === "xai") {
|
||||||
|
const client = req.provider === "openai" ? openaiClient() : xaiClient();
|
||||||
|
const r = await client.chat.completions.create({
|
||||||
|
model: req.model,
|
||||||
|
// OpenAI SDK has very specific message union types; our normalized schema is compatible.
|
||||||
|
messages: req.messages.map((m) => ({ role: m.role, content: m.content, name: m.name })) as any,
|
||||||
|
temperature: req.temperature,
|
||||||
|
max_tokens: req.maxTokens,
|
||||||
|
});
|
||||||
|
raw = r;
|
||||||
|
outText = r.choices?.[0]?.message?.content ?? "";
|
||||||
|
usage = r.usage
|
||||||
|
? {
|
||||||
|
inputTokens: r.usage.prompt_tokens,
|
||||||
|
outputTokens: r.usage.completion_tokens,
|
||||||
|
totalTokens: r.usage.total_tokens,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
} else if (req.provider === "anthropic") {
|
||||||
|
const client = anthropicClient();
|
||||||
|
|
||||||
|
// Anthropic splits system prompt. We'll convert first system message into system string.
|
||||||
|
const system = req.messages.find((m) => m.role === "system")?.content;
|
||||||
|
const msgs = req.messages
|
||||||
|
.filter((m) => m.role !== "system")
|
||||||
|
.map((m) => ({ role: m.role === "assistant" ? "assistant" : "user", content: m.content }));
|
||||||
|
|
||||||
|
const r = await client.messages.create({
|
||||||
|
model: req.model,
|
||||||
|
system,
|
||||||
|
max_tokens: req.maxTokens ?? 1024,
|
||||||
|
temperature: req.temperature,
|
||||||
|
messages: msgs as any,
|
||||||
|
});
|
||||||
|
raw = r;
|
||||||
|
outText = r.content
|
||||||
|
.map((c: any) => (c.type === "text" ? c.text : ""))
|
||||||
|
.join("")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
// Anthropic usage (SDK typing varies by version)
|
||||||
|
const ru: any = (r as any).usage;
|
||||||
|
if (ru) {
|
||||||
|
usage = {
|
||||||
|
inputTokens: ru.input_tokens,
|
||||||
|
outputTokens: ru.output_tokens,
|
||||||
|
totalTokens: (ru.input_tokens ?? 0) + (ru.output_tokens ?? 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`unknown provider: ${req.provider}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const latencyMs = Math.round(performance.now() - t0);
|
||||||
|
|
||||||
|
// Store assistant message + call record
|
||||||
|
await prisma.$transaction([
|
||||||
|
prisma.message.create({
|
||||||
|
data: {
|
||||||
|
chatId: call.chatId,
|
||||||
|
role: "assistant" as any,
|
||||||
|
content: outText,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.llmCall.update({
|
||||||
|
where: { id: call.id },
|
||||||
|
data: {
|
||||||
|
response: raw as any,
|
||||||
|
latencyMs,
|
||||||
|
inputTokens: usage?.inputTokens,
|
||||||
|
outputTokens: usage?.outputTokens,
|
||||||
|
totalTokens: usage?.totalTokens,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
provider: req.provider,
|
||||||
|
model: req.model,
|
||||||
|
message: { role: "assistant", content: outText },
|
||||||
|
usage,
|
||||||
|
raw,
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
const latencyMs = Math.round(performance.now() - t0);
|
||||||
|
await prisma.llmCall.update({
|
||||||
|
where: { id: call.id },
|
||||||
|
data: {
|
||||||
|
error: e?.message ?? String(e),
|
||||||
|
latencyMs,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import OpenAI from "openai";
|
||||||
|
import Anthropic from "@anthropic-ai/sdk";
|
||||||
|
import { env } from "../env.js";
|
||||||
|
|
||||||
|
export function openaiClient() {
|
||||||
|
if (!env.OPENAI_API_KEY) throw new Error("OPENAI_API_KEY not set");
|
||||||
|
return new OpenAI({ apiKey: env.OPENAI_API_KEY });
|
||||||
|
}
|
||||||
|
|
||||||
|
// xAI (Grok) is OpenAI-compatible at https://api.x.ai/v1
|
||||||
|
export function xaiClient() {
|
||||||
|
if (!env.XAI_API_KEY) throw new Error("XAI_API_KEY not set");
|
||||||
|
return new OpenAI({ apiKey: env.XAI_API_KEY, baseURL: "https://api.x.ai/v1" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function anthropicClient() {
|
||||||
|
if (!env.ANTHROPIC_API_KEY) throw new Error("ANTHROPIC_API_KEY not set");
|
||||||
|
return new Anthropic({ apiKey: env.ANTHROPIC_API_KEY });
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
export type Provider = "openai" | "anthropic" | "xai";
|
||||||
|
|
||||||
|
export type ChatMessage = {
|
||||||
|
role: "system" | "user" | "assistant" | "tool";
|
||||||
|
content: string;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MultiplexRequest = {
|
||||||
|
chatId?: string;
|
||||||
|
provider: Provider;
|
||||||
|
model: string;
|
||||||
|
messages: ChatMessage[];
|
||||||
|
temperature?: number;
|
||||||
|
maxTokens?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MultiplexResponse = {
|
||||||
|
provider: Provider;
|
||||||
|
model: string;
|
||||||
|
message: { role: "assistant"; content: string };
|
||||||
|
usage?: {
|
||||||
|
inputTokens?: number;
|
||||||
|
outputTokens?: number;
|
||||||
|
totalTokens?: number;
|
||||||
|
};
|
||||||
|
raw: unknown;
|
||||||
|
};
|
||||||
+111
@@ -0,0 +1,111 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import type { FastifyInstance } from "fastify";
|
||||||
|
import { prisma } from "./db.js";
|
||||||
|
import { requireAdmin } from "./auth.js";
|
||||||
|
import { runMultiplex } from "./llm/multiplexer.js";
|
||||||
|
|
||||||
|
export async function registerRoutes(app: FastifyInstance) {
|
||||||
|
app.get("/health", async () => ({ ok: true }));
|
||||||
|
|
||||||
|
app.get("/v1/chats", async (req) => {
|
||||||
|
requireAdmin(req);
|
||||||
|
const chats = await prisma.chat.findMany({
|
||||||
|
orderBy: { updatedAt: "desc" },
|
||||||
|
take: 100,
|
||||||
|
select: { id: true, title: true, createdAt: true, updatedAt: true },
|
||||||
|
});
|
||||||
|
return { chats };
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/v1/chats", async (req) => {
|
||||||
|
requireAdmin(req);
|
||||||
|
const Body = z.object({ title: z.string().optional() });
|
||||||
|
const body = Body.parse(req.body ?? {});
|
||||||
|
const chat = await prisma.chat.create({ data: { title: body.title } });
|
||||||
|
return { chat };
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/v1/chats/:chatId", async (req) => {
|
||||||
|
requireAdmin(req);
|
||||||
|
const Params = z.object({ chatId: z.string() });
|
||||||
|
const { chatId } = Params.parse(req.params);
|
||||||
|
|
||||||
|
const chat = await prisma.chat.findUnique({
|
||||||
|
where: { id: chatId },
|
||||||
|
include: { messages: { orderBy: { createdAt: "asc" } }, calls: { orderBy: { createdAt: "desc" } } },
|
||||||
|
});
|
||||||
|
if (!chat) return app.httpErrors.notFound("chat not found");
|
||||||
|
return { chat };
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/v1/chats/:chatId/messages", async (req) => {
|
||||||
|
requireAdmin(req);
|
||||||
|
const Params = z.object({ chatId: z.string() });
|
||||||
|
const Body = z.object({
|
||||||
|
role: z.enum(["system", "user", "assistant", "tool"]),
|
||||||
|
content: z.string(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
metadata: z.unknown().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { chatId } = Params.parse(req.params);
|
||||||
|
const body = Body.parse(req.body);
|
||||||
|
|
||||||
|
const msg = await prisma.message.create({
|
||||||
|
data: {
|
||||||
|
chatId,
|
||||||
|
role: body.role as any,
|
||||||
|
content: body.content,
|
||||||
|
name: body.name,
|
||||||
|
metadata: body.metadata as any,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { message: msg };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Main: create a completion via provider+model and store everything.
|
||||||
|
app.post("/v1/chat-completions", async (req) => {
|
||||||
|
requireAdmin(req);
|
||||||
|
|
||||||
|
const Body = z.object({
|
||||||
|
chatId: z.string().optional(),
|
||||||
|
provider: z.enum(["openai", "anthropic", "xai"]),
|
||||||
|
model: z.string().min(1),
|
||||||
|
messages: z.array(
|
||||||
|
z.object({
|
||||||
|
role: z.enum(["system", "user", "assistant", "tool"]),
|
||||||
|
content: z.string(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
temperature: z.number().min(0).max(2).optional(),
|
||||||
|
maxTokens: z.number().int().positive().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = Body.parse(req.body);
|
||||||
|
|
||||||
|
// ensure chat exists if provided
|
||||||
|
if (body.chatId) {
|
||||||
|
const exists = await prisma.chat.findUnique({ where: { id: body.chatId }, select: { id: true } });
|
||||||
|
if (!exists) return app.httpErrors.notFound("chat not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// store user messages (anything not assistant) for DB fidelity
|
||||||
|
if (body.chatId) {
|
||||||
|
const toInsert = body.messages.filter((m) => m.role !== "assistant");
|
||||||
|
if (toInsert.length) {
|
||||||
|
await prisma.message.createMany({
|
||||||
|
data: toInsert.map((m) => ({ chatId: body.chatId!, role: m.role as any, content: m.content, name: m.name })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await runMultiplex(body);
|
||||||
|
|
||||||
|
return {
|
||||||
|
chatId: body.chatId ?? null,
|
||||||
|
...result,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user