Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import { Button } from '@/components/ui/button';
import Link from 'next/link';
import { SquareArrowOutUpRight, Webhook } from 'lucide-react';
import { createHash } from 'crypto';
import { useMutation } from '@tanstack/react-query';
import { useTRPC } from '@/lib/trpc/utils';
import { toast } from 'sonner';
import { useState } from 'react';

function getGravatarUrl(email: string, size: number = 80): string {
const hash = createHash('md5').update(email.toLowerCase().trim()).digest('hex');
Expand All @@ -25,6 +29,24 @@ export function UserAdminAccountInfo(user: UserAdminAccountInfoProps) {
const stripeUrl = `https://dashboard.stripe.com/${process.env.NODE_ENV === 'development' ? 'test/' : ''}customers/${user.stripe_customer_id}`;
const hibpUrl = `https://haveibeenpwned.com/account/${encodeURIComponent(user.google_user_email)}`;

const trpc = useTRPC();
const [isOpenAIDeactivated, setIsOpenAIDeactivated] = useState(user.is_openai_deactivated);
const toggleOpenAIDeactivated = useMutation(
trpc.admin.users.setOpenAIDeactivated.mutationOptions({
onSuccess: (_, variables) => {
setIsOpenAIDeactivated(variables.is_openai_deactivated);
toast.success(
variables.is_openai_deactivated
? 'OpenAI deactivated: balanced will route to Kimi K2.5'
: 'OpenAI re-enabled: balanced will route to Codex'
);
},
onError: error => {
toast.error(error.message || 'Failed to update OpenAI deactivation status');
},
})
);

return (
<Card
className={
Expand Down Expand Up @@ -58,6 +80,24 @@ export function UserAdminAccountInfo(user: UserAdminAccountInfoProps) {
<PaymentMethodStatusBadge paymentMethodStatus={user.paymentMethodStatus} />
<ResetAPIKeyButton userId={user.id} />
{!user.is_sso_protected_domain && <ResetToMagicLinkLoginButton userId={user.id} />}
<Button
size="sm"
variant={isOpenAIDeactivated ? 'destructive' : 'outline'}
disabled={toggleOpenAIDeactivated.isPending}
onClick={() =>
toggleOpenAIDeactivated.mutate({
userId: user.id,
is_openai_deactivated: !isOpenAIDeactivated,
})
}
title={
isOpenAIDeactivated
? 'OpenAI deactivated: balanced routes to Kimi K2.5. Click to re-enable.'
: 'Click to deactivate OpenAI for balanced routing (uses Kimi K2.5 instead of Codex)'
}
>
{isOpenAIDeactivated ? 'OpenAI deactivated' : 'Deactivate OpenAI'}
</Button>
<Button variant="outline" size="sm" asChild>
<Link href={`/admin/users/${encodeURIComponent(user.id)}/heuristic-abuse`}>
View usage + abuse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ function createMockUser(overrides: Partial<User> = {}): User {
account_deletion_requested_at: null,
normalized_email: null,
email_domain: null,
is_openai_deactivated: false,
...overrides,
};
}
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/lib/kilo-auto/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { minimax_m25_free_model } from '@/lib/ai-gateway/providers/minimax';
import type { OpenRouterReasoningConfig } from '@/lib/ai-gateway/providers/openrouter/types';
import type { ModelSettings, OpenCodeSettings, Verbosity } from '@kilocode/db/schema-types';
import { KIMI_CURRENT_MODEL_ID } from '@/lib/ai-gateway/providers/moonshotai';

type AutoModel = {
id: string;
Expand Down Expand Up @@ -83,6 +84,10 @@ export const BALANCED_CODEX_MODEL: ResolvedAutoModel = {
reasoning: { enabled: true, effort: 'low' },
};

export const BALANCED_KIMI_MODEL: ResolvedAutoModel = {
model: KIMI_CURRENT_MODEL_ID,
};

export const BALANCED_CLAW_SETUP_MODEL: ResolvedAutoModel = {
model: claude_sonnet_clawsetup_model.public_id,
reasoning: { enabled: true, effort: 'high' },
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/lib/kilo-auto/resolution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
modeSchema,
BALANCED_CLAW_SETUP_MODEL,
BALANCED_CODEX_MODEL,
BALANCED_KIMI_MODEL,
FRONTIER_MODE_TO_MODEL,
FRONTIER_CODE_MODEL,
type ResolvedAutoModel,
Expand Down Expand Up @@ -69,6 +70,10 @@ export async function resolveAutoModel(
return BALANCED_CLAW_SETUP_MODEL;
}
}
const user = await userPromise;
if (user?.is_openai_deactivated) {
return BALANCED_KIMI_MODEL;
}
return BALANCED_CODEX_MODEL;
}
return (mode !== null ? FRONTIER_MODE_TO_MODEL[mode] : null) ?? FRONTIER_CODE_MODEL;
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/lib/token.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const mockUser: User = {
account_deletion_requested_at: null,
normalized_email: null,
email_domain: null,
is_openai_deactivated: false,
};

describe('Token Functions', () => {
Expand Down
16 changes: 16 additions & 0 deletions apps/web/src/routers/admin-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ const UpdateUserBlockStatusSchema = z.object({
blocked_reason: z.string().nullable(),
});

const SetOpenAIDeactivatedSchema = z.object({
userId: z.string(),
is_openai_deactivated: z.boolean(),
});

const GetStytchFingerprintsSchema = z.object({
kilo_user_id: z.string(),
fingerprint_type: z
Expand Down Expand Up @@ -356,6 +361,17 @@ export const adminRouter = createTRPCRouter({
return successResult();
}),

setOpenAIDeactivated: adminProcedure
.input(SetOpenAIDeactivatedSchema)
.mutation(async ({ input }) => {
await db
.update(kilocode_users)
.set({ is_openai_deactivated: input.is_openai_deactivated })
.where(eq(kilocode_users.id, input.userId));

return successResult();
}),

getStytchFingerprints: adminProcedure
.input(GetStytchFingerprintsSchema)
.query(async ({ input }) => {
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/tests/helpers/user.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export function defineTestUser(userData: Partial<User> = {}): User {
account_deletion_requested_at: null,
normalized_email: null,
email_domain: null,
is_openai_deactivated: false,
...userData,
} satisfies User;
}
Expand Down
1 change: 1 addition & 0 deletions packages/db/src/migrations/0097_pink_patriot.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE "kilocode_users" ADD COLUMN "is_openai_deactivated" boolean DEFAULT false NOT NULL;
Loading
Loading