91 lines
2.6 KiB
TypeScript
91 lines
2.6 KiB
TypeScript
'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<Id<'users'>> => {
|
|
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<Id<'aiProviderProfiles'>> => {
|
|
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,
|
|
},
|
|
);
|
|
},
|
|
});
|