'use node'; import { getAuthUserId } from '@convex-dev/auth/server'; import { ConvexError, v } from 'convex/values'; import type { Id } from './_generated/dataModel'; import type { ActionCtx } from './_generated/server'; import { internal } from './_generated/api'; import { action } from './_generated/server'; import { encryptSecret } from './secretCrypto'; const provider = v.union( v.literal('openai'), v.literal('anthropic'), v.literal('google'), v.literal('openrouter'), v.literal('requesty'), v.literal('litellm'), v.literal('cloudflare_ai_gateway'), v.literal('custom_openai_compatible'), v.literal('opencode_openai_login'), ); const authType = v.union( v.literal('api_key'), v.literal('opencode_auth_json'), v.literal('none'), ); const reasoningEffort = v.union( v.literal('none'), v.literal('minimal'), v.literal('low'), v.literal('medium'), v.literal('high'), v.literal('xhigh'), ); const getRequiredUserId = async (ctx: ActionCtx): Promise> => { const userId = await getAuthUserId(ctx); if (!userId) throw new ConvexError('Not authenticated.'); return userId; }; const previewSecret = (secret: string) => { const trimmed = secret.trim(); if (!trimmed) return undefined; if (trimmed.startsWith('{')) return 'auth json configured'; if (trimmed.length <= 10) return 'configured'; return `${trimmed.slice(0, 7)}...${trimmed.slice(-4)}`; }; export const save = action({ args: { profileId: v.optional(v.id('aiProviderProfiles')), name: v.string(), provider, authType, secret: v.optional(v.string()), baseUrl: v.optional(v.string()), defaultModel: v.string(), modelOptions: v.optional(v.array(v.string())), reasoningEffort, enabled: v.boolean(), }, handler: async (ctx, args): Promise> => { const ownerId = await getRequiredUserId(ctx); const secret = args.secret?.trim(); if (!args.profileId && args.authType !== 'none' && !secret) { throw new ConvexError('A credential is required for this provider.'); } return await ctx.runMutation( internal.aiProviderProfiles.upsertEncryptedInternal, { ownerId, profileId: args.profileId, name: args.name, provider: args.provider, authType: args.authType, encryptedSecret: secret ? encryptSecret(secret) : undefined, secretPreview: secret ? previewSecret(secret) : undefined, baseUrl: args.baseUrl, defaultModel: args.defaultModel, modelOptions: args.modelOptions, reasoningEffort: args.reasoningEffort, enabled: args.enabled, }, ); }, });