diff --git a/.dockerignore b/.dockerignore index 793f24558..95c09e912 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,7 @@ node_modules frontend/node_modules frontend/dist +frontend/.tanstack .axonhub axonhub axonhub.db diff --git a/Dockerfile b/Dockerfile index a90ac3b83..4cebbcc53 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM --platform=$BUILDPLATFORM node:20-alpine AS frontend-builder WORKDIR /build -RUN corepack enable && corepack prepare pnpm@latest --activate +RUN corepack enable && corepack prepare pnpm@10 --activate COPY frontend/package.json frontend/pnpm-lock.yaml ./ RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ pnpm install --frozen-lockfile diff --git a/frontend/src/features/channels/components/channels-action-dialog.tsx b/frontend/src/features/channels/components/channels-action-dialog.tsx index 4141e48ce..0e4f78fef 100644 --- a/frontend/src/features/channels/components/channels-action-dialog.tsx +++ b/frontend/src/features/channels/components/channels-action-dialog.tsx @@ -1952,6 +1952,7 @@ export function ChannelsActionDialog({ currentRow, duplicateFromRow, open, onOpe className='min-h-[80px] resize-y pr-10 font-mono text-sm md:col-span-6' autoComplete='new-password' data-form-type='other' + spellCheck={false} aria-invalid={!!fieldState.error} data-testid='channel-api-key-input' /> @@ -2028,6 +2029,7 @@ export function ChannelsActionDialog({ currentRow, duplicateFromRow, open, onOpe className='min-h-[80px] resize-y font-mono text-sm md:col-span-6' autoComplete='new-password' data-form-type='other' + spellCheck={false} aria-invalid={!!fieldState.error} data-testid='channel-api-key-input' /> diff --git a/frontend/src/features/channels/data/config_channels.ts b/frontend/src/features/channels/data/config_channels.ts index 3e319e4b3..dbbaf6c22 100644 --- a/frontend/src/features/channels/data/config_channels.ts +++ b/frontend/src/features/channels/data/config_channels.ts @@ -1,5 +1,35 @@ import type { ComponentType } from 'react'; -import { OpenAI, Anthropic, Google, DeepSeek, Doubao, Moonshot, Zhipu, OpenRouter, XAI, Volcengine, SiliconCloud, PPIO, ZAI, LongCat, Minimax, BurnCloud, Vercel, ModelScope, Bailian, Jina, DeepInfra, Github, Claude, Cerebras, XiaomiMiMo, Fireworks, Ollama } from '@lobehub/icons'; +import { + OpenAI, + Anthropic, + Google, + DeepSeek, + Doubao, + Moonshot, + Zhipu, + OpenRouter, + XAI, + Volcengine, + SiliconCloud, + PPIO, + ZAI, + LongCat, + Minimax, + BurnCloud, + Vercel, + ModelScope, + Bailian, + Jina, + DeepInfra, + Github, + Claude, + Cerebras, + Qiniu, + XiaomiMiMo, + Fireworks, + Ollama, + AiHubMix, +} from '@lobehub/icons'; import { NanoGPTIcon } from '../components/nanogpt-icon'; import { BURNCLOUD_DEFAULT_MODELS } from './burncloud-models'; import { ApiFormat, ChannelType } from './schema'; diff --git a/frontend/src/features/models/components/data-table-row-actions.tsx b/frontend/src/features/models/components/data-table-row-actions.tsx index b9a63fb16..162520c21 100644 --- a/frontend/src/features/models/components/data-table-row-actions.tsx +++ b/frontend/src/features/models/components/data-table-row-actions.tsx @@ -1,6 +1,6 @@ import { DotsHorizontalIcon } from '@radix-ui/react-icons'; import { Row } from '@tanstack/react-table'; -import { IconEdit, IconArchive, IconTrash, IconNote } from '@tabler/icons-react'; +import { IconEdit, IconArchive, IconTrash, IconNote, IconArchiveOff } from '@tabler/icons-react'; import { useTranslation } from 'react-i18next'; import { usePermissions } from '@/hooks/usePermissions'; import { Button } from '@/components/ui/button'; @@ -75,7 +75,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) { {channelPermissions.canRead && } - {model.status !== 'archived' && ( + {model.status !== 'archived' ? ( { setCurrentRow(row.original); @@ -86,6 +86,17 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) { {t('common.buttons.archive')} + ) : ( + { + setCurrentRow(row.original); + setOpen('archive'); + }} + className='text-green-500!' + > + + {t('common.buttons.restore')} + )} { + if (!currentRow) return; + updateModelStatus.mutate( + { id: currentRow.id, status: isArchived ? 'enabled' : 'archived' }, + { onSuccess: () => setOpen(null) }, + ); + }; + + const handleClose = () => { + setOpen(null); + }; + + const getDescription = () => { + if (!currentRow) return null; + const baseDescription = t(isArchived ? 'models.dialogs.status.restoreDescription' : 'models.dialogs.status.archiveDescription', { + name: currentRow.name, + }); + const infoText = t(isArchived ? 'models.dialogs.status.restoreInfo' : 'models.dialogs.status.archiveWarning'); + + return ( +
+

{baseDescription}

+
+
+ +
+

{infoText}

+
+
+
+
+ ); + }; + + return ( + + {isArchived ? : } + {t(isArchived ? 'models.dialogs.status.restoreTitle' : 'models.dialogs.status.archiveTitle')} + + } + desc={getDescription()} + confirmText={t(isArchived ? 'common.buttons.restore' : 'common.buttons.archive')} + cancelBtnText={t('common.buttons.cancel')} + /> + ); +} diff --git a/frontend/src/features/models/components/models-dialogs.tsx b/frontend/src/features/models/components/models-dialogs.tsx index 52163c255..5772f9a58 100644 --- a/frontend/src/features/models/components/models-dialogs.tsx +++ b/frontend/src/features/models/components/models-dialogs.tsx @@ -1,5 +1,6 @@ import { useModels } from '../context/models-context'; import { ModelsActionDialog } from './models-action-dialog'; +import { ModelsArchiveDialog } from './models-archive-dialog'; import { ModelsAssociationDialog } from './models-association-dialog'; import { ModelsBatchCreateDialog } from './models-batch-create-dialog'; import { ModelsBulkDisableDialog } from './models-bulk-disable-dialog'; @@ -16,6 +17,7 @@ export function ModelsDialogs() { {(open === 'create' || open === 'edit') && } {open === 'batchCreate' && } {open === 'delete' && } + {open === 'archive' && } {open === 'association' && } {open === 'settings' && } {open === 'unassociated' && } diff --git a/frontend/src/features/models/data/models.ts b/frontend/src/features/models/data/models.ts index aa87d43be..cbc81aea3 100644 --- a/frontend/src/features/models/data/models.ts +++ b/frontend/src/features/models/data/models.ts @@ -582,6 +582,34 @@ export function useDeleteModel() { }); } +const UPDATE_MODEL_STATUS_MUTATION = ` + mutation UpdateModelStatus($id: ID!, $status: ModelStatus!) { + updateModelStatus(id: $id, status: $status) + } +`; + +export function useUpdateModelStatus() { + const { t } = useTranslation(); + const queryClient = useQueryClient(); + const { handleError } = useErrorHandler(); + + return useMutation({ + mutationFn: async ({ id, status }: { id: string; status: 'enabled' | 'archived' }) => { + const data = await graphqlRequest<{ updateModelStatus: boolean }>(UPDATE_MODEL_STATUS_MUTATION, { id, status }); + return data.updateModelStatus; + }, + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ queryKey: ['models'] }); + const statusKey = variables.status === 'archived' ? 'archiveSuccess' : 'restoreSuccess'; + toast.success(t(`models.messages.${statusKey}`)); + }, + onError: (error, variables) => { + const contextKey = variables.status === 'archived' ? 'archiveTitle' : 'restoreTitle'; + handleError(error, { context: t(`models.dialogs.status.${contextKey}`) }); + }, + }); +} + export function useBulkDisableModels() { const { t } = useTranslation(); const queryClient = useQueryClient(); diff --git a/frontend/src/locales/en/models.json b/frontend/src/locales/en/models.json index be2872096..173577ce2 100644 --- a/frontend/src/locales/en/models.json +++ b/frontend/src/locales/en/models.json @@ -114,6 +114,12 @@ "models.dialogs.status.enableDescription": "Are you sure you want to enable {{name}}?", "models.dialogs.status.disableTitle": "Disable Model", "models.dialogs.status.disableDescription": "Are you sure you want to disable {{name}}?", + "models.dialogs.status.archiveTitle": "Archive Model", + "models.dialogs.status.archiveDescription": "Are you sure you want to archive {{name}}?", + "models.dialogs.status.archiveWarning": "Archived models will not be used for AI requests until they are restored.", + "models.dialogs.status.restoreTitle": "Restore Model", + "models.dialogs.status.restoreDescription": "Are you sure you want to restore {{name}}?", + "models.dialogs.status.restoreInfo": "Restoring this model will re-enable it for AI requests.", "models.dialogs.bulkDisable.title": "Bulk Disable Models", "models.dialogs.bulkDisable.description": "Are you sure you want to disable {{count}} models?", "models.dialogs.bulkDisable.warning": "Disabled models will not be used for AI requests until they are re-enabled.", @@ -169,6 +175,8 @@ "models.messages.deleteSuccess": "Model deleted successfully", "models.messages.bulkDisableSuccess": "Successfully disabled {{count}} models", "models.messages.bulkEnableSuccess": "Successfully enabled {{count}} models", + "models.messages.archiveSuccess": "Model archived successfully", + "models.messages.restoreSuccess": "Model restored successfully", "models.developers.alibaba": "Alibaba", "models.developers.anthropic": "Anthropic", "models.developers.deepseek": "DeepSeek", diff --git a/frontend/src/locales/zh-CN/models.json b/frontend/src/locales/zh-CN/models.json index a741d9a9b..fff838f00 100644 --- a/frontend/src/locales/zh-CN/models.json +++ b/frontend/src/locales/zh-CN/models.json @@ -114,6 +114,12 @@ "models.dialogs.status.enableDescription": "确定要启用 {{name}} 吗?", "models.dialogs.status.disableTitle": "禁用模型", "models.dialogs.status.disableDescription": "确定要禁用 {{name}} 吗?", + "models.dialogs.status.archiveTitle": "归档模型", + "models.dialogs.status.archiveDescription": "确定要归档 {{name}} 吗?", + "models.dialogs.status.archiveWarning": "归档的模型将不会用于 AI 请求,直到恢复。", + "models.dialogs.status.restoreTitle": "恢复模型", + "models.dialogs.status.restoreDescription": "确定要恢复 {{name}} 吗?", + "models.dialogs.status.restoreInfo": "恢复此模型将重新启用它用于 AI 请求。", "models.dialogs.bulkDisable.title": "批量禁用模型", "models.dialogs.bulkDisable.description": "确定要禁用 {{count}} 个模型吗?", "models.dialogs.bulkDisable.warning": "禁用的模型将不会用于 AI 请求,直到重新启用。", @@ -169,6 +175,8 @@ "models.messages.deleteSuccess": "模型删除成功", "models.messages.bulkDisableSuccess": "已成功禁用 {{count}} 个模型", "models.messages.bulkEnableSuccess": "已成功启用 {{count}} 个模型", + "models.messages.archiveSuccess": "模型归档成功", + "models.messages.restoreSuccess": "模型恢复成功", "models.developers.alibaba": "阿里巴巴", "models.developers.anthropic": "Anthropic", "models.developers.deepseek": "深度求索", diff --git a/frontend/src/routes/_authenticated/project/playground/index.tsx b/frontend/src/routes/_authenticated/project/playground/index.tsx index 6d329e33c..ba1b97e15 100644 --- a/frontend/src/routes/_authenticated/project/playground/index.tsx +++ b/frontend/src/routes/_authenticated/project/playground/index.tsx @@ -6,7 +6,7 @@ import Playground from '@/features/playground'; function ProtectedPlayground() { return ( - + diff --git a/internal/ent/internal/schema.go b/internal/ent/internal/schema.go index b5a0b2bc4..986d3c0a0 100644 --- a/internal/ent/internal/schema.go +++ b/internal/ent/internal/schema.go @@ -6,4 +6,4 @@ // Package internal holds a loadable version of the latest schema. package internal -const Schema = "{\"Schema\":\"github.com/looplj/axonhub/internal/ent/schema\",\"Package\":\"github.com/looplj/axonhub/internal/ent\",\"Schemas\":[{\"name\":\"APIKey\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"api_keys\",\"unique\":true,\"inverse\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}],\"Skip\":48}}},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"api_keys\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Skip\":32}}},{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"The creator of the API key\"},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":32}},\"comment\":\"Project ID, default to 1 for backward compatibility\"},{\"name\":\"key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"apikey.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"user\",\"V\":\"user\"},{\"N\":\"service_account\",\"V\":\"service_account\"},{\"N\":\"noauth\",\"V\":\"noauth\"}],\"default\":true,\"default_value\":\"user\",\"default_kind\":24,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":32}},\"comment\":\"API Key type: user, service_account, or noauth\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"apikey.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"enabled\",\"default_kind\":24,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}}},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[\"read_channels\",\"write_requests\"],\"default_kind\":23,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"API Key specific scopes. For user type: default read_channels, write_requests (immutable). For service_account: custom scopes.\"},{\"name\":\"profiles\",\"type\":{\"Type\":3,\"Ident\":\"*objects.APIKeyProfiles\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"APIKeyProfiles\",\"Ident\":\"objects.APIKeyProfiles\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"activeProfile\":\"\",\"profiles\":null},\"default_kind\":22,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}}}],\"indexes\":[{\"fields\":[\"user_id\"],\"storage_key\":\"api_keys_by_user_id\"},{\"fields\":[\"project_id\"],\"storage_key\":\"api_keys_by_project_id\"},{\"unique\":true,\"fields\":[\"key\"],\"storage_key\":\"api_keys_by_key\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Channel\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"executions\",\"type\":\"RequestExecution\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"usage_logs\",\"type\":\"UsageLog\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"channel_probes\",\"type\":\"ChannelProbe\",\"annotations\":{\"EntGQL\":{\"Skip\":48}}},{\"name\":\"channel_model_prices\",\"type\":\"ChannelModelPrice\",\"annotations\":{\"EntGQL\":{\"Skip\":48}}},{\"name\":\"provider_quota_status\",\"type\":\"ProviderQuotaStatus\",\"unique\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}],\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"channel.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"openai\",\"V\":\"openai\"},{\"N\":\"openai_responses\",\"V\":\"openai_responses\"},{\"N\":\"codex\",\"V\":\"codex\"},{\"N\":\"vercel\",\"V\":\"vercel\"},{\"N\":\"anthropic\",\"V\":\"anthropic\"},{\"N\":\"anthropic_aws\",\"V\":\"anthropic_aws\"},{\"N\":\"anthropic_gcp\",\"V\":\"anthropic_gcp\"},{\"N\":\"gemini_openai\",\"V\":\"gemini_openai\"},{\"N\":\"gemini\",\"V\":\"gemini\"},{\"N\":\"gemini_vertex\",\"V\":\"gemini_vertex\"},{\"N\":\"deepseek\",\"V\":\"deepseek\"},{\"N\":\"deepseek_anthropic\",\"V\":\"deepseek_anthropic\"},{\"N\":\"deepinfra\",\"V\":\"deepinfra\"},{\"N\":\"fireworks\",\"V\":\"fireworks\"},{\"N\":\"doubao\",\"V\":\"doubao\"},{\"N\":\"doubao_anthropic\",\"V\":\"doubao_anthropic\"},{\"N\":\"moonshot\",\"V\":\"moonshot\"},{\"N\":\"moonshot_anthropic\",\"V\":\"moonshot_anthropic\"},{\"N\":\"zhipu\",\"V\":\"zhipu\"},{\"N\":\"zai\",\"V\":\"zai\"},{\"N\":\"zhipu_anthropic\",\"V\":\"zhipu_anthropic\"},{\"N\":\"zai_anthropic\",\"V\":\"zai_anthropic\"},{\"N\":\"anthropic_fake\",\"V\":\"anthropic_fake\"},{\"N\":\"openai_fake\",\"V\":\"openai_fake\"},{\"N\":\"openrouter\",\"V\":\"openrouter\"},{\"N\":\"xiaomi\",\"V\":\"xiaomi\"},{\"N\":\"xai\",\"V\":\"xai\"},{\"N\":\"ppio\",\"V\":\"ppio\"},{\"N\":\"siliconflow\",\"V\":\"siliconflow\"},{\"N\":\"volcengine\",\"V\":\"volcengine\"},{\"N\":\"longcat\",\"V\":\"longcat\"},{\"N\":\"longcat_anthropic\",\"V\":\"longcat_anthropic\"},{\"N\":\"minimax\",\"V\":\"minimax\"},{\"N\":\"minimax_anthropic\",\"V\":\"minimax_anthropic\"},{\"N\":\"aihubmix\",\"V\":\"aihubmix\"},{\"N\":\"burncloud\",\"V\":\"burncloud\"},{\"N\":\"modelscope\",\"V\":\"modelscope\"},{\"N\":\"bailian\",\"V\":\"bailian\"},{\"N\":\"bailian_anthropic\",\"V\":\"bailian_anthropic\"},{\"N\":\"moonshot_coding\",\"V\":\"moonshot_coding\"},{\"N\":\"jina\",\"V\":\"jina\"},{\"N\":\"github\",\"V\":\"github\"},{\"N\":\"github_copilot\",\"V\":\"github_copilot\"},{\"N\":\"claudecode\",\"V\":\"claudecode\"},{\"N\":\"cerebras\",\"V\":\"cerebras\"},{\"N\":\"antigravity\",\"V\":\"antigravity\"},{\"N\":\"nanogpt\",\"V\":\"nanogpt\"},{\"N\":\"nanogpt_responses\",\"V\":\"nanogpt_responses\"},{\"N\":\"ollama\",\"V\":\"ollama\"}],\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"TYPE\"}}},{\"name\":\"base_url\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"NAME\"}}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"channel.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"disabled\",\"default_kind\":24,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"STATUS\",\"Skip\":16}}},{\"name\":\"credentials\",\"type\":{\"Type\":3,\"Ident\":\"objects.ChannelCredentials\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"ChannelCredentials\",\"Ident\":\"objects.ChannelCredentials\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"GetAllAPIKeys\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"GetEnabledAPIKeys\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]objects.DisabledAPIKey\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"IsOAuth\":{\"In\":[],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"disabled_api_keys\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.DisabledAPIKey\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.DisabledAPIKey\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true,\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"Disabled API keys with metadata (sensitive; requires channel write permission)\"},{\"name\":\"supported_models\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"manual_models\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"auto_sync_supported_models\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"auto_sync_model_pattern\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Regex pattern to filter models during auto-sync. Empty string means no filtering.\"},{\"name\":\"tags\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"default_test_model\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"policies\",\"type\":{\"Type\":3,\"Ident\":\"objects.ChannelPolicies\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"ChannelPolicies\",\"Ident\":\"objects.ChannelPolicies\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"stream\":\"unlimited\"},\"default_kind\":25,\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*objects.ChannelSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"ChannelSettings\",\"Ident\":\"objects.ChannelSettings\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"autoTrimedModelPrefixes\":null,\"extraModelPrefix\":\"\",\"hideMappedModels\":false,\"hideOriginalModels\":false,\"modelMappings\":[],\"overrideHeaders\":null,\"overrideParameters\":\"\",\"transformOptions\":{\"forceArrayInputs\":false,\"forceArrayInstructions\":false,\"replaceDeveloperRoleWithSystem\":false}},\"default_kind\":22,\"position\":{\"Index\":13,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ordering_weight\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":14,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"ORDERING_WEIGHT\"}},\"comment\":\"Ordering weight for display sorting\"},{\"name\":\"error_message\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":15,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":16}}},{\"name\":\"remark\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":16,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"User-defined remark or note for the channel\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\",\"deleted_at\"],\"storage_key\":\"channels_by_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"ChannelModelPrice\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"channel_model_prices\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"versions\",\"type\":\"ChannelModelPriceVersion\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"price\",\"type\":{\"Type\":3,\"Ident\":\"objects.ModelPrice\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"ModelPrice\",\"Ident\":\"objects.ModelPrice\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"Equals\":{\"In\":[{\"Name\":\"ModelPrice\",\"Ident\":\"objects.ModelPrice\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"Validate\":{\"In\":[],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The model price, if changed, it will genearte a new reference id.\"},{\"name\":\"reference_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The bill should reference this id.\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"channel_id\",\"model_id\",\"deleted_at\"],\"storage_key\":\"channel_model_prices_by_channel_id_model_id\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"ChannelModelPriceVersion\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"channel_model_price\",\"type\":\"ChannelModelPrice\",\"field\":\"channel_model_price_id\",\"ref_name\":\"versions\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"channel_model_price_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"price\",\"type\":{\"Type\":3,\"Ident\":\"objects.ModelPrice\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"ModelPrice\",\"Ident\":\"objects.ModelPrice\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"Equals\":{\"In\":[{\"Name\":\"ModelPrice\",\"Ident\":\"objects.ModelPrice\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"Validate\":{\"In\":[],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The model price, if changed, it will genearte a new reference id.\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"channelmodelpriceversion.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"effective_start_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The effective start time of the model price.\"},{\"name\":\"effective_end_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The effective end time of the model price, null means it is effective until the next version.\"},{\"name\":\"reference_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"immutable\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The bill should reference this id.\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"ChannelOverrideTemplate\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"channel_override_templates\",\"unique\":true,\"inverse\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}],\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":32}},\"comment\":\"Owner of this template\"},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"validators\":1,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Template name, unique per user\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Template description\"},{\"name\":\"override_parameters\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"Override request body parameters as JSON string\",\"deprecated\":true,\"deprecated_reason\":\"Use body_override_operations instead\"},{\"name\":\"override_headers\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.HeaderEntry\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.HeaderEntry\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"Override request headers\",\"deprecated\":true},{\"name\":\"header_override_operations\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.OverrideOperation\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.OverrideOperation\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}},\"comment\":\"Override request headers\"},{\"name\":\"body_override_operations\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.OverrideOperation\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.OverrideOperation\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}},\"comment\":\"Override request body parameters\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"name\",\"deleted_at\"],\"storage_key\":\"channel_override_templates_by_user_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"ChannelProbe\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"channel_probes\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"total_request_count\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"success_request_count\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"avg_tokens_per_second\",\"type\":{\"Type\":20,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"avg_time_to_first_token_ms\",\"type\":{\"Type\":20,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"immutable\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"timestamp\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"channel_id\",\"timestamp\"],\"storage_key\":\"channel_probes_by_channel_id_timestamp\"}]},{\"name\":\"DataStorage\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"executions\",\"type\":\"RequestExecution\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source name\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source description\"},{\"name\":\"primary\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"data source is primary, only the system database is the primary, it can not be archived.\"},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"datastorage.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"database\",\"V\":\"database\"},{\"N\":\"fs\",\"V\":\"fs\"},{\"N\":\"s3\",\"V\":\"s3\"},{\"N\":\"gcs\",\"V\":\"gcs\"},{\"N\":\"webdav\",\"V\":\"webdav\"}],\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source type\"},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*objects.DataStorageSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"DataStorageSettings\",\"Ident\":\"objects.DataStorageSettings\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source setting\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"datastorage.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"active\",\"default_kind\":24,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source status\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\"],\"storage_key\":\"data_sources_by_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Model\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"developer\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"developer of the model, eg. deeepseek\"},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"model id, eg. deeepseek-chat\"},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"model.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"chat\",\"V\":\"chat\"},{\"N\":\"embedding\",\"V\":\"embedding\"},{\"N\":\"rerank\",\"V\":\"rerank\"},{\"N\":\"image_generation\",\"V\":\"image_generation\"},{\"N\":\"video_generation\",\"V\":\"video_generation\"}],\"default\":true,\"default_value\":\"chat\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"model type\"},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"NAME\"}},\"comment\":\"model name, eg. DeepSeek Chat\"},{\"name\":\"icon\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"icon of the model from the lobe-icons, eg. DeepSeek\"},{\"name\":\"group\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"model group, eg. deepseek\"},{\"name\":\"model_card\",\"type\":{\"Type\":3,\"Ident\":\"*objects.ModelCard\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"ModelCard\",\"Ident\":\"objects.ModelCard\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*objects.ModelSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"ModelSettings\",\"Ident\":\"objects.ModelSettings\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"model.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"disabled\",\"default_kind\":24,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":16}}},{\"name\":\"remark\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"User-defined remark or note for the Model\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\",\"deleted_at\"],\"storage_key\":\"models_by_name\"},{\"unique\":true,\"fields\":[\"model_id\",\"deleted_at\"],\"storage_key\":\"models_by_model_id\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Project\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\",\"through\":{\"N\":\"project_users\",\"T\":\"UserProject\"},\"storage_key\":{\"Table\":\"\",\"Symbols\":[\"user_projects_by_user_id_project_id\"],\"Columns\":null},\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"roles\",\"type\":\"Role\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"api_keys\",\"type\":\"APIKey\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"usage_logs\",\"type\":\"UsageLog\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"threads\",\"type\":\"Thread\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"traces\",\"type\":\"Trace\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"prompts\",\"type\":\"Prompt\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"project name\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"project description\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"project.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"active\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"project status\"},{\"name\":\"profiles\",\"type\":{\"Type\":3,\"Ident\":\"*objects.ProjectProfiles\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"ProjectProfiles\",\"Ident\":\"objects.ProjectProfiles\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"activeProfile\":\"\",\"profiles\":null},\"default_kind\":22,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}}}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\",\"deleted_at\"],\"storage_key\":\"projects_by_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Prompt\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"projects\",\"type\":\"Project\",\"ref_name\":\"prompts\",\"inverse\":true,\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"Project ID that this prompt belongs to\"},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt name\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt description\"},{\"name\":\"role\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt role\"},{\"name\":\"content\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt content\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"prompt.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"}],\"default\":true,\"default_value\":\"disabled\",\"default_kind\":24,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"order\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"ORDER\"}},\"comment\":\"prompt insertion order, smaller values are inserted first\"},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"objects.PromptSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"PromptSettings\",\"Ident\":\"objects.PromptSettings\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt settings in JSON format\"}],\"indexes\":[{\"fields\":[\"project_id\"],\"storage_key\":\"prompts_by_project_id\"},{\"unique\":true,\"fields\":[\"project_id\",\"name\",\"deleted_at\"],\"storage_key\":\"prompts_by_project_id_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"PromptProtectionRule\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"NAME\"}},\"comment\":\"Rule name\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Rule description\"},{\"name\":\"pattern\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Regex pattern to match prompt content\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"promptprotectionrule.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"disabled\",\"default_kind\":24,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":16}}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*objects.PromptProtectionSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"PromptProtectionSettings\",\"Ident\":\"objects.PromptProtectionSettings\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Prompt protection rule settings\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\",\"deleted_at\"],\"storage_key\":\"prompt_protection_rules_by_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"ProviderQuotaStatus\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"provider_quota_status\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"provider_type\",\"type\":{\"Type\":6,\"Ident\":\"providerquotastatus.ProviderType\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"claudecode\",\"V\":\"claudecode\"},{\"N\":\"codex\",\"V\":\"codex\"},{\"N\":\"github_copilot\",\"V\":\"github_copilot\"},{\"N\":\"nanogpt\",\"V\":\"nanogpt\"}],\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"providerquotastatus.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"available\",\"V\":\"available\"},{\"N\":\"warning\",\"V\":\"warning\"},{\"N\":\"exhausted\",\"V\":\"exhausted\"},{\"N\":\"unknown\",\"V\":\"unknown\"}],\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Overall status: available, warning, exhausted, unknown\"},{\"name\":\"quota_data\",\"type\":{\"Type\":3,\"Ident\":\"map[string]interface {}\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"map[string]interface {}\",\"Kind\":21,\"PkgPath\":\"\",\"Methods\":{}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Provider-specific quota data\"},{\"name\":\"next_reset_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Timestamp for next quota reset (primary window)\"},{\"name\":\"ready\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":true,\"default_kind\":1,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"True if status is available or warning\"},{\"name\":\"next_check_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Timestamp for next scheduled quota check\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"channel_id\"]},{\"fields\":[\"next_check_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}]},{\"name\":\"Request\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"api_key\",\"type\":\"APIKey\",\"field\":\"api_key_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"immutable\":true},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"trace\",\"type\":\"Trace\",\"field\":\"trace_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"immutable\":true},{\"name\":\"data_storage\",\"type\":\"DataStorage\",\"field\":\"data_storage_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"immutable\":true},{\"name\":\"executions\",\"type\":\"RequestExecution\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"usage_logs\",\"type\":\"UsageLog\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"api_key_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"API Key ID of the request, null for the request from the Admin.\"},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID, default to 1 for backward compatibility\"},{\"name\":\"trace_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Trace ID that this request belongs to\"},{\"name\":\"data_storage_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Data Storage ID that this request belongs to\"},{\"name\":\"source\",\"type\":{\"Type\":6,\"Ident\":\"request.Source\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"api\",\"V\":\"api\"},{\"N\":\"playground\",\"V\":\"playground\"},{\"N\":\"test\",\"V\":\"test\"}],\"default\":true,\"default_value\":\"api\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"format\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"openai/chat_completions\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"request_headers\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Request headers\"},{\"name\":\"request_body\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"immutable\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"response_body\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"response_chunks\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"external_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":512,\"optional\":true,\"validators\":1,\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"request.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"pending\",\"V\":\"pending\"},{\"N\":\"processing\",\"V\":\"processing\"},{\"N\":\"completed\",\"V\":\"completed\"},{\"N\":\"failed\",\"V\":\"failed\"},{\"N\":\"canceled\",\"V\":\"canceled\"}],\"position\":{\"Index\":13,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"stream\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"immutable\":true,\"position\":{\"Index\":14,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"client_ip\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":15,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_latency_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":16,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_first_token_latency_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":17,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_reasoning_duration_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":18,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Reasoning/thinking duration in milliseconds\"},{\"name\":\"content_saved\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":19,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"whether the generated content has been saved to external storage\"},{\"name\":\"content_storage_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":20,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data storage id used to save the content file\"},{\"name\":\"content_storage_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":21,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"storage key/path of the saved content file\"},{\"name\":\"content_saved_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":22,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"when the content file was saved\"}],\"indexes\":[{\"fields\":[\"api_key_id\",\"created_at\"],\"storage_key\":\"requests_by_api_key_id_created_at\"},{\"fields\":[\"project_id\",\"created_at\"],\"storage_key\":\"requests_by_project_id_created_at\"},{\"fields\":[\"channel_id\",\"created_at\"],\"storage_key\":\"requests_by_channel_id_created_at\"},{\"fields\":[\"trace_id\",\"created_at\"],\"storage_key\":\"requests_by_trace_id_created_at\"},{\"fields\":[\"created_at\"],\"storage_key\":\"requests_by_created_at\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"RequestExecution\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"request\",\"type\":\"Request\",\"field\":\"request_id\",\"ref_name\":\"executions\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"executions\",\"unique\":true,\"inverse\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"data_storage\",\"type\":\"DataStorage\",\"field\":\"data_storage_id\",\"ref_name\":\"executions\",\"unique\":true,\"inverse\":true,\"immutable\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"request_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"data_storage_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Data Storage ID that this request belongs to\"},{\"name\":\"external_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"size\":512,\"optional\":true,\"validators\":1,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"format\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"openai/chat_completions\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"request_body\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"immutable\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"response_body\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"response_chunks\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"error_message\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"response_status_code\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"HTTP status code from the upstream provider\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"requestexecution.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"pending\",\"V\":\"pending\"},{\"N\":\"processing\",\"V\":\"processing\"},{\"N\":\"completed\",\"V\":\"completed\"},{\"N\":\"failed\",\"V\":\"failed\"},{\"N\":\"canceled\",\"V\":\"canceled\"}],\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"stream\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"immutable\":true,\"position\":{\"Index\":13,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_latency_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":14,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_first_token_latency_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":15,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_reasoning_duration_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":16,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Reasoning/thinking duration in milliseconds\"},{\"name\":\"request_headers\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":17,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Request headers\"}],\"indexes\":[{\"fields\":[\"request_id\",\"status\",\"created_at\"],\"storage_key\":\"request_executions_by_request_id_status_created_at\"},{\"fields\":[\"channel_id\"],\"storage_key\":\"request_executions_by_channel_id_created_at\"}],\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"Role\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\",\"ref_name\":\"roles\",\"through\":{\"N\":\"user_roles\",\"T\":\"UserRole\"},\"inverse\":true,\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"roles\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"level\",\"type\":{\"Type\":6,\"Ident\":\"role.Level\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"system\",\"V\":\"system\"},{\"N\":\"project\",\"V\":\"project\"}],\"default\":true,\"default_value\":\"system\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Role level: system or project\"},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID for project-level roles, 0 for system roles, it is used to make the role unique in system level.\"},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Available scopes for this role: write_channels, read_channels, add_users, read_users, etc.\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"project_id\",\"name\"],\"storage_key\":\"roles_by_project_id_name\"},{\"fields\":[\"level\"],\"storage_key\":\"roles_by_level\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"System\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"value\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"mediumtext\"}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Thread\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"threads\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"traces\",\"type\":\"Trace\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID that this thread belongs to\"},{\"name\":\"thread_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Unique thread identifier for this thread\"}],\"indexes\":[{\"fields\":[\"project_id\"],\"storage_key\":\"threads_by_project_id\"},{\"unique\":true,\"fields\":[\"thread_id\"],\"storage_key\":\"threads_by_thread_id\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Trace\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"traces\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"thread\",\"type\":\"Thread\",\"field\":\"thread_id\",\"ref_name\":\"traces\",\"unique\":true,\"inverse\":true,\"immutable\":true},{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID that this trace belongs to\"},{\"name\":\"trace_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Unique trace identifier\"},{\"name\":\"thread_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Thread ID that this trace belongs to\"}],\"indexes\":[{\"fields\":[\"project_id\"],\"storage_key\":\"traces_by_project_id\"},{\"unique\":true,\"fields\":[\"trace_id\"],\"storage_key\":\"traces_by_trace_id\"},{\"fields\":[\"thread_id\"],\"storage_key\":\"traces_by_thread_id\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"UsageLog\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"request\",\"type\":\"Request\",\"field\":\"request_id\",\"ref_name\":\"usage_logs\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"usage_logs\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"usage_logs\",\"unique\":true,\"inverse\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"request_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Related request ID\"},{\"name\":\"api_key_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID, default to 1 for backward compatibility\"},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Channel ID used for the request\"},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Model identifier used for the request\"},{\"name\":\"prompt_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of tokens in the prompt\"},{\"name\":\"completion_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of tokens in the completion\"},{\"name\":\"total_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Total number of tokens used\"},{\"name\":\"prompt_audio_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of audio tokens in the prompt\"},{\"name\":\"prompt_cached_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of cached tokens in the prompt\"},{\"name\":\"prompt_write_cached_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of total write cache tokens, if 5m or 1h ttl variant is present, the field is the sum of 5m and 1h\"},{\"name\":\"prompt_write_cached_tokens_5m\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of token write cache with 5m ttl\"},{\"name\":\"prompt_write_cached_tokens_1h\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of token write cache with 1h ttl\"},{\"name\":\"completion_audio_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":13,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of audio tokens in the completion\"},{\"name\":\"completion_reasoning_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":14,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of reasoning tokens in the completion\"},{\"name\":\"completion_accepted_prediction_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":15,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of accepted prediction tokens\"},{\"name\":\"completion_rejected_prediction_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":16,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of rejected prediction tokens\"},{\"name\":\"source\",\"type\":{\"Type\":6,\"Ident\":\"usagelog.Source\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"api\",\"V\":\"api\"},{\"N\":\"playground\",\"V\":\"playground\"},{\"N\":\"test\",\"V\":\"test\"}],\"default\":true,\"default_value\":\"api\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":17,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Source of the request\"},{\"name\":\"format\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"openai/chat_completions\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":18,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Request format used\"},{\"name\":\"total_cost\",\"type\":{\"Type\":20,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":19,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Total cost calculated based on channel model price\"},{\"name\":\"cost_items\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.CostItem\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.CostItem\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":20,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Detailed cost breakdown items in JSON\"},{\"name\":\"cost_price_reference_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":21,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Reference ID to the channel model price version used for cost calculation\"}],\"indexes\":[{\"fields\":[\"request_id\"],\"storage_key\":\"usage_logs_by_request_id\"},{\"fields\":[\"created_at\"],\"storage_key\":\"usage_logs_by_created_at\"},{\"fields\":[\"model_id\",\"created_at\"],\"storage_key\":\"usage_logs_by_model_id_created_at\"},{\"fields\":[\"project_id\",\"created_at\"],\"storage_key\":\"usage_logs_by_project_id_created_at\"},{\"fields\":[\"channel_id\",\"created_at\"],\"storage_key\":\"usage_logs_by_channel_id_created_at\"},{\"fields\":[\"api_key_id\",\"created_at\"],\"storage_key\":\"usage_logs_by_api_key_id_created_at\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"projects\",\"type\":\"Project\",\"ref_name\":\"users\",\"through\":{\"N\":\"project_users\",\"T\":\"UserProject\"},\"inverse\":true,\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"api_keys\",\"type\":\"APIKey\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"roles\",\"type\":\"Role\",\"through\":{\"N\":\"user_roles\",\"T\":\"UserRole\"},\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"channel_override_templates\",\"type\":\"ChannelOverrideTemplate\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"email\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"user.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"activated\",\"V\":\"activated\"},{\"N\":\"deactivated\",\"V\":\"deactivated\"}],\"default\":true,\"default_value\":\"activated\",\"default_kind\":24,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"prefer_language\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"en\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"用户偏好语言\"},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"first_name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"last_name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"avatar\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"mediumtext\"},\"comment\":\"用户头像URL\"},{\"name\":\"is_owner\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"User scopes in system level: write_channels, read_channels, add_users, read_users, etc.\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"email\",\"deleted_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"UserProject\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true,\"immutable\":true},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"unique\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_owner\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Indicates whether the user is the owner of the project. This field is mutable to allow transferring ownership between users. Only users with sufficient permissions (e.g., current owner) can modify this field.\"},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"User-specific scopes: write_channels, read_channels, add_users, read_users, etc.\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"project_id\"],\"storage_key\":\"user_projects_by_user_id_project_id\"},{\"fields\":[\"project_id\"],\"storage_key\":\"user_projects_by_project_id\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"UserRole\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true,\"immutable\":true},{\"name\":\"role\",\"type\":\"Role\",\"field\":\"role_id\",\"unique\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"role_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"default\":true,\"default_kind\":19,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"role_id\"],\"storage_key\":\"user_roles_by_user_id_role_id\"},{\"fields\":[\"role_id\"],\"storage_key\":\"user_roles_by_role_id\"}]}],\"Features\":[\"intercept\",\"schema/snapshot\",\"sql/upsert\",\"sql/modifier\",\"entql\",\"privacy\",\"namedges\"]}" +const Schema = "{\"Schema\":\"github.com/looplj/axonhub/internal/ent/schema\",\"Package\":\"github.com/looplj/axonhub/internal/ent\",\"Schemas\":[{\"name\":\"APIKey\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"api_keys\",\"unique\":true,\"inverse\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}],\"Skip\":48}}},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"api_keys\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Skip\":32}}},{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"The creator of the API key\"},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":32}},\"comment\":\"Project ID, default to 1 for backward compatibility\"},{\"name\":\"key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"apikey.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"user\",\"V\":\"user\"},{\"N\":\"service_account\",\"V\":\"service_account\"},{\"N\":\"noauth\",\"V\":\"noauth\"}],\"default\":true,\"default_value\":\"user\",\"default_kind\":24,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":32}},\"comment\":\"API Key type: user, service_account, or noauth\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"apikey.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"enabled\",\"default_kind\":24,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}}},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[\"read_channels\",\"write_requests\"],\"default_kind\":23,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"API Key specific scopes. For user type: default read_channels, write_requests (immutable). For service_account: custom scopes.\"},{\"name\":\"profiles\",\"type\":{\"Type\":3,\"Ident\":\"*objects.APIKeyProfiles\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"APIKeyProfiles\",\"Ident\":\"objects.APIKeyProfiles\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"activeProfile\":\"\",\"profiles\":null},\"default_kind\":22,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}}}],\"indexes\":[{\"fields\":[\"user_id\"],\"storage_key\":\"api_keys_by_user_id\"},{\"fields\":[\"project_id\"],\"storage_key\":\"api_keys_by_project_id\"},{\"unique\":true,\"fields\":[\"key\"],\"storage_key\":\"api_keys_by_key\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Channel\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"executions\",\"type\":\"RequestExecution\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"usage_logs\",\"type\":\"UsageLog\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"channel_probes\",\"type\":\"ChannelProbe\",\"annotations\":{\"EntGQL\":{\"Skip\":48}}},{\"name\":\"channel_model_prices\",\"type\":\"ChannelModelPrice\",\"annotations\":{\"EntGQL\":{\"Skip\":48}}},{\"name\":\"provider_quota_status\",\"type\":\"ProviderQuotaStatus\",\"unique\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}],\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"channel.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"openai\",\"V\":\"openai\"},{\"N\":\"openai_responses\",\"V\":\"openai_responses\"},{\"N\":\"codex\",\"V\":\"codex\"},{\"N\":\"vercel\",\"V\":\"vercel\"},{\"N\":\"anthropic\",\"V\":\"anthropic\"},{\"N\":\"anthropic_aws\",\"V\":\"anthropic_aws\"},{\"N\":\"anthropic_gcp\",\"V\":\"anthropic_gcp\"},{\"N\":\"gemini_openai\",\"V\":\"gemini_openai\"},{\"N\":\"gemini\",\"V\":\"gemini\"},{\"N\":\"gemini_vertex\",\"V\":\"gemini_vertex\"},{\"N\":\"deepseek\",\"V\":\"deepseek\"},{\"N\":\"deepseek_anthropic\",\"V\":\"deepseek_anthropic\"},{\"N\":\"deepinfra\",\"V\":\"deepinfra\"},{\"N\":\"fireworks\",\"V\":\"fireworks\"},{\"N\":\"doubao\",\"V\":\"doubao\"},{\"N\":\"doubao_anthropic\",\"V\":\"doubao_anthropic\"},{\"N\":\"moonshot\",\"V\":\"moonshot\"},{\"N\":\"moonshot_anthropic\",\"V\":\"moonshot_anthropic\"},{\"N\":\"zhipu\",\"V\":\"zhipu\"},{\"N\":\"zai\",\"V\":\"zai\"},{\"N\":\"zhipu_anthropic\",\"V\":\"zhipu_anthropic\"},{\"N\":\"zai_anthropic\",\"V\":\"zai_anthropic\"},{\"N\":\"anthropic_fake\",\"V\":\"anthropic_fake\"},{\"N\":\"openai_fake\",\"V\":\"openai_fake\"},{\"N\":\"openrouter\",\"V\":\"openrouter\"},{\"N\":\"xiaomi\",\"V\":\"xiaomi\"},{\"N\":\"xai\",\"V\":\"xai\"},{\"N\":\"ppio\",\"V\":\"ppio\"},{\"N\":\"siliconflow\",\"V\":\"siliconflow\"},{\"N\":\"volcengine\",\"V\":\"volcengine\"},{\"N\":\"longcat\",\"V\":\"longcat\"},{\"N\":\"longcat_anthropic\",\"V\":\"longcat_anthropic\"},{\"N\":\"minimax\",\"V\":\"minimax\"},{\"N\":\"minimax_anthropic\",\"V\":\"minimax_anthropic\"},{\"N\":\"aihubmix\",\"V\":\"aihubmix\"},{\"N\":\"burncloud\",\"V\":\"burncloud\"},{\"N\":\"modelscope\",\"V\":\"modelscope\"},{\"N\":\"bailian\",\"V\":\"bailian\"},{\"N\":\"bailian_anthropic\",\"V\":\"bailian_anthropic\"},{\"N\":\"moonshot_coding\",\"V\":\"moonshot_coding\"},{\"N\":\"jina\",\"V\":\"jina\"},{\"N\":\"github\",\"V\":\"github\"},{\"N\":\"github_copilot\",\"V\":\"github_copilot\"},{\"N\":\"claudecode\",\"V\":\"claudecode\"},{\"N\":\"cerebras\",\"V\":\"cerebras\"},{\"N\":\"antigravity\",\"V\":\"antigravity\"},{\"N\":\"nanogpt\",\"V\":\"nanogpt\"},{\"N\":\"nanogpt_responses\",\"V\":\"nanogpt_responses\"},{\"N\":\"ollama\",\"V\":\"ollama\"}],\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"TYPE\"}}},{\"name\":\"base_url\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"NAME\"}}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"channel.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"disabled\",\"default_kind\":24,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"STATUS\",\"Skip\":16}}},{\"name\":\"credentials\",\"type\":{\"Type\":3,\"Ident\":\"objects.ChannelCredentials\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"ChannelCredentials\",\"Ident\":\"objects.ChannelCredentials\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"GetAllAPIKeys\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"GetEnabledAPIKeys\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]objects.DisabledAPIKey\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}]},\"IsOAuth\":{\"In\":[],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"disabled_api_keys\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.DisabledAPIKey\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.DisabledAPIKey\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true,\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"Disabled API keys with metadata (sensitive; requires channel write permission)\"},{\"name\":\"supported_models\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"manual_models\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"auto_sync_supported_models\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"auto_sync_model_pattern\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Regex pattern to filter models during auto-sync. Empty string means no filtering.\"},{\"name\":\"tags\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"default_test_model\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"policies\",\"type\":{\"Type\":3,\"Ident\":\"objects.ChannelPolicies\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"ChannelPolicies\",\"Ident\":\"objects.ChannelPolicies\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"stream\":\"unlimited\"},\"default_kind\":25,\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*objects.ChannelSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"ChannelSettings\",\"Ident\":\"objects.ChannelSettings\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"autoTrimedModelPrefixes\":null,\"extraModelPrefix\":\"\",\"hideMappedModels\":false,\"hideOriginalModels\":false,\"modelMappings\":[],\"overrideHeaders\":null,\"overrideParameters\":\"\",\"transformOptions\":{\"forceArrayInputs\":false,\"forceArrayInstructions\":false,\"replaceDeveloperRoleWithSystem\":false}},\"default_kind\":22,\"position\":{\"Index\":13,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"ordering_weight\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":14,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"ORDERING_WEIGHT\"}},\"comment\":\"Ordering weight for display sorting\"},{\"name\":\"error_message\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":15,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":16}}},{\"name\":\"remark\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":16,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"User-defined remark or note for the channel\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\",\"deleted_at\"],\"storage_key\":\"channels_by_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"ChannelModelPrice\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"channel_model_prices\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"versions\",\"type\":\"ChannelModelPriceVersion\"}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"price\",\"type\":{\"Type\":3,\"Ident\":\"objects.ModelPrice\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"ModelPrice\",\"Ident\":\"objects.ModelPrice\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"Equals\":{\"In\":[{\"Name\":\"ModelPrice\",\"Ident\":\"objects.ModelPrice\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"Validate\":{\"In\":[],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The model price, if changed, it will genearte a new reference id.\"},{\"name\":\"reference_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The bill should reference this id.\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"channel_id\",\"model_id\",\"deleted_at\"],\"storage_key\":\"channel_model_prices_by_channel_id_model_id\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"ChannelModelPriceVersion\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"channel_model_price\",\"type\":\"ChannelModelPrice\",\"field\":\"channel_model_price_id\",\"ref_name\":\"versions\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"channel_model_price_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"price\",\"type\":{\"Type\":3,\"Ident\":\"objects.ModelPrice\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"ModelPrice\",\"Ident\":\"objects.ModelPrice\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"Equals\":{\"In\":[{\"Name\":\"ModelPrice\",\"Ident\":\"objects.ModelPrice\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":null}],\"Out\":[{\"Name\":\"bool\",\"Ident\":\"bool\",\"Kind\":1,\"PkgPath\":\"\",\"Methods\":null}]},\"Validate\":{\"In\":[],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The model price, if changed, it will genearte a new reference id.\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"channelmodelpriceversion.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"effective_start_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The effective start time of the model price.\"},{\"name\":\"effective_end_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The effective end time of the model price, null means it is effective until the next version.\"},{\"name\":\"reference_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"immutable\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"The bill should reference this id.\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"ChannelOverrideTemplate\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"ref_name\":\"channel_override_templates\",\"unique\":true,\"inverse\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}],\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":32}},\"comment\":\"Owner of this template\"},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"validators\":1,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Template name, unique per user\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Template description\"},{\"name\":\"override_parameters\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"Override request body parameters as JSON string\",\"deprecated\":true,\"deprecated_reason\":\"Use body_override_operations instead\"},{\"name\":\"override_headers\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.HeaderEntry\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.HeaderEntry\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"Override request headers\",\"deprecated\":true},{\"name\":\"header_override_operations\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.OverrideOperation\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.OverrideOperation\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}},\"comment\":\"Override request headers\"},{\"name\":\"body_override_operations\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.OverrideOperation\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.OverrideOperation\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}},\"comment\":\"Override request body parameters\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"name\",\"deleted_at\"],\"storage_key\":\"channel_override_templates_by_user_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"ChannelProbe\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"channel_probes\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"total_request_count\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"success_request_count\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"avg_tokens_per_second\",\"type\":{\"Type\":20,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"avg_time_to_first_token_ms\",\"type\":{\"Type\":20,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"immutable\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"timestamp\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}}],\"indexes\":[{\"fields\":[\"channel_id\",\"timestamp\"],\"storage_key\":\"channel_probes_by_channel_id_timestamp\"}]},{\"name\":\"DataStorage\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"executions\",\"type\":\"RequestExecution\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source name\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source description\"},{\"name\":\"primary\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"data source is primary, only the system database is the primary, it can not be archived.\"},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"datastorage.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"database\",\"V\":\"database\"},{\"N\":\"fs\",\"V\":\"fs\"},{\"N\":\"s3\",\"V\":\"s3\"},{\"N\":\"gcs\",\"V\":\"gcs\"},{\"N\":\"webdav\",\"V\":\"webdav\"}],\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source type\"},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*objects.DataStorageSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"DataStorageSettings\",\"Ident\":\"objects.DataStorageSettings\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source setting\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"datastorage.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"active\",\"default_kind\":24,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data source status\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\"],\"storage_key\":\"data_sources_by_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Model\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"developer\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"developer of the model, eg. deeepseek\"},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"model id, eg. deeepseek-chat\"},{\"name\":\"type\",\"type\":{\"Type\":6,\"Ident\":\"model.Type\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"chat\",\"V\":\"chat\"},{\"N\":\"embedding\",\"V\":\"embedding\"},{\"N\":\"rerank\",\"V\":\"rerank\"},{\"N\":\"image_generation\",\"V\":\"image_generation\"},{\"N\":\"video_generation\",\"V\":\"video_generation\"}],\"default\":true,\"default_value\":\"chat\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"model type\"},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"NAME\"}},\"comment\":\"model name, eg. DeepSeek Chat\"},{\"name\":\"icon\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"icon of the model from the lobe-icons, eg. DeepSeek\"},{\"name\":\"group\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"model group, eg. deepseek\"},{\"name\":\"model_card\",\"type\":{\"Type\":3,\"Ident\":\"*objects.ModelCard\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"ModelCard\",\"Ident\":\"objects.ModelCard\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*objects.ModelSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"ModelSettings\",\"Ident\":\"objects.ModelSettings\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"model.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"disabled\",\"default_kind\":24,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":16}}},{\"name\":\"remark\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"User-defined remark or note for the Model\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\",\"deleted_at\"],\"storage_key\":\"models_by_name\"},{\"unique\":true,\"fields\":[\"model_id\",\"deleted_at\"],\"storage_key\":\"models_by_model_id\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Project\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\",\"through\":{\"N\":\"project_users\",\"T\":\"UserProject\"},\"storage_key\":{\"Table\":\"\",\"Symbols\":[\"user_projects_by_user_id_project_id\"],\"Columns\":null},\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"roles\",\"type\":\"Role\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"api_keys\",\"type\":\"APIKey\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"usage_logs\",\"type\":\"UsageLog\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"threads\",\"type\":\"Thread\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"traces\",\"type\":\"Trace\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"prompts\",\"type\":\"Prompt\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"project name\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"project description\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"project.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"active\",\"V\":\"active\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"active\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"project status\"},{\"name\":\"profiles\",\"type\":{\"Type\":3,\"Ident\":\"*objects.ProjectProfiles\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"ProjectProfiles\",\"Ident\":\"objects.ProjectProfiles\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":{\"activeProfile\":\"\",\"profiles\":null},\"default_kind\":22,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}}}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\",\"deleted_at\"],\"storage_key\":\"projects_by_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Prompt\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"projects\",\"type\":\"Project\",\"ref_name\":\"prompts\",\"inverse\":true,\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":48}},\"comment\":\"Project ID that this prompt belongs to\"},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt name\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt description\"},{\"name\":\"role\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt role\"},{\"name\":\"content\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt content\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"prompt.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"}],\"default\":true,\"default_value\":\"disabled\",\"default_kind\":24,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"order\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"ORDER\"}},\"comment\":\"prompt insertion order, smaller values are inserted first\"},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"objects.PromptSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":false,\"RType\":{\"Name\":\"PromptSettings\",\"Ident\":\"objects.PromptSettings\",\"Kind\":25,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"prompt settings in JSON format\"}],\"indexes\":[{\"fields\":[\"project_id\"],\"storage_key\":\"prompts_by_project_id\"},{\"unique\":true,\"fields\":[\"project_id\",\"name\",\"deleted_at\"],\"storage_key\":\"prompts_by_project_id_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"PromptProtectionRule\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"NAME\"}},\"comment\":\"Rule name\"},{\"name\":\"description\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Rule description\"},{\"name\":\"pattern\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Regex pattern to match prompt content\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"promptprotectionrule.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"enabled\",\"V\":\"enabled\"},{\"N\":\"disabled\",\"V\":\"disabled\"},{\"N\":\"archived\",\"V\":\"archived\"}],\"default\":true,\"default_value\":\"disabled\",\"default_kind\":24,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Skip\":16}}},{\"name\":\"settings\",\"type\":{\"Type\":3,\"Ident\":\"*objects.PromptProtectionSettings\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"PromptProtectionSettings\",\"Ident\":\"objects.PromptProtectionSettings\",\"Kind\":22,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{}}},\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Prompt protection rule settings\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"name\",\"deleted_at\"],\"storage_key\":\"prompt_protection_rules_by_name\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"ProviderQuotaStatus\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"provider_quota_status\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"provider_type\",\"type\":{\"Type\":6,\"Ident\":\"providerquotastatus.ProviderType\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"claudecode\",\"V\":\"claudecode\"},{\"N\":\"codex\",\"V\":\"codex\"},{\"N\":\"github_copilot\",\"V\":\"github_copilot\"},{\"N\":\"nanogpt\",\"V\":\"nanogpt\"}],\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"providerquotastatus.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"available\",\"V\":\"available\"},{\"N\":\"warning\",\"V\":\"warning\"},{\"N\":\"exhausted\",\"V\":\"exhausted\"},{\"N\":\"unknown\",\"V\":\"unknown\"}],\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Overall status: available, warning, exhausted, unknown\"},{\"name\":\"quota_data\",\"type\":{\"Type\":3,\"Ident\":\"map[string]interface {}\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"map[string]interface {}\",\"Kind\":21,\"PkgPath\":\"\",\"Methods\":{}}},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Provider-specific quota data\"},{\"name\":\"next_reset_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Timestamp for next quota reset (primary window)\"},{\"name\":\"ready\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":true,\"default_kind\":1,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"True if status is available or warning\"},{\"name\":\"next_check_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Timestamp for next scheduled quota check\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"channel_id\"]},{\"fields\":[\"next_check_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}]},{\"name\":\"Request\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"api_key\",\"type\":\"APIKey\",\"field\":\"api_key_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"immutable\":true},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"trace\",\"type\":\"Trace\",\"field\":\"trace_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"immutable\":true},{\"name\":\"data_storage\",\"type\":\"DataStorage\",\"field\":\"data_storage_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"immutable\":true},{\"name\":\"executions\",\"type\":\"RequestExecution\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"requests\",\"unique\":true,\"inverse\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"usage_logs\",\"type\":\"UsageLog\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"api_key_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"API Key ID of the request, null for the request from the Admin.\"},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID, default to 1 for backward compatibility\"},{\"name\":\"trace_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Trace ID that this request belongs to\"},{\"name\":\"data_storage_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Data Storage ID that this request belongs to\"},{\"name\":\"source\",\"type\":{\"Type\":6,\"Ident\":\"request.Source\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"api\",\"V\":\"api\"},{\"N\":\"playground\",\"V\":\"playground\"},{\"N\":\"test\",\"V\":\"test\"}],\"default\":true,\"default_value\":\"api\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"format\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"openai/chat_completions\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"request_headers\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Request headers\"},{\"name\":\"request_body\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"immutable\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"response_body\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"response_chunks\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"external_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"text\"}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"request.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"pending\",\"V\":\"pending\"},{\"N\":\"processing\",\"V\":\"processing\"},{\"N\":\"completed\",\"V\":\"completed\"},{\"N\":\"failed\",\"V\":\"failed\"},{\"N\":\"canceled\",\"V\":\"canceled\"}],\"position\":{\"Index\":13,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"stream\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"immutable\":true,\"position\":{\"Index\":14,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"client_ip\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":15,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_latency_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":16,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_first_token_latency_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":17,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_reasoning_duration_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":18,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Reasoning/thinking duration in milliseconds\"},{\"name\":\"content_saved\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":19,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"whether the generated content has been saved to external storage\"},{\"name\":\"content_storage_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":20,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"data storage id used to save the content file\"},{\"name\":\"content_storage_key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":21,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"storage key/path of the saved content file\"},{\"name\":\"content_saved_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":22,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"when the content file was saved\"}],\"indexes\":[{\"fields\":[\"api_key_id\",\"created_at\"],\"storage_key\":\"requests_by_api_key_id_created_at\"},{\"fields\":[\"project_id\",\"created_at\"],\"storage_key\":\"requests_by_project_id_created_at\"},{\"fields\":[\"channel_id\",\"created_at\"],\"storage_key\":\"requests_by_channel_id_created_at\"},{\"fields\":[\"trace_id\",\"created_at\"],\"storage_key\":\"requests_by_trace_id_created_at\"},{\"fields\":[\"created_at\"],\"storage_key\":\"requests_by_created_at\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"RequestExecution\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"request\",\"type\":\"Request\",\"field\":\"request_id\",\"ref_name\":\"executions\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"executions\",\"unique\":true,\"inverse\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"data_storage\",\"type\":\"DataStorage\",\"field\":\"data_storage_id\",\"ref_name\":\"executions\",\"unique\":true,\"inverse\":true,\"immutable\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"request_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"data_storage_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Data Storage ID that this request belongs to\"},{\"name\":\"external_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"text\"}},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"format\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"openai/chat_completions\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"request_body\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"immutable\":true,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"response_body\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"response_chunks\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}},{\"name\":\"error_message\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"response_status_code\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"HTTP status code from the upstream provider\"},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"requestexecution.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"pending\",\"V\":\"pending\"},{\"N\":\"processing\",\"V\":\"processing\"},{\"N\":\"completed\",\"V\":\"completed\"},{\"N\":\"failed\",\"V\":\"failed\"},{\"N\":\"canceled\",\"V\":\"canceled\"}],\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"stream\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"immutable\":true,\"position\":{\"Index\":13,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_latency_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":14,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_first_token_latency_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":15,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"metrics_reasoning_duration_ms\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":16,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Reasoning/thinking duration in milliseconds\"},{\"name\":\"request_headers\",\"type\":{\"Type\":3,\"Ident\":\"objects.JSONRawMessage\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"JSONRawMessage\",\"Ident\":\"objects.JSONRawMessage\",\"Kind\":23,\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"Methods\":{\"MarshalGQL\":{\"In\":[{\"Name\":\"Writer\",\"Ident\":\"io.Writer\",\"Kind\":20,\"PkgPath\":\"io\",\"Methods\":null}],\"Out\":[]},\"MarshalJSON\":{\"In\":[],\"Out\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null},{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalGQL\":{\"In\":[{\"Name\":\"\",\"Ident\":\"interface {}\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]},\"UnmarshalJSON\":{\"In\":[{\"Name\":\"\",\"Ident\":\"[]uint8\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":null}],\"Out\":[{\"Name\":\"error\",\"Ident\":\"error\",\"Kind\":20,\"PkgPath\":\"\",\"Methods\":null}]}}}},\"optional\":true,\"position\":{\"Index\":17,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Request headers\"}],\"indexes\":[{\"fields\":[\"request_id\",\"status\",\"created_at\"],\"storage_key\":\"request_executions_by_request_id_status_created_at\"},{\"fields\":[\"channel_id\"],\"storage_key\":\"request_executions_by_channel_id_created_at\"}],\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"Role\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"users\",\"type\":\"User\",\"ref_name\":\"roles\",\"through\":{\"N\":\"user_roles\",\"T\":\"UserRole\"},\"inverse\":true,\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"roles\",\"unique\":true,\"inverse\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"level\",\"type\":{\"Type\":6,\"Ident\":\"role.Level\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"system\",\"V\":\"system\"},{\"N\":\"project\",\"V\":\"project\"}],\"default\":true,\"default_value\":\"system\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Role level: system or project\"},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID for project-level roles, 0 for system roles, it is used to make the role unique in system level.\"},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Available scopes for this role: write_channels, read_channels, add_users, read_users, etc.\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"project_id\",\"name\"],\"storage_key\":\"roles_by_project_id_name\"},{\"fields\":[\"level\"],\"storage_key\":\"roles_by_level\"}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"System\",\"config\":{\"Table\":\"\"},\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"key\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"value\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"mediumtext\"}}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Thread\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"threads\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"traces\",\"type\":\"Trace\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID that this thread belongs to\"},{\"name\":\"thread_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Unique thread identifier for this thread\"}],\"indexes\":[{\"fields\":[\"project_id\"],\"storage_key\":\"threads_by_project_id\"},{\"unique\":true,\"fields\":[\"thread_id\"],\"storage_key\":\"threads_by_thread_id\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"Trace\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"traces\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"thread\",\"type\":\"Thread\",\"field\":\"thread_id\",\"ref_name\":\"traces\",\"unique\":true,\"inverse\":true,\"immutable\":true},{\"name\":\"requests\",\"type\":\"Request\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID that this trace belongs to\"},{\"name\":\"trace_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"unique\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Unique trace identifier\"},{\"name\":\"thread_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Thread ID that this trace belongs to\"}],\"indexes\":[{\"fields\":[\"project_id\"],\"storage_key\":\"traces_by_project_id\"},{\"unique\":true,\"fields\":[\"trace_id\"],\"storage_key\":\"traces_by_trace_id\"},{\"fields\":[\"thread_id\"],\"storage_key\":\"traces_by_thread_id\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"UsageLog\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"request\",\"type\":\"Request\",\"field\":\"request_id\",\"ref_name\":\"usage_logs\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"ref_name\":\"usage_logs\",\"unique\":true,\"inverse\":true,\"required\":true,\"immutable\":true},{\"name\":\"channel\",\"type\":\"Channel\",\"field\":\"channel_id\",\"ref_name\":\"usage_logs\",\"unique\":true,\"inverse\":true,\"immutable\":true,\"annotations\":{\"EntGQL\":{\"Directives\":[{\"arguments\":[{\"Comment\":null,\"Name\":\"forceResolver\",\"Value\":{\"Children\":null,\"Comment\":null,\"Definition\":null,\"ExpectedType\":null,\"ExpectedTypeHasDefault\":false,\"Kind\":5,\"Raw\":\"true\",\"VariableDefinition\":null}}],\"name\":\"goField\"}]}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"request_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Related request ID\"},{\"name\":\"api_key_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":1,\"default_kind\":2,\"immutable\":true,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Project ID, default to 1 for backward compatibility\"},{\"name\":\"channel_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"immutable\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Channel ID used for the request\"},{\"name\":\"model_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Model identifier used for the request\"},{\"name\":\"prompt_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of tokens in the prompt\"},{\"name\":\"completion_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of tokens in the completion\"},{\"name\":\"total_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Total number of tokens used\"},{\"name\":\"prompt_audio_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of audio tokens in the prompt\"},{\"name\":\"prompt_cached_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":9,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of cached tokens in the prompt\"},{\"name\":\"prompt_write_cached_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":10,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of total write cache tokens, if 5m or 1h ttl variant is present, the field is the sum of 5m and 1h\"},{\"name\":\"prompt_write_cached_tokens_5m\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":11,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of token write cache with 5m ttl\"},{\"name\":\"prompt_write_cached_tokens_1h\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":12,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of token write cache with 1h ttl\"},{\"name\":\"completion_audio_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":13,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of audio tokens in the completion\"},{\"name\":\"completion_reasoning_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":14,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of reasoning tokens in the completion\"},{\"name\":\"completion_accepted_prediction_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":15,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of accepted prediction tokens\"},{\"name\":\"completion_rejected_prediction_tokens\",\"type\":{\"Type\":13,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"default\":true,\"default_value\":0,\"default_kind\":6,\"position\":{\"Index\":16,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Number of rejected prediction tokens\"},{\"name\":\"source\",\"type\":{\"Type\":6,\"Ident\":\"usagelog.Source\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"api\",\"V\":\"api\"},{\"N\":\"playground\",\"V\":\"playground\"},{\"N\":\"test\",\"V\":\"test\"}],\"default\":true,\"default_value\":\"api\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":17,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Source of the request\"},{\"name\":\"format\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"openai/chat_completions\",\"default_kind\":24,\"immutable\":true,\"position\":{\"Index\":18,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Request format used\"},{\"name\":\"total_cost\",\"type\":{\"Type\":20,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"position\":{\"Index\":19,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Total cost calculated based on channel model price\"},{\"name\":\"cost_items\",\"type\":{\"Type\":3,\"Ident\":\"[]objects.CostItem\",\"PkgPath\":\"github.com/looplj/axonhub/internal/objects\",\"PkgName\":\"objects\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]objects.CostItem\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":20,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Detailed cost breakdown items in JSON\"},{\"name\":\"cost_price_reference_id\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":21,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Reference ID to the channel model price version used for cost calculation\"}],\"indexes\":[{\"fields\":[\"request_id\"],\"storage_key\":\"usage_logs_by_request_id\"},{\"fields\":[\"created_at\"],\"storage_key\":\"usage_logs_by_created_at\"},{\"fields\":[\"model_id\",\"created_at\"],\"storage_key\":\"usage_logs_by_model_id_created_at\"},{\"fields\":[\"project_id\",\"created_at\"],\"storage_key\":\"usage_logs_by_project_id_created_at\"},{\"fields\":[\"channel_id\",\"created_at\"],\"storage_key\":\"usage_logs_by_channel_id_created_at\"},{\"fields\":[\"api_key_id\",\"created_at\"],\"storage_key\":\"usage_logs_by_api_key_id_created_at\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"User\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"projects\",\"type\":\"Project\",\"ref_name\":\"users\",\"through\":{\"N\":\"project_users\",\"T\":\"UserProject\"},\"inverse\":true,\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"api_keys\",\"type\":\"APIKey\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}},{\"name\":\"roles\",\"type\":\"Role\",\"through\":{\"N\":\"user_roles\",\"T\":\"UserRole\"},\"annotations\":{\"EntGQL\":{\"RelayConnection\":true}}},{\"name\":\"channel_override_templates\",\"type\":\"ChannelOverrideTemplate\",\"annotations\":{\"EntGQL\":{\"RelayConnection\":true,\"Skip\":48}}}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"deleted_at\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":0,\"default_kind\":2,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1},\"annotations\":{\"EntGQL\":{\"Skip\":63}}},{\"name\":\"email\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"status\",\"type\":{\"Type\":6,\"Ident\":\"user.Status\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"enums\":[{\"N\":\"activated\",\"V\":\"activated\"},{\"N\":\"deactivated\",\"V\":\"deactivated\"}],\"default\":true,\"default_value\":\"activated\",\"default_kind\":24,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"prefer_language\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"en\",\"default_kind\":24,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"用户偏好语言\"},{\"name\":\"password\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"sensitive\":true},{\"name\":\"first_name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":4,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"last_name\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":\"\",\"default_kind\":24,\"position\":{\"Index\":5,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"avatar\",\"type\":{\"Type\":7,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"optional\":true,\"position\":{\"Index\":6,\"MixedIn\":false,\"MixinIndex\":0},\"schema_type\":{\"mysql\":\"mediumtext\"},\"comment\":\"用户头像URL\"},{\"name\":\"is_owner\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":7,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":8,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"User scopes in system level: write_channels, read_channels, add_users, read_users, etc.\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"email\",\"deleted_at\"]}],\"hooks\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"interceptors\":[{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":1}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}],\"annotations\":{\"EntGQL\":{\"MutationInputs\":[{\"IsCreate\":true},{}],\"QueryField\":{},\"RelayConnection\":true}}},{\"name\":\"UserProject\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true,\"immutable\":true},{\"name\":\"project\",\"type\":\"Project\",\"field\":\"project_id\",\"unique\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":1,\"MixedIn\":true,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"project_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"is_owner\",\"type\":{\"Type\":1,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"default\":true,\"default_value\":false,\"default_kind\":1,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"Indicates whether the user is the owner of the project. This field is mutable to allow transferring ownership between users. Only users with sufficient permissions (e.g., current owner) can modify this field.\"},{\"name\":\"scopes\",\"type\":{\"Type\":3,\"Ident\":\"[]string\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":true,\"RType\":{\"Name\":\"\",\"Ident\":\"[]string\",\"Kind\":23,\"PkgPath\":\"\",\"Methods\":{}}},\"optional\":true,\"default\":true,\"default_value\":[],\"default_kind\":23,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"comment\":\"User-specific scopes: write_channels, read_channels, add_users, read_users, etc.\"}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"project_id\"],\"storage_key\":\"user_projects_by_user_id_project_id\"},{\"fields\":[\"project_id\"],\"storage_key\":\"user_projects_by_project_id\"}],\"policy\":[{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}]},{\"name\":\"UserRole\",\"config\":{\"Table\":\"\"},\"edges\":[{\"name\":\"user\",\"type\":\"User\",\"field\":\"user_id\",\"unique\":true,\"required\":true,\"immutable\":true},{\"name\":\"role\",\"type\":\"Role\",\"field\":\"role_id\",\"unique\":true,\"required\":true,\"immutable\":true}],\"fields\":[{\"name\":\"user_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":0,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"role_id\",\"type\":{\"Type\":12,\"Ident\":\"\",\"PkgPath\":\"\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"immutable\":true,\"position\":{\"Index\":1,\"MixedIn\":false,\"MixinIndex\":0}},{\"name\":\"created_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"default\":true,\"default_kind\":19,\"position\":{\"Index\":2,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"CREATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}},{\"name\":\"updated_at\",\"type\":{\"Type\":2,\"Ident\":\"\",\"PkgPath\":\"time\",\"PkgName\":\"\",\"Nillable\":false,\"RType\":null},\"nillable\":true,\"optional\":true,\"default\":true,\"default_kind\":19,\"update_default\":true,\"position\":{\"Index\":3,\"MixedIn\":false,\"MixinIndex\":0},\"annotations\":{\"EntGQL\":{\"OrderField\":\"UPDATED_AT\",\"Skip\":48},\"EntSQL\":{\"default_expr\":\"CURRENT_TIMESTAMP\"}}}],\"indexes\":[{\"unique\":true,\"fields\":[\"user_id\",\"role_id\"],\"storage_key\":\"user_roles_by_user_id_role_id\"},{\"fields\":[\"role_id\"],\"storage_key\":\"user_roles_by_role_id\"}]}],\"Features\":[\"intercept\",\"schema/snapshot\",\"sql/upsert\",\"sql/modifier\",\"entql\",\"privacy\",\"namedges\"]}" diff --git a/internal/ent/migrate/schema.go b/internal/ent/migrate/schema.go index fd857dc73..eb7be5ea9 100644 --- a/internal/ent/migrate/schema.go +++ b/internal/ent/migrate/schema.go @@ -418,7 +418,7 @@ var ( {Name: "request_body", Type: field.TypeJSON}, {Name: "response_body", Type: field.TypeJSON, Nullable: true}, {Name: "response_chunks", Type: field.TypeJSON, Nullable: true}, - {Name: "external_id", Type: field.TypeString, Nullable: true, Size: 512}, + {Name: "external_id", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"mysql": "text"}}, {Name: "status", Type: field.TypeEnum, Enums: []string{"pending", "processing", "completed", "failed", "canceled"}}, {Name: "stream", Type: field.TypeBool, Default: false}, {Name: "client_ip", Type: field.TypeString, Default: ""}, @@ -506,7 +506,7 @@ var ( {Name: "created_at", Type: field.TypeTime, Default: schema.Expr("CURRENT_TIMESTAMP")}, {Name: "updated_at", Type: field.TypeTime, Default: schema.Expr("CURRENT_TIMESTAMP")}, {Name: "project_id", Type: field.TypeInt, Default: 1}, - {Name: "external_id", Type: field.TypeString, Nullable: true, Size: 512}, + {Name: "external_id", Type: field.TypeString, Nullable: true, SchemaType: map[string]string{"mysql": "text"}}, {Name: "model_id", Type: field.TypeString}, {Name: "format", Type: field.TypeString, Default: "openai/chat_completions"}, {Name: "request_body", Type: field.TypeJSON}, diff --git a/internal/ent/request/request.go b/internal/ent/request/request.go index 084434898..597707141 100644 --- a/internal/ent/request/request.go +++ b/internal/ent/request/request.go @@ -193,8 +193,6 @@ var ( DefaultProjectID int // DefaultFormat holds the default value on creation for the "format" field. DefaultFormat string - // ExternalIDValidator is a validator for the "external_id" field. It is called by the builders before save. - ExternalIDValidator func(string) error // DefaultStream holds the default value on creation for the "stream" field. DefaultStream bool // DefaultClientIP holds the default value on creation for the "client_ip" field. diff --git a/internal/ent/request_create.go b/internal/ent/request_create.go index d4761a197..9d5e3f26b 100644 --- a/internal/ent/request_create.go +++ b/internal/ent/request_create.go @@ -487,11 +487,6 @@ func (_c *RequestCreate) check() error { if _, ok := _c.mutation.RequestBody(); !ok { return &ValidationError{Name: "request_body", err: errors.New(`ent: missing required field "Request.request_body"`)} } - if v, ok := _c.mutation.ExternalID(); ok { - if err := request.ExternalIDValidator(v); err != nil { - return &ValidationError{Name: "external_id", err: fmt.Errorf(`ent: validator failed for field "Request.external_id": %w`, err)} - } - } if _, ok := _c.mutation.Status(); !ok { return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "Request.status"`)} } diff --git a/internal/ent/request_update.go b/internal/ent/request_update.go index f19c55fdf..c7f19030e 100644 --- a/internal/ent/request_update.go +++ b/internal/ent/request_update.go @@ -442,11 +442,6 @@ func (_u *RequestUpdate) defaults() error { // check runs all checks and user-defined validators on the builder. func (_u *RequestUpdate) check() error { - if v, ok := _u.mutation.ExternalID(); ok { - if err := request.ExternalIDValidator(v); err != nil { - return &ValidationError{Name: "external_id", err: fmt.Errorf(`ent: validator failed for field "Request.external_id": %w`, err)} - } - } if v, ok := _u.mutation.Status(); ok { if err := request.StatusValidator(v); err != nil { return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Request.status": %w`, err)} @@ -1134,11 +1129,6 @@ func (_u *RequestUpdateOne) defaults() error { // check runs all checks and user-defined validators on the builder. func (_u *RequestUpdateOne) check() error { - if v, ok := _u.mutation.ExternalID(); ok { - if err := request.ExternalIDValidator(v); err != nil { - return &ValidationError{Name: "external_id", err: fmt.Errorf(`ent: validator failed for field "Request.external_id": %w`, err)} - } - } if v, ok := _u.mutation.Status(); ok { if err := request.StatusValidator(v); err != nil { return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "Request.status": %w`, err)} diff --git a/internal/ent/requestexecution/requestexecution.go b/internal/ent/requestexecution/requestexecution.go index 44dbcd5aa..cec50f62b 100644 --- a/internal/ent/requestexecution/requestexecution.go +++ b/internal/ent/requestexecution/requestexecution.go @@ -132,8 +132,6 @@ var ( UpdateDefaultUpdatedAt func() time.Time // DefaultProjectID holds the default value on creation for the "project_id" field. DefaultProjectID int - // ExternalIDValidator is a validator for the "external_id" field. It is called by the builders before save. - ExternalIDValidator func(string) error // DefaultFormat holds the default value on creation for the "format" field. DefaultFormat string // DefaultStream holds the default value on creation for the "stream" field. diff --git a/internal/ent/requestexecution_create.go b/internal/ent/requestexecution_create.go index af22082e6..8603ffb54 100644 --- a/internal/ent/requestexecution_create.go +++ b/internal/ent/requestexecution_create.go @@ -330,11 +330,6 @@ func (_c *RequestExecutionCreate) check() error { if _, ok := _c.mutation.RequestID(); !ok { return &ValidationError{Name: "request_id", err: errors.New(`ent: missing required field "RequestExecution.request_id"`)} } - if v, ok := _c.mutation.ExternalID(); ok { - if err := requestexecution.ExternalIDValidator(v); err != nil { - return &ValidationError{Name: "external_id", err: fmt.Errorf(`ent: validator failed for field "RequestExecution.external_id": %w`, err)} - } - } if _, ok := _c.mutation.ModelID(); !ok { return &ValidationError{Name: "model_id", err: errors.New(`ent: missing required field "RequestExecution.model_id"`)} } diff --git a/internal/ent/requestexecution_update.go b/internal/ent/requestexecution_update.go index bb1331ddc..af31151bc 100644 --- a/internal/ent/requestexecution_update.go +++ b/internal/ent/requestexecution_update.go @@ -296,11 +296,6 @@ func (_u *RequestExecutionUpdate) defaults() { // check runs all checks and user-defined validators on the builder. func (_u *RequestExecutionUpdate) check() error { - if v, ok := _u.mutation.ExternalID(); ok { - if err := requestexecution.ExternalIDValidator(v); err != nil { - return &ValidationError{Name: "external_id", err: fmt.Errorf(`ent: validator failed for field "RequestExecution.external_id": %w`, err)} - } - } if v, ok := _u.mutation.Status(); ok { if err := requestexecution.StatusValidator(v); err != nil { return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "RequestExecution.status": %w`, err)} @@ -717,11 +712,6 @@ func (_u *RequestExecutionUpdateOne) defaults() { // check runs all checks and user-defined validators on the builder. func (_u *RequestExecutionUpdateOne) check() error { - if v, ok := _u.mutation.ExternalID(); ok { - if err := requestexecution.ExternalIDValidator(v); err != nil { - return &ValidationError{Name: "external_id", err: fmt.Errorf(`ent: validator failed for field "RequestExecution.external_id": %w`, err)} - } - } if v, ok := _u.mutation.Status(); ok { if err := requestexecution.StatusValidator(v); err != nil { return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "RequestExecution.status": %w`, err)} diff --git a/internal/ent/runtime/runtime.go b/internal/ent/runtime/runtime.go index 35a1bcbbd..1263c7b1c 100644 --- a/internal/ent/runtime/runtime.go +++ b/internal/ent/runtime/runtime.go @@ -526,10 +526,6 @@ func init() { requestDescFormat := requestFields[6].Descriptor() // request.DefaultFormat holds the default value on creation for the format field. request.DefaultFormat = requestDescFormat.Default.(string) - // requestDescExternalID is the schema descriptor for external_id field. - requestDescExternalID := requestFields[12].Descriptor() - // request.ExternalIDValidator is a validator for the "external_id" field. It is called by the builders before save. - request.ExternalIDValidator = requestDescExternalID.Validators[0].(func(string) error) // requestDescStream is the schema descriptor for stream field. requestDescStream := requestFields[14].Descriptor() // request.DefaultStream holds the default value on creation for the stream field. @@ -561,10 +557,6 @@ func init() { requestexecutionDescProjectID := requestexecutionFields[0].Descriptor() // requestexecution.DefaultProjectID holds the default value on creation for the project_id field. requestexecution.DefaultProjectID = requestexecutionDescProjectID.Default.(int) - // requestexecutionDescExternalID is the schema descriptor for external_id field. - requestexecutionDescExternalID := requestexecutionFields[4].Descriptor() - // requestexecution.ExternalIDValidator is a validator for the "external_id" field. It is called by the builders before save. - requestexecution.ExternalIDValidator = requestexecutionDescExternalID.Validators[0].(func(string) error) // requestexecutionDescFormat is the schema descriptor for format field. requestexecutionDescFormat := requestexecutionFields[6].Descriptor() // requestexecution.DefaultFormat holds the default value on creation for the format field. diff --git a/internal/ent/schema/request.go b/internal/ent/schema/request.go index c98fdb17b..c57a4f480 100644 --- a/internal/ent/schema/request.go +++ b/internal/ent/schema/request.go @@ -3,6 +3,7 @@ package schema import ( "entgo.io/contrib/entgql" "entgo.io/ent" + "entgo.io/ent/dialect" "entgo.io/ent/schema" "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" @@ -84,7 +85,9 @@ func (Request) Fields() []ent.Field { // External ID for tracking requests in external systems field.String("external_id"). Optional(). - MaxLen(512), + SchemaType(map[string]string{ + dialect.MySQL: "text", + }), // The status of the request. field.Enum("status").Values("pending", "processing", "completed", "failed", "canceled"), // Whether the request is a streaming request diff --git a/internal/ent/schema/request_execution.go b/internal/ent/schema/request_execution.go index 722fd5f35..d0988c692 100644 --- a/internal/ent/schema/request_execution.go +++ b/internal/ent/schema/request_execution.go @@ -3,6 +3,7 @@ package schema import ( "entgo.io/contrib/entgql" "entgo.io/ent" + "entgo.io/ent/dialect" "entgo.io/ent/schema" "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" @@ -43,7 +44,9 @@ func (RequestExecution) Fields() []ent.Field { // External ID for tracking requests in external systems field.String("external_id"). Optional(). - MaxLen(512), + SchemaType(map[string]string{ + dialect.MySQL: "text", + }), field.String("model_id").Immutable(), // The format of the request, e.g: openai/chat_completions, claude/messages, openai/response. field.String("format").Immutable().Default("openai/chat_completions"), diff --git a/internal/ent/schema/user.go b/internal/ent/schema/user.go index b98389f8a..fa910f59f 100644 --- a/internal/ent/schema/user.go +++ b/internal/ent/schema/user.go @@ -101,7 +101,6 @@ func (User) Policy() ent.Policy { Mutation: scopes.MutationPolicy{ scopes.OwnerRule(), scopes.UserWriteScopeRule(scopes.ScopeWriteUsers), - scopes.UserOwnedMutationRule(), }, } } diff --git a/internal/server/api/openai.go b/internal/server/api/openai.go index e013fb971..8d6afdc34 100644 --- a/internal/server/api/openai.go +++ b/internal/server/api/openai.go @@ -574,15 +574,19 @@ func (handlers *OpenAIHandlers) ListModels(c *gin.Context) { return m.ID }) - dbModels, err := handlers.EntClient.Model.Query(). - Where( - model.StatusEQ(model.StatusEnabled), - model.ModelIDIn(visibleIDs...), - ). - All(ctx) - if err != nil { - handlers.writeOpenAIInternalError(c, requestID, err) - return + var dbModels []*ent.Model + for _, chunk := range lo.Chunk(visibleIDs, biz.SQLiteMaxVariableLimit) { + chunkModels, err := handlers.EntClient.Model.Query(). + Where( + model.StatusEQ(model.StatusEnabled), + model.ModelIDIn(chunk...), + ). + All(ctx) + if err != nil { + handlers.writeOpenAIInternalError(c, requestID, err) + return + } + dbModels = append(dbModels, chunkModels...) } dbModelMap := make(map[string]*ent.Model, len(dbModels)) diff --git a/internal/server/biz/channel_metrics.go b/internal/server/biz/channel_metrics.go index 53cdf4f8b..f0322bd71 100644 --- a/internal/server/biz/channel_metrics.go +++ b/internal/server/biz/channel_metrics.go @@ -98,9 +98,9 @@ type channelMetricsResult struct { func (svc *ChannelService) loadAllChannelMetricsFromExecutions(ctx context.Context, client *ent.Client, since time.Time) (map[int]*channelMetricsResult, error) { // Single query to get request count and last failure time for all channels type queryResult struct { - ChannelID int `json:"channel_id"` - RequestCount int64 `json:"request_count"` - LastFailureAt time.Time `json:"last_failure_at"` + ChannelID int `json:"channel_id"` + RequestCount int64 `json:"request_count"` + LastFailureAt *time.Time `json:"last_failure_at"` } var results []queryResult @@ -112,8 +112,8 @@ func (svc *ChannelService) loadAllChannelMetricsFromExecutions(ctx context.Conte requestexecution.StatusNotIn(requestexecution.StatusPending, requestexecution.StatusProcessing), ). Modify(func(s *sql.Selector) { - // Use a subquery or join to get last failure time per channel - // For simplicity, we use MAX(CASE WHEN status = 'failed' THEN created_at END) to get last failure + // Use MAX(CASE WHEN ...) to get last failure time per channel. + // Returns NULL when there are no failed requests, which maps to nil for *time.Time. s.Select( s.C(requestexecution.FieldChannelID), sql.As(sql.Count("*"), "request_count"), @@ -133,8 +133,8 @@ func (svc *ChannelService) loadAllChannelMetricsFromExecutions(ctx context.Conte ChannelID: r.ChannelID, RequestCount: r.RequestCount, } - if !r.LastFailureAt.IsZero() { - m.LastFailureAt = &r.LastFailureAt + if r.LastFailureAt != nil { + m.LastFailureAt = r.LastFailureAt } metricsMap[r.ChannelID] = m diff --git a/internal/server/biz/model.go b/internal/server/biz/model.go index 04dff0aa4..f143a84a7 100644 --- a/internal/server/biz/model.go +++ b/internal/server/biz/model.go @@ -39,6 +39,12 @@ func NewModelService(params ModelServiceParams) *ModelService { } } +// SQLiteMaxVariableLimit is a safe chunk size for SQL IN clauses. +// SQLite versions prior to 3.32.0 have a limit of 999 variables; +// newer versions support 32766. We use 500 to stay safely below both limits +// and leave room for other query parameters. +const SQLiteMaxVariableLimit = 500 + type ModelService struct { *AbstractService @@ -312,22 +318,31 @@ func (svc *ModelService) BulkCreateModels(ctx context.Context, inputs []*ent.Cre inputMap[key] = true } - // Check if any models already exist - existingModels, err := svc.entFromContext(ctx).Model.Query(). - Where(func(s *sql.Selector) { - var predicates []*sql.Predicate - for _, input := range inputs { - predicates = append(predicates, sql.And( - sql.EQ(model.FieldDeveloper, input.Developer), - sql.EQ(model.FieldModelID, input.ModelID), - )) - } + // Check if any models already exist (chunk inputs to avoid SQLite variable limit; + // each predicate uses 2 variables: developer + modelID). + chunkSize := SQLiteMaxVariableLimit / 2 - s.Where(sql.Or(predicates...)) - }). - All(ctx) - if err != nil { - return nil, fmt.Errorf("failed to check existing models: %w", err) + var existingModels []*ent.Model + + for _, chunk := range lo.Chunk(inputs, chunkSize) { + chunkModels, err := svc.entFromContext(ctx).Model.Query(). + Where(func(s *sql.Selector) { + var predicates []*sql.Predicate + for _, input := range chunk { + predicates = append(predicates, sql.And( + sql.EQ(model.FieldDeveloper, input.Developer), + sql.EQ(model.FieldModelID, input.ModelID), + )) + } + + s.Where(sql.Or(predicates...)) + }). + All(ctx) + if err != nil { + return nil, fmt.Errorf("failed to check existing models: %w", err) + } + + existingModels = append(existingModels, chunkModels...) } if len(existingModels) > 0 { @@ -430,51 +445,63 @@ func (svc *ModelService) DeleteModel(ctx context.Context, id int) error { } // BulkArchiveModels archives multiple models by their IDs. +// IDs are chunked to avoid SQLite "too many SQL variables" error. func (svc *ModelService) BulkArchiveModels(ctx context.Context, ids []int) error { - _, err := svc.entFromContext(ctx).Model.Update(). - Where(model.IDIn(ids...)). - SetStatus(model.StatusArchived). - Save(ctx) - if err != nil { - return fmt.Errorf("failed to bulk archive models: %w", err) + for _, chunk := range lo.Chunk(ids, SQLiteMaxVariableLimit) { + _, err := svc.entFromContext(ctx).Model.Update(). + Where(model.IDIn(chunk...)). + SetStatus(model.StatusArchived). + Save(ctx) + if err != nil { + return fmt.Errorf("failed to bulk archive models: %w", err) + } } return nil } // BulkDisableModels disables multiple models by their IDs. +// IDs are chunked to avoid SQLite "too many SQL variables" error. func (svc *ModelService) BulkDisableModels(ctx context.Context, ids []int) error { - _, err := svc.entFromContext(ctx).Model.Update(). - Where(model.IDIn(ids...)). - SetStatus(model.StatusDisabled). - Save(ctx) - if err != nil { - return fmt.Errorf("failed to bulk disable models: %w", err) + for _, chunk := range lo.Chunk(ids, SQLiteMaxVariableLimit) { + _, err := svc.entFromContext(ctx).Model.Update(). + Where(model.IDIn(chunk...)). + SetStatus(model.StatusDisabled). + Save(ctx) + if err != nil { + return fmt.Errorf("failed to bulk disable models: %w", err) + } } return nil } // BulkEnableModels enables multiple models by their IDs. +// IDs are chunked to avoid SQLite "too many SQL variables" error. func (svc *ModelService) BulkEnableModels(ctx context.Context, ids []int) error { - _, err := svc.entFromContext(ctx).Model.Update(). - Where(model.IDIn(ids...)). - SetStatus(model.StatusEnabled). - Save(ctx) - if err != nil { - return fmt.Errorf("failed to bulk enable models: %w", err) + for _, chunk := range lo.Chunk(ids, SQLiteMaxVariableLimit) { + _, err := svc.entFromContext(ctx).Model.Update(). + Where(model.IDIn(chunk...)). + SetStatus(model.StatusEnabled). + Save(ctx) + if err != nil { + return fmt.Errorf("failed to bulk enable models: %w", err) + } } return nil } // BulkDeleteModels deletes multiple models by their IDs. +// IDs are chunked to avoid SQLite "too many SQL variables" error. func (svc *ModelService) BulkDeleteModels(ctx context.Context, ids []int) error { - _, err := svc.entFromContext(ctx).Model.Delete(). - Where(model.IDIn(ids...)). - Exec(ctx) - if err != nil { - return fmt.Errorf("failed to bulk delete models: %w", err) + for _, chunk := range lo.Chunk(ids, SQLiteMaxVariableLimit) { + _, err := svc.entFromContext(ctx).Model.Delete(). + Where(model.IDIn(chunk...)). + Exec(ctx) + if err != nil { + return fmt.Errorf("failed to bulk delete models: %w", err) + } } return nil @@ -651,17 +678,32 @@ func (svc *ModelService) ListEnabledModels(ctx context.Context) ([]ModelFacade, // queryConfiguredModelFacades queries enabled Model entities and returns them as ModelFacades // filtered by allowed model IDs and channel associations. func (svc *ModelService) queryConfiguredModelFacades(ctx context.Context, allowedModelIDs []string, channels []*Channel) ([]ModelFacade, error) { - query := svc.entFromContext(ctx). - Model. - Query(). - Where(model.StatusEQ(model.StatusEnabled)) + var enabledModels []*ent.Model + if len(allowedModelIDs) > 0 { - query = query.Where(model.ModelIDIn(allowedModelIDs...)) - } + // Chunk allowedModelIDs to avoid SQLite "too many SQL variables" error. + for _, chunk := range lo.Chunk(allowedModelIDs, SQLiteMaxVariableLimit) { + chunkModels, err := svc.entFromContext(ctx).Model.Query(). + Where( + model.StatusEQ(model.StatusEnabled), + model.ModelIDIn(chunk...), + ). + All(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list configured models: %w", err) + } - enabledModels, err := query.All(ctx) - if err != nil { - return nil, fmt.Errorf("failed to list configured models: %w", err) + enabledModels = append(enabledModels, chunkModels...) + } + } else { + var err error + + enabledModels, err = svc.entFromContext(ctx).Model.Query(). + Where(model.StatusEQ(model.StatusEnabled)). + All(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list configured models: %w", err) + } } var models []ModelFacade diff --git a/internal/server/gc/gc.go b/internal/server/gc/gc.go index 4ebf1412b..f307bb915 100644 --- a/internal/server/gc/gc.go +++ b/internal/server/gc/gc.go @@ -571,8 +571,7 @@ func (w *Worker) runVacuum(ctx context.Context) error { vacuumSQL = "VACUUM" } - _, err := sqlDriver.ExecContext(ctx, vacuumSQL, nil, nil) - if err != nil { + if _, err := sqlDriver.ExecContext(ctx, vacuumSQL); err != nil { return fmt.Errorf("failed to execute %s: %w", vacuumSQL, err) } diff --git a/internal/server/gc/vacuum_test.go b/internal/server/gc/vacuum_test.go new file mode 100644 index 000000000..16cf172c7 --- /dev/null +++ b/internal/server/gc/vacuum_test.go @@ -0,0 +1,81 @@ +package gc + +import ( + "context" + "database/sql" + "os" + "testing" + + "github.com/stretchr/testify/require" + + _ "github.com/jackc/pgx/v5/stdlib" + + entsql "entgo.io/ent/dialect/sql" + + "github.com/looplj/axonhub/internal/ent" + "github.com/looplj/axonhub/internal/ent/enttest" +) + +func TestWorker_runVacuum_Disabled(t *testing.T) { + t.Parallel() + + w := &Worker{Config: Config{VacuumEnabled: false}} + require.NoError(t, w.runVacuum(context.Background())) +} + +func TestWorker_runVacuum_SQLite(t *testing.T) { + t.Parallel() + + // Distinct DSN per subtest so the in-memory DBs don't collide. + for _, full := range []bool{false, true} { + name := "vacuum" + if full { + name = "vacuum_full_ignored_on_sqlite" + } + t.Run(name, func(t *testing.T) { + t.Parallel() + + dsn := "file:vac_" + name + "?mode=memory&_fk=1" + client := enttest.NewEntClient(t, "sqlite3", dsn) + t.Cleanup(func() { _ = client.Close() }) + + w := &Worker{Ent: client, Config: Config{VacuumEnabled: true, VacuumFull: full}} + require.NoError(t, w.runVacuum(context.Background())) + }) + } +} + +// TestWorker_runVacuum_Postgres exercises the real pgx code path that previously +// failed with "mismatched param and argument count". Gated on AXONHUB_TEST_PG_DSN +// because the project has no in-process Postgres harness. +func TestWorker_runVacuum_Postgres(t *testing.T) { + dsn := os.Getenv("AXONHUB_TEST_PG_DSN") + if dsn == "" { + t.Skip("AXONHUB_TEST_PG_DSN not set; skipping real-Postgres VACUUM check") + } + + for _, full := range []bool{false, true} { + name := "vacuum" + if full { + name = "vacuum_full" + } + t.Run(name, func(t *testing.T) { + client := newPostgresEntClient(t, dsn) + t.Cleanup(func() { _ = client.Close() }) + + w := &Worker{Ent: client, Config: Config{VacuumEnabled: true, VacuumFull: full}} + require.NoError(t, w.runVacuum(context.Background())) + }) + } +} + +func newPostgresEntClient(t *testing.T, dsn string) *ent.Client { + t.Helper() + + sqlDB, err := sql.Open("pgx", dsn) + require.NoError(t, err) + + require.NoError(t, sqlDB.PingContext(context.Background())) + + return ent.NewClient(ent.Driver(entsql.OpenDB("postgres", sqlDB))) +} diff --git a/internal/server/gql/graphql.go b/internal/server/gql/graphql.go index a71e37780..8a78d5626 100644 --- a/internal/server/gql/graphql.go +++ b/internal/server/gql/graphql.go @@ -7,6 +7,7 @@ import ( "net/http" "entgo.io/contrib/entgql" + "entgo.io/ent/privacy" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/handler" "github.com/99designs/gqlgen/graphql/handler/extension" @@ -144,6 +145,15 @@ func NewGraphqlHandlers(deps Dependencies) *GraphqlHandler { }, } } + // Convert ent privacy deny errors to FORBIDDEN + if errors.Is(err, privacy.Deny) { + return &gqlerror.Error{ + Message: "permission denied", + Extensions: map[string]any{ + "code": xerrors.ErrCodeForbidden, + }, + } + } // Return default error presentation return graphql.DefaultErrorPresenter(ctx, err) }) diff --git a/internal/server/static/dist/.gitkeep b/internal/server/static/dist/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/llm/transformer/anthropic/claudecode/outbound_test.go b/llm/transformer/anthropic/claudecode/outbound_test.go index 02cd8c5b8..1e8e6ee4a 100644 --- a/llm/transformer/anthropic/claudecode/outbound_test.go +++ b/llm/transformer/anthropic/claudecode/outbound_test.go @@ -183,10 +183,13 @@ func TestClaudeCodeTransformer_TransformRequest(t *testing.T) { httpReq, err := transformer.TransformRequest(ctx, req) require.NoError(t, err) - // Should have generated user ID + // Should have generated user ID (may be sanitized to a hash if too long) userID := gjson.GetBytes(httpReq.Body, "metadata.user_id").String() assert.NotEmpty(t, userID) - assert.NotNil(t, ParseUserID(userID)) + if parsed := ParseUserID(userID); parsed == nil { + // Sanitized format: "h_" prefix followed by hex hash + assert.True(t, strings.HasPrefix(userID, "h_"), "user_id should be either parseable v2 format or sanitized h_ hash") + } }) t.Run("does not add billing cch when not official", func(t *testing.T) { diff --git a/llm/transformer/anthropic/outbound_convert.go b/llm/transformer/anthropic/outbound_convert.go index 117386530..21d4a8efc 100644 --- a/llm/transformer/anthropic/outbound_convert.go +++ b/llm/transformer/anthropic/outbound_convert.go @@ -6,6 +6,7 @@ import ( "github.com/looplj/axonhub/llm" "github.com/looplj/axonhub/llm/internal/pkg/xjson" "github.com/looplj/axonhub/llm/internal/pkg/xurl" + "github.com/looplj/axonhub/llm/transformer/openai" "github.com/looplj/axonhub/llm/transformer/shared" ) @@ -122,7 +123,7 @@ func buildBaseRequest(chatReq *llm.Request, config *Config) *MessageRequest { } if chatReq.Metadata != nil && chatReq.Metadata["user_id"] != "" { - req.Metadata = &AnthropicMetadata{UserID: chatReq.Metadata["user_id"]} + req.Metadata = &AnthropicMetadata{UserID: openai.SanitizeUserStr(chatReq.Metadata["user_id"])} } // Determine thinking config priority: adaptive > enabled > disabled diff --git a/llm/transformer/doubao/outbound.go b/llm/transformer/doubao/outbound.go index 8092cc9c3..08d074755 100644 --- a/llm/transformer/doubao/outbound.go +++ b/llm/transformer/doubao/outbound.go @@ -142,7 +142,7 @@ func (t *OutboundTransformer) TransformRequest( } if llmReq.Metadata != nil { - doubaoReq.UserID = llmReq.Metadata["user_id"] + doubaoReq.UserID = openai.SanitizeUserStr(llmReq.Metadata["user_id"]) doubaoReq.RequestID = llmReq.Metadata["request_id"] } diff --git a/llm/transformer/ollama/outbound.go b/llm/transformer/ollama/outbound.go index c3be5090e..6ff9ead13 100644 --- a/llm/transformer/ollama/outbound.go +++ b/llm/transformer/ollama/outbound.go @@ -47,6 +47,7 @@ type ChatRequest struct { type ChatMessage struct { Role string `json:"role"` Content string `json:"content"` + Images []string `json:"images,omitempty"` Thinking string `json:"thinking,omitempty"` } @@ -101,10 +102,10 @@ func (t *OutboundTransformer) TransformRequest(ctx context.Context, llmReq *llm. } for _, msg := range llmReq.Messages { - content := getContentString(msg.Content) ollamaReq.Messages = append(ollamaReq.Messages, ChatMessage{ Role: msg.Role, - Content: content, + Content: getContentString(msg.Content), + Images: getImages(msg.Content), }) } @@ -158,6 +159,35 @@ func getContentString(content llm.MessageContent) string { return "" } +func getImages(content llm.MessageContent) []string { + if len(content.MultipleContent) == 0 { + return nil + } + + images := make([]string, 0) + + for _, part := range content.MultipleContent { + if part.Type != "image_url" || part.ImageURL == nil { + continue + } + + image := strings.TrimSpace(part.ImageURL.URL) + if image == "" { + continue + } + + if comma := strings.Index(image, ","); strings.HasPrefix(image, "data:") && comma >= 0 { + image = image[comma+1:] + } + + if image != "" { + images = append(images, image) + } + } + + return images +} + func (t *OutboundTransformer) buildOptions(llmReq *llm.Request) *Options { var opts *Options diff --git a/llm/transformer/ollama/outbound_test.go b/llm/transformer/ollama/outbound_test.go new file mode 100644 index 000000000..a5f3bd44b --- /dev/null +++ b/llm/transformer/ollama/outbound_test.go @@ -0,0 +1,81 @@ +package ollama + +import ( + "context" + "encoding/json" + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/require" + + "github.com/looplj/axonhub/llm" +) + +func TestTransformRequestPreservesImageURLParts(t *testing.T) { + transformer, err := NewOutboundTransformerWithConfig(&Config{ + BaseURL: "http://localhost:11434", + }) + require.NoError(t, err) + + req, err := transformer.TransformRequest(context.Background(), &llm.Request{ + Model: "qwen3.5:4b-q4_K_M", + Messages: []llm.Message{ + { + Role: "user", + Content: llm.MessageContent{ + MultipleContent: []llm.MessageContentPart{ + { + Type: "text", + Text: lo.ToPtr("OCR this image."), + }, + { + Type: "image_url", + ImageURL: &llm.ImageURL{ + URL: "data:image/png;base64,Zm9v", + }, + }, + }, + }, + }, + }, + }) + require.NoError(t, err) + + var got ChatRequest + require.NoError(t, json.Unmarshal(req.Body, &got)) + require.Len(t, got.Messages, 1) + require.Equal(t, "OCR this image.", got.Messages[0].Content) + require.Equal(t, []string{"Zm9v"}, got.Messages[0].Images) +} + +func TestTransformRequestPreservesPlainImageURLParts(t *testing.T) { + transformer, err := NewOutboundTransformerWithConfig(&Config{ + BaseURL: "http://localhost:11434", + }) + require.NoError(t, err) + + req, err := transformer.TransformRequest(context.Background(), &llm.Request{ + Model: "vision-model", + Messages: []llm.Message{ + { + Role: "user", + Content: llm.MessageContent{ + MultipleContent: []llm.MessageContentPart{ + { + Type: "image_url", + ImageURL: &llm.ImageURL{ + URL: "https://example.com/image.png", + }, + }, + }, + }, + }, + }, + }) + require.NoError(t, err) + + var got ChatRequest + require.NoError(t, json.Unmarshal(req.Body, &got)) + require.Len(t, got.Messages, 1) + require.Equal(t, []string{"https://example.com/image.png"}, got.Messages[0].Images) +} diff --git a/llm/transformer/openai/codex/constants.go b/llm/transformer/openai/codex/constants.go index 02437e871..1a255a2ab 100644 --- a/llm/transformer/openai/codex/constants.go +++ b/llm/transformer/openai/codex/constants.go @@ -19,6 +19,7 @@ func DefaultModels() []string { "gpt-5.3-codex-spark", "gpt-5.4", "gpt-5.4-mini", + "gpt-5.5", } } diff --git a/llm/transformer/openai/embedding.go b/llm/transformer/openai/embedding.go index acd327b1b..4ef1032ce 100644 --- a/llm/transformer/openai/embedding.go +++ b/llm/transformer/openai/embedding.go @@ -54,7 +54,7 @@ func (t *OutboundTransformer) transformEmbeddingRequest( Model: llmReq.Model, EncodingFormat: llmReq.Embedding.EncodingFormat, Dimensions: llmReq.Embedding.Dimensions, - User: llmReq.Embedding.User, + User: sanitizeUserStr(llmReq.Embedding.User), } // Re-marshal to JSON (ensure clean output) diff --git a/llm/transformer/openai/image_outbound.go b/llm/transformer/openai/image_outbound.go index 8c95db34e..b52733ef9 100644 --- a/llm/transformer/openai/image_outbound.go +++ b/llm/transformer/openai/image_outbound.go @@ -343,11 +343,12 @@ func (t *OutboundTransformer) buildImageEditRequest(chatReq *llm.Request, apiKey // Add user if specified if chatReq.Image.User != "" { - if err := writer.WriteField("user", chatReq.Image.User); err != nil { + sanitized := sanitizeUserStr(chatReq.Image.User) + if err := writer.WriteField("user", sanitized); err != nil { return nil, fmt.Errorf("failed to write user field: %w", err) } - jsonBody["user"] = chatReq.Image.User + jsonBody["user"] = sanitized } if err := writer.Close(); err != nil { diff --git a/llm/transformer/openai/outbound_convert.go b/llm/transformer/openai/outbound_convert.go index 549c0aba0..d97ffda06 100644 --- a/llm/transformer/openai/outbound_convert.go +++ b/llm/transformer/openai/outbound_convert.go @@ -1,11 +1,50 @@ package openai import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "github.com/samber/lo" "github.com/looplj/axonhub/llm" ) +// maxUserLen is the maximum allowed length for the user field. +// Some upstream providers (e.g., Zhipu) reject user_id values longer than 128 characters. +const maxUserLen = 128 + +// SanitizeUser ensures the user string does not exceed maxUserLen characters. +// If the input exceeds the limit, it returns a deterministic SHA256-based +// identifier prefixed with "h_" so it remains unique and reproducible. +// Returns nil unchanged. +func SanitizeUser(user *string) *string { + if user == nil || *user == "" { + return user + } + s := SanitizeUserStr(*user) + return &s +} + +// SanitizeUserStr is the string variant of SanitizeUser. +func SanitizeUserStr(user string) string { + if user == "" || len(user) <= maxUserLen { + return user + } + h := sha256.Sum256([]byte(user)) + hexStr := hex.EncodeToString(h[:]) + if len(hexStr) > maxUserLen-2 { + hexStr = hexStr[:maxUserLen-2] + } + return fmt.Sprintf("h_%s", hexStr) +} + +// sanitizeUser is an alias for package-internal use. +func sanitizeUser(user *string) *string { return SanitizeUser(user) } + +// sanitizeUserStr is an alias for package-internal use. +func sanitizeUserStr(user string) string { return SanitizeUserStr(user) } + // RequestFromLLM creates OpenAI Request from unified llm.Request. func RequestFromLLM(r *llm.Request) *Request { if r == nil { @@ -26,7 +65,7 @@ func RequestFromLLM(r *llm.Request) *Request { TopP: r.TopP, PromptCacheKey: r.PromptCacheKey, SafetyIdentifier: r.SafetyIdentifier, - User: r.User, + User: sanitizeUser(r.User), LogitBias: r.LogitBias, Metadata: r.Metadata, Modalities: r.Modalities, diff --git a/llm/transformer/openai/responses/outbound.go b/llm/transformer/openai/responses/outbound.go index 784fb2079..c9cbeafcf 100644 --- a/llm/transformer/openai/responses/outbound.go +++ b/llm/transformer/openai/responses/outbound.go @@ -14,6 +14,7 @@ import ( "github.com/looplj/axonhub/llm/httpclient" "github.com/looplj/axonhub/llm/internal/pkg/xmap" "github.com/looplj/axonhub/llm/transformer" + "github.com/looplj/axonhub/llm/transformer/openai" "github.com/looplj/axonhub/llm/transformer/shared" ) @@ -170,7 +171,7 @@ func (t *OutboundTransformer) TransformRequest(ctx context.Context, llmReq *llm. Store: llmReq.Store, ServiceTier: llmReq.ServiceTier, SafetyIdentifier: llmReq.SafetyIdentifier, - User: llmReq.User, + User: openai.SanitizeUser(llmReq.User), Metadata: llmReq.Metadata, MaxOutputTokens: llmReq.MaxCompletionTokens, TopLogprobs: llmReq.TopLogprobs, diff --git a/llm/transformer/zai/outbound.go b/llm/transformer/zai/outbound.go index 3d6c2c522..aeb00fb50 100644 --- a/llm/transformer/zai/outbound.go +++ b/llm/transformer/zai/outbound.go @@ -124,7 +124,7 @@ func (t *OutboundTransformer) TransformRequest( } if llmReq.Metadata != nil { - zaiReq.UserID = llmReq.Metadata["user_id"] + zaiReq.UserID = openai.SanitizeUserStr(llmReq.Metadata["user_id"]) zaiReq.RequestID = llmReq.Metadata["request_id"] }