mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
Connect more of the Default Parameters and Model/Provider whitelist screen (#28)
This commit is contained in:
parent
a933a42de5
commit
01e6eada6d
7 changed files with 621 additions and 268 deletions
|
|
@ -2,34 +2,33 @@
|
|||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { AuditLogTargetType } from '@/db/schema';
|
||||
import { createAuditLog } from '@/lib/server/auditLogs';
|
||||
|
||||
import { db } from '@/db';
|
||||
import { AuditLogTargetType, orgSettings } from '@/db/schema';
|
||||
import { insertAuditLog } from '@/lib/server/auditLogs';
|
||||
import {
|
||||
handleError,
|
||||
isAuthSuccess,
|
||||
validateAuth,
|
||||
type ApiResponse,
|
||||
} from './apiUtils';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { ORGANIZATION_ALLOW_ALL } from '@/schemas';
|
||||
|
||||
const defaultParametersSchema = z.object({
|
||||
experimentalPowerSteering: z.boolean().optional(),
|
||||
terminalOutputLimit: z.number().int().nonnegative().optional(),
|
||||
compressProgressBar: z.boolean().optional(),
|
||||
terminalOutputLineLimit: z.number().int().nonnegative().optional(),
|
||||
terminalCompressProgressBar: z.boolean().optional(),
|
||||
inheritEnvVars: z.boolean().optional(),
|
||||
disableShellIntegration: z.boolean().optional(),
|
||||
shellIntegrationTimeout: z.number().int().nonnegative().optional(),
|
||||
commandDelay: z.number().int().nonnegative().optional(),
|
||||
enablePowerShellCounter: z.boolean().optional(),
|
||||
clearZshEol: z.boolean().optional(),
|
||||
enableOhMyZsh: z.boolean().optional(),
|
||||
terminalShellIntegrationDisabled: z.boolean().optional(),
|
||||
terminalShellIntegrationTimeout: z.number().int().nonnegative().optional(),
|
||||
terminalCommandDelay: z.number().int().nonnegative().optional(),
|
||||
terminalZshClearEolMark: z.boolean().optional(),
|
||||
enablePowerlevel10k: z.boolean().optional(),
|
||||
openTabsLimit: z.number().int().nonnegative().optional(),
|
||||
workspaceFilesLimit: z.number().int().nonnegative().optional(),
|
||||
showRooignoreFiles: z.boolean().optional(),
|
||||
fileReadThreshold: z.number().int().optional(),
|
||||
alwaysReadEntireFile: z.boolean().optional(),
|
||||
enableAutoCheckpoints: z.boolean().optional(),
|
||||
maxOpenTabsContext: z.number().int().nonnegative().optional(),
|
||||
maxWorkspaceFiles: z.number().int().nonnegative().optional(),
|
||||
showRooIgnoredFiles: z.boolean().optional(),
|
||||
maxReadFileLine: z.number().int().gte(-1).optional(),
|
||||
enableCheckpoints: z.boolean().optional(),
|
||||
useCustomTemperature: z.boolean().optional(),
|
||||
temperature: z.number().nonnegative().optional(),
|
||||
rateLimit: z.number().nonnegative().optional(),
|
||||
|
|
@ -62,15 +61,33 @@ export async function updateDefaultParameters(
|
|||
|
||||
const validatedData = result.data;
|
||||
|
||||
// TODO: Audit log description should contain more granular information on
|
||||
// what changed.
|
||||
await createAuditLog({
|
||||
userId: authResult.userId,
|
||||
orgId: authResult.orgId,
|
||||
targetType: AuditLogTargetType.DEFAULT_PARAMETERS,
|
||||
targetId: 'default-parameters',
|
||||
newValue: validatedData,
|
||||
description: 'Updated default parameters',
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.insert(orgSettings)
|
||||
.values({
|
||||
orgId: authResult.orgId,
|
||||
version: 1,
|
||||
defaultSettings: validatedData,
|
||||
allowList: ORGANIZATION_ALLOW_ALL,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: orgSettings.orgId,
|
||||
set: {
|
||||
defaultSettings: validatedData,
|
||||
version: sql`${orgSettings.version} + 1`,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: consider trying to capture the changes more granularly,
|
||||
// although that would prevent upsert
|
||||
await insertAuditLog(tx, {
|
||||
userId: authResult.userId,
|
||||
orgId: authResult.orgId,
|
||||
targetType: AuditLogTargetType.DEFAULT_PARAMETERS,
|
||||
targetId: 'default-parameters',
|
||||
newValue: validatedData,
|
||||
description: 'Updated default parameters',
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,27 @@
|
|||
'use server';
|
||||
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { auth } from '@clerk/nextjs/server';
|
||||
|
||||
import { db } from '@/db';
|
||||
import { orgSettings, type OrgSettings } from '@/db/schema';
|
||||
import { AuditLogTargetType, orgSettings } from '@/db/schema';
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
import {
|
||||
ORGANIZATION_ALLOW_ALL,
|
||||
ORGANIZATION_DEFAULT,
|
||||
type OrganizationSettings,
|
||||
organizationAllowListSchema,
|
||||
organizationDefaultSettingsSchema,
|
||||
} from '@/schemas';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
handleError,
|
||||
isAuthSuccess,
|
||||
validateAuth,
|
||||
type ApiResponse,
|
||||
} from './apiUtils';
|
||||
import { insertAuditLog } from '@/lib/server/auditLogs';
|
||||
|
||||
export async function getOrganizationSettings(): Promise<
|
||||
OrgSettings | undefined
|
||||
OrganizationSettings | undefined
|
||||
> {
|
||||
const { userId, orgId } = await auth();
|
||||
|
||||
|
|
@ -25,5 +39,111 @@ export async function getOrganizationSettings(): Promise<
|
|||
.where(eq(orgSettings.orgId, orgId))
|
||||
.limit(1);
|
||||
|
||||
return settings.length === 0 ? undefined : settings[0];
|
||||
return settings.length === 0 ? ORGANIZATION_DEFAULT : settings[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Schema for updating organization settings
|
||||
*/
|
||||
const updateOrganizationSchema = z
|
||||
.object({
|
||||
defaultSettings: organizationDefaultSettingsSchema.optional(),
|
||||
allowList: organizationAllowListSchema.optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) =>
|
||||
data.defaultSettings !== undefined || data.allowList !== undefined,
|
||||
{
|
||||
message: 'At least one of defaultSettings or allowList must be provided',
|
||||
},
|
||||
);
|
||||
|
||||
type UpdateOrganizationRequest = z.infer<typeof updateOrganizationSchema>;
|
||||
|
||||
export async function updateOrganization(
|
||||
data: UpdateOrganizationRequest,
|
||||
): Promise<ApiResponse> {
|
||||
try {
|
||||
const authResult = await validateAuth();
|
||||
if (!isAuthSuccess(authResult)) return authResult;
|
||||
const { userId, orgId } = authResult;
|
||||
|
||||
const result = updateOrganizationSchema.safeParse(data);
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid request data',
|
||||
};
|
||||
}
|
||||
const validatedData = result.data;
|
||||
|
||||
// Perform database update in a transaction
|
||||
await db.transaction(async (tx) => {
|
||||
// Get current settings or prepare for insert
|
||||
const currentSettings = await tx
|
||||
.select()
|
||||
.from(orgSettings)
|
||||
.where(eq(orgSettings.orgId, orgId))
|
||||
.limit(1);
|
||||
|
||||
const isNewRecord = currentSettings.length === 0;
|
||||
|
||||
const updateData: Partial<typeof orgSettings.$inferInsert> = {};
|
||||
|
||||
if (validatedData.defaultSettings) {
|
||||
updateData.defaultSettings = validatedData.defaultSettings;
|
||||
}
|
||||
|
||||
if (validatedData.allowList) {
|
||||
updateData.allowList = validatedData.allowList;
|
||||
}
|
||||
|
||||
if (isNewRecord) {
|
||||
await tx.insert(orgSettings).values({
|
||||
orgId,
|
||||
version: 1,
|
||||
defaultSettings: validatedData.defaultSettings || {},
|
||||
allowList: validatedData.allowList || ORGANIZATION_ALLOW_ALL,
|
||||
});
|
||||
} else {
|
||||
await tx
|
||||
.update(orgSettings)
|
||||
.set({
|
||||
...updateData,
|
||||
version: sql`${orgSettings.version} + 1`,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(orgSettings.orgId, orgId));
|
||||
}
|
||||
|
||||
if (validatedData.defaultSettings) {
|
||||
await insertAuditLog(tx, {
|
||||
userId,
|
||||
orgId,
|
||||
targetType: AuditLogTargetType.DEFAULT_PARAMETERS,
|
||||
targetId: 'organization-default-settings',
|
||||
newValue: validatedData.defaultSettings,
|
||||
description: 'Updated organization default settings',
|
||||
});
|
||||
}
|
||||
|
||||
if (validatedData.allowList) {
|
||||
await insertAuditLog(tx, {
|
||||
userId,
|
||||
orgId,
|
||||
targetType: AuditLogTargetType.PROVIDER_WHITELIST,
|
||||
targetId: 'organization-allow-list',
|
||||
newValue: validatedData.allowList,
|
||||
description: 'Updated organization allow list',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Organization settings updated successfully',
|
||||
};
|
||||
} catch (error) {
|
||||
return handleError(error, 'organization_settings');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,18 @@
|
|||
'use client';
|
||||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import { useState, useMemo, useRef, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
useQuery,
|
||||
useQueryClient,
|
||||
type QueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
|
||||
import { updateDefaultParameters } from '@/actions/defaultParameters';
|
||||
import { getOrganizationSettings } from '@/actions/organizationSettings';
|
||||
import { type OrganizationSettings } from '@/schemas';
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
|
|
@ -24,22 +31,19 @@ import { CheckCheck, FlaskConical, SlidersHorizontal } from 'lucide-react';
|
|||
|
||||
type DefaultParamsFormValues = {
|
||||
experimentalPowerSteering: boolean;
|
||||
terminalOutputLimit: number;
|
||||
compressProgressBar: boolean;
|
||||
terminalOutputLineLimit: number;
|
||||
terminalCompressProgressBar: boolean;
|
||||
inheritEnvVars: boolean;
|
||||
disableShellIntegration: boolean;
|
||||
shellIntegrationTimeout: number;
|
||||
commandDelay: number;
|
||||
enablePowerShellCounter: boolean;
|
||||
clearZshEol: boolean;
|
||||
enableOhMyZsh: boolean;
|
||||
terminalShellIntegrationDisabled: boolean;
|
||||
terminalShellIntegrationTimeout: number;
|
||||
terminalCommandDelay: number;
|
||||
terminalZshClearEolMark: boolean;
|
||||
enablePowerlevel10k: boolean;
|
||||
openTabsLimit: number;
|
||||
workspaceFilesLimit: number;
|
||||
showRooignoreFiles: boolean;
|
||||
fileReadThreshold: number;
|
||||
alwaysReadEntireFile: boolean;
|
||||
enableAutoCheckpoints: boolean;
|
||||
maxOpenTabsContext: number;
|
||||
maxWorkspaceFiles: number;
|
||||
showRooIgnoredFiles: boolean;
|
||||
maxReadFileLine: number;
|
||||
enableCheckpoints: boolean;
|
||||
useCustomTemperature: boolean;
|
||||
temperature: number;
|
||||
rateLimit: number;
|
||||
|
|
@ -47,44 +51,102 @@ type DefaultParamsFormValues = {
|
|||
matchPrecision: number;
|
||||
};
|
||||
|
||||
export const DefaultParameters = () => {
|
||||
const mergeWithDefaultValues = (
|
||||
defaultValues: DefaultParamsFormValues,
|
||||
orgSettings?: OrganizationSettings,
|
||||
): DefaultParamsFormValues => {
|
||||
if (!orgSettings || !orgSettings.defaultSettings) {
|
||||
return defaultValues;
|
||||
}
|
||||
|
||||
return {
|
||||
...defaultValues,
|
||||
...orgSettings.defaultSettings,
|
||||
};
|
||||
};
|
||||
|
||||
const defaultFormValues: DefaultParamsFormValues = {
|
||||
experimentalPowerSteering: true,
|
||||
terminalOutputLineLimit: 500,
|
||||
terminalCompressProgressBar: true,
|
||||
inheritEnvVars: true,
|
||||
terminalShellIntegrationDisabled: false,
|
||||
terminalShellIntegrationTimeout: 5,
|
||||
terminalCommandDelay: 0,
|
||||
terminalZshClearEolMark: true,
|
||||
enablePowerlevel10k: false,
|
||||
maxOpenTabsContext: 20,
|
||||
maxWorkspaceFiles: 200,
|
||||
showRooIgnoredFiles: true,
|
||||
maxReadFileLine: 500,
|
||||
enableCheckpoints: true,
|
||||
useCustomTemperature: true,
|
||||
temperature: 0,
|
||||
rateLimit: 0,
|
||||
enableEditingThroughDiffs: true,
|
||||
matchPrecision: 100,
|
||||
} as const;
|
||||
|
||||
type ParametersFormProps = {
|
||||
orgSettings: OrganizationSettings;
|
||||
queryClient: QueryClient;
|
||||
};
|
||||
|
||||
const ParametersForm = ({ orgSettings, queryClient }: ParametersFormProps) => {
|
||||
const t = useTranslations('ProviderWhitelist');
|
||||
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [readEntireFile, setReadEntireFile] = useState(false);
|
||||
|
||||
const mergedValues = useMemo(
|
||||
() => mergeWithDefaultValues(defaultFormValues, orgSettings),
|
||||
[orgSettings],
|
||||
);
|
||||
|
||||
const previousMaxReadFileLine = useRef<number>(
|
||||
mergedValues.maxReadFileLine === -1 ? 500 : mergedValues.maxReadFileLine,
|
||||
);
|
||||
|
||||
const form = useForm<DefaultParamsFormValues>({
|
||||
defaultValues: {
|
||||
experimentalPowerSteering: true,
|
||||
terminalOutputLimit: 500,
|
||||
compressProgressBar: true,
|
||||
inheritEnvVars: true,
|
||||
disableShellIntegration: false,
|
||||
shellIntegrationTimeout: 5,
|
||||
commandDelay: 0,
|
||||
enablePowerShellCounter: false,
|
||||
clearZshEol: true,
|
||||
enableOhMyZsh: false,
|
||||
enablePowerlevel10k: false,
|
||||
openTabsLimit: 20,
|
||||
workspaceFilesLimit: 200,
|
||||
showRooignoreFiles: true,
|
||||
fileReadThreshold: 500,
|
||||
alwaysReadEntireFile: false,
|
||||
enableAutoCheckpoints: true,
|
||||
useCustomTemperature: true,
|
||||
temperature: 0,
|
||||
rateLimit: 0,
|
||||
enableEditingThroughDiffs: true,
|
||||
matchPrecision: 100,
|
||||
},
|
||||
defaultValues: mergedValues,
|
||||
});
|
||||
|
||||
const maxReadFileLineValue = form.watch('maxReadFileLine');
|
||||
|
||||
useEffect(() => {
|
||||
previousMaxReadFileLine.current =
|
||||
mergedValues.maxReadFileLine === -1 ? 500 : mergedValues.maxReadFileLine;
|
||||
}, [mergedValues]);
|
||||
|
||||
useEffect(() => {
|
||||
setReadEntireFile(maxReadFileLineValue === -1);
|
||||
|
||||
if (maxReadFileLineValue !== -1) {
|
||||
previousMaxReadFileLine.current = maxReadFileLineValue;
|
||||
}
|
||||
}, [maxReadFileLineValue, form]);
|
||||
|
||||
const handleReadEntireFileChange = (checked: boolean) => {
|
||||
if (checked) {
|
||||
const currentValue = form.getValues('maxReadFileLine');
|
||||
if (currentValue !== -1) {
|
||||
previousMaxReadFileLine.current = currentValue;
|
||||
}
|
||||
form.setValue('maxReadFileLine', -1);
|
||||
} else {
|
||||
form.setValue('maxReadFileLine', previousMaxReadFileLine.current);
|
||||
}
|
||||
setReadEntireFile(checked);
|
||||
};
|
||||
|
||||
const onSubmit = async (data: DefaultParamsFormValues) => {
|
||||
setIsSaving(true);
|
||||
|
||||
try {
|
||||
const result = await updateDefaultParameters(data);
|
||||
if (result.success) {
|
||||
queryClient.invalidateQueries({ queryKey: ['organizationSettings'] });
|
||||
|
||||
toast('Settings saved', {
|
||||
description: 'Default parameters have been updated successfully.',
|
||||
});
|
||||
|
|
@ -124,7 +186,7 @@ export const DefaultParameters = () => {
|
|||
<div className="mt-4 space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="enableAutoCheckpoints"
|
||||
name="enableCheckpoints"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
|
|
@ -172,7 +234,7 @@ export const DefaultParameters = () => {
|
|||
<div className="mt-4 space-y-6">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="openTabsLimit"
|
||||
name="maxOpenTabsContext"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -202,7 +264,7 @@ export const DefaultParameters = () => {
|
|||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="workspaceFilesLimit"
|
||||
name="maxWorkspaceFiles"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -232,7 +294,7 @@ export const DefaultParameters = () => {
|
|||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="showRooignoreFiles"
|
||||
name="showRooIgnoredFiles"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
|
|
@ -261,7 +323,7 @@ export const DefaultParameters = () => {
|
|||
<div className="flex items-center gap-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="fileReadThreshold"
|
||||
name="maxReadFileLine"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormControl>
|
||||
|
|
@ -274,27 +336,23 @@ export const DefaultParameters = () => {
|
|||
field.onChange(Number(e.target.value))
|
||||
}
|
||||
className="w-32"
|
||||
disabled={readEntireFile}
|
||||
value={readEntireFile ? '' : field.value}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<span>lines</span>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="alwaysReadEntireFile"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex items-center space-x-2">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Always read entire file</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormItem className="flex items-center space-x-2">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={readEntireFile}
|
||||
onCheckedChange={handleReadEntireFileChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>Always read entire file</FormLabel>
|
||||
</FormItem>
|
||||
</div>
|
||||
<FormDescription>
|
||||
Roo reads this number of lines when the model omits
|
||||
|
|
@ -482,7 +540,7 @@ export const DefaultParameters = () => {
|
|||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="terminalOutputLimit"
|
||||
name="terminalOutputLineLimit"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -512,7 +570,7 @@ export const DefaultParameters = () => {
|
|||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="compressProgressBar"
|
||||
name="terminalCompressProgressBar"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
|
|
@ -570,7 +628,7 @@ export const DefaultParameters = () => {
|
|||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="disableShellIntegration"
|
||||
name="terminalShellIntegrationDisabled"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
|
|
@ -596,7 +654,7 @@ export const DefaultParameters = () => {
|
|||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="shellIntegrationTimeout"
|
||||
name="terminalShellIntegrationTimeout"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -630,7 +688,7 @@ export const DefaultParameters = () => {
|
|||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="commandDelay"
|
||||
name="terminalCommandDelay"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
@ -668,7 +726,7 @@ export const DefaultParameters = () => {
|
|||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="clearZshEol"
|
||||
name="terminalZshClearEolMark"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
|
||||
<FormControl>
|
||||
|
|
@ -746,3 +804,23 @@ export const DefaultParameters = () => {
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultParameters = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: orgSettings } = useQuery({
|
||||
queryKey: ['organizationSettings'],
|
||||
queryFn: getOrganizationSettings,
|
||||
});
|
||||
|
||||
if (!orgSettings) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin text-2xl">⟳</div>
|
||||
<span className="ml-2">Loading settings...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <ParametersForm orgSettings={orgSettings} queryClient={queryClient} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,211 +2,274 @@
|
|||
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useState } from 'react';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import {
|
||||
updateAllowAllProviders,
|
||||
updateModelStatus,
|
||||
updateProviderStatus,
|
||||
} from '@/actions/providerWhitelist';
|
||||
import { Badge, Checkbox, Label } from '@/components/ui';
|
||||
getOrganizationSettings,
|
||||
updateOrganization,
|
||||
} from '@/actions/organizationSettings';
|
||||
import { Badge, Button, Checkbox, Label } from '@/components/ui';
|
||||
import { toast } from 'sonner';
|
||||
import {
|
||||
ORGANIZATION_ALLOW_ALL,
|
||||
type OrganizationAllowList,
|
||||
type OrganizationSettings,
|
||||
} from '@/schemas';
|
||||
import { type ProviderName } from '@roo-code/types';
|
||||
|
||||
const initialProviders = [
|
||||
type ProviderSetting = {
|
||||
allowAll: boolean;
|
||||
models?: string[];
|
||||
};
|
||||
|
||||
// Define a type for providers record
|
||||
type ProvidersRecord = Record<ProviderName, ProviderSetting>;
|
||||
|
||||
// Provider metadata without state information
|
||||
const providerMetadata: {
|
||||
id: ProviderName;
|
||||
name: string;
|
||||
models: {
|
||||
id: string;
|
||||
name: string;
|
||||
}[];
|
||||
}[] = [
|
||||
{
|
||||
id: 'openai',
|
||||
id: 'openai-native',
|
||||
name: 'OpenAI',
|
||||
enabled: true,
|
||||
models: [
|
||||
{ id: 'gpt-4', name: 'GPT-4', enabled: true },
|
||||
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo', enabled: true },
|
||||
{ id: 'gpt-4o', name: 'GPT-4o', enabled: false },
|
||||
{ id: 'gpt-4', name: 'GPT-4' },
|
||||
{ id: 'gpt-3.5-turbo', name: 'GPT-3.5 Turbo' },
|
||||
{ id: 'gpt-4o', name: 'GPT-4o' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'anthropic',
|
||||
name: 'Anthropic',
|
||||
enabled: true,
|
||||
models: [
|
||||
{ id: 'claude-3-opus', name: 'Claude 3 Opus', enabled: true },
|
||||
{ id: 'claude-3-sonnet', name: 'Claude 3 Sonnet', enabled: true },
|
||||
{ id: 'claude-3-haiku', name: 'Claude 3 Haiku', enabled: false },
|
||||
{ id: 'claude-3-opus-20240229', name: 'Claude 3 Opus' },
|
||||
{ id: 'claude-3-7-sonnet-20250219', name: 'Claude 3 Sonnet' },
|
||||
{
|
||||
id: 'claude-3-7-sonnet-20250219:thinking',
|
||||
name: 'Claude 3 Sonnet Thinking',
|
||||
},
|
||||
{ id: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
models: [
|
||||
{ id: 'anthropic/claude-3-opus', name: 'Claude 3 Opus' },
|
||||
{ id: 'anthropic/claude-3.7-sonnet', name: 'Claude 3.7 Sonnet' },
|
||||
{
|
||||
id: 'anthropic/claude-3.7-sonnet:thinking',
|
||||
name: 'Claude 3.7 Sonnet Thinking',
|
||||
},
|
||||
{ id: 'anthropic/claude-3.5-haiku', name: 'Claude 3.5 Haiku' },
|
||||
{ id: 'openai/gpt-4', name: 'GPT-4' },
|
||||
{ id: 'openai/gpt-3.5-turbo', name: 'GPT-3.5 Turbo' },
|
||||
{ id: 'openai/gpt-4o', name: 'GPT-4o' },
|
||||
{
|
||||
id: 'google/gemini-2.5-flash-preview',
|
||||
name: 'Gemini 2.5 Flash Preview',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'mistral',
|
||||
name: 'Mistral AI',
|
||||
enabled: false,
|
||||
models: [
|
||||
{ id: 'mistral-large', name: 'Mistral Large', enabled: false },
|
||||
{ id: 'mistral-medium', name: 'Mistral Medium', enabled: false },
|
||||
{ id: 'mistral-small', name: 'Mistral Small', enabled: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'cohere',
|
||||
name: 'Cohere',
|
||||
enabled: false,
|
||||
models: [
|
||||
{ id: 'command-r', name: 'Command R', enabled: false },
|
||||
{ id: 'command-r-plus', name: 'Command R+', enabled: false },
|
||||
{ id: 'mistral-large-latest', name: 'Mistral Large' },
|
||||
{ id: 'mistral-small-latest', name: 'Mistral Small' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const ProviderWhitelist = () => {
|
||||
type ProviderWhitelistFormProps = {
|
||||
orgSettings: OrganizationSettings;
|
||||
queryClient: ReturnType<typeof useQueryClient>;
|
||||
};
|
||||
|
||||
const ProviderWhitelistForm = ({
|
||||
orgSettings,
|
||||
queryClient,
|
||||
}: ProviderWhitelistFormProps) => {
|
||||
const t = useTranslations('ProviderWhitelist');
|
||||
|
||||
const [providers, setProviders] = useState(initialProviders);
|
||||
const [policyVersion] = useState(1);
|
||||
const [allowAllProviders, setAllowAllProviders] = useState(true);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [allowList, setAllowList] = useState<OrganizationAllowList>(
|
||||
orgSettings?.allowList || ORGANIZATION_ALLOW_ALL,
|
||||
);
|
||||
|
||||
// Common error handling function for API calls.
|
||||
const handleApiError = (error: unknown, errorMessage: string) => {
|
||||
console.error(errorMessage, error);
|
||||
toast.error(errorMessage);
|
||||
setIsUpdating(false);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const isAllowAllChecked = () => allowList.allowAll;
|
||||
|
||||
const isProviderChecked = (providerId: ProviderName) => {
|
||||
if (allowList.allowAll) return true;
|
||||
return allowList.providers[providerId]?.allowAll || false;
|
||||
};
|
||||
|
||||
const toggleAllowAllProviders = async () => {
|
||||
const previousAllowAllProviders = allowAllProviders;
|
||||
const previousProviders = [...providers];
|
||||
const newAllowAllProviders = !allowAllProviders;
|
||||
setAllowAllProviders(newAllowAllProviders);
|
||||
const isModelChecked = (providerId: ProviderName, modelId: string) => {
|
||||
if (allowList.allowAll) return true;
|
||||
const provider = allowList.providers[providerId];
|
||||
if (!provider) return false;
|
||||
if (provider.allowAll) return true;
|
||||
return provider.models?.includes(modelId) || false;
|
||||
};
|
||||
|
||||
let updatedProviders = providers;
|
||||
const toggleAllowAll = () => {
|
||||
const newAllowAll = !allowList.allowAll;
|
||||
|
||||
if (newAllowAllProviders) {
|
||||
// Enable all providers and their models/
|
||||
updatedProviders = providers.map((provider) => ({
|
||||
...provider,
|
||||
enabled: true,
|
||||
models: provider.models.map((model) => ({ ...model, enabled: true })),
|
||||
}));
|
||||
if (newAllowAll) {
|
||||
setAllowList({ allowAll: true, providers: {} });
|
||||
} else {
|
||||
const newProviders: Partial<ProvidersRecord> = {};
|
||||
|
||||
setProviders(updatedProviders);
|
||||
}
|
||||
|
||||
// Call API to update backend with the changed allow all providers setting.
|
||||
setIsUpdating(true);
|
||||
|
||||
try {
|
||||
const result = await updateAllowAllProviders({
|
||||
allowAllProviders: newAllowAllProviders,
|
||||
policyVersion,
|
||||
providerMetadata.forEach((provider) => {
|
||||
newProviders[provider.id] = { allowAll: true };
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to update setting');
|
||||
}
|
||||
} catch (error) {
|
||||
setAllowAllProviders(previousAllowAllProviders);
|
||||
setProviders(previousProviders);
|
||||
handleApiError(
|
||||
error,
|
||||
'Failed to update allow all providers setting. Please try again.',
|
||||
);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
setAllowList({
|
||||
allowAll: false,
|
||||
providers: newProviders,
|
||||
});
|
||||
}
|
||||
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const toggleProvider = async (providerId: string) => {
|
||||
if (allowAllProviders) {
|
||||
return;
|
||||
const toggleProvider = (providerId: ProviderName) => {
|
||||
if (allowList.allowAll) {
|
||||
const newProviders: Partial<ProvidersRecord> = {};
|
||||
|
||||
providerMetadata.forEach((provider) => {
|
||||
if (provider.id !== providerId) {
|
||||
newProviders[provider.id] = { allowAll: true };
|
||||
}
|
||||
});
|
||||
|
||||
setAllowList({
|
||||
allowAll: false,
|
||||
providers: newProviders,
|
||||
});
|
||||
} else {
|
||||
const newProviders = { ...allowList.providers };
|
||||
const providersRecord = newProviders;
|
||||
const isCurrentlyEnabled = isProviderChecked(providerId);
|
||||
|
||||
if (isCurrentlyEnabled) {
|
||||
if (providersRecord[providerId]) {
|
||||
providersRecord[providerId] = {
|
||||
...providersRecord[providerId],
|
||||
allowAll: false,
|
||||
models: [],
|
||||
};
|
||||
}
|
||||
} else {
|
||||
providersRecord[providerId] = { allowAll: true };
|
||||
}
|
||||
|
||||
setAllowList({ ...allowList, providers: newProviders });
|
||||
}
|
||||
|
||||
// Find the provider to get its current state.
|
||||
const provider = providers.find((p) => p.id === providerId);
|
||||
if (!provider) return;
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const previousProviders = [...providers];
|
||||
const newEnabled = !provider.enabled;
|
||||
const toggleModel = (providerId: ProviderName, modelId: string) => {
|
||||
if (allowList.allowAll || isProviderChecked(providerId)) {
|
||||
const newProviders = { ...allowList.providers };
|
||||
const providersRecord = newProviders;
|
||||
|
||||
const updatedProviders = providers.map((provider) => {
|
||||
if (provider.id === providerId) {
|
||||
return {
|
||||
if (allowList.allowAll) {
|
||||
providerMetadata.forEach((provider) => {
|
||||
providersRecord[provider.id] = { allowAll: true };
|
||||
});
|
||||
|
||||
const provider = providerMetadata.find((p) => p.id === providerId);
|
||||
if (provider) {
|
||||
const models = provider.models
|
||||
.filter((m) => m.id !== modelId)
|
||||
.map((m) => m.id);
|
||||
|
||||
providersRecord[providerId] = { allowAll: false, models };
|
||||
}
|
||||
|
||||
setAllowList({ allowAll: false, providers: newProviders });
|
||||
} else {
|
||||
const provider = providerMetadata.find((p) => p.id === providerId);
|
||||
if (provider) {
|
||||
const models = provider.models
|
||||
.filter((m) => m.id !== modelId)
|
||||
.map((m) => m.id);
|
||||
|
||||
providersRecord[providerId] = { allowAll: false, models };
|
||||
setAllowList({ ...allowList, providers: newProviders });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const newProviders = { ...allowList.providers };
|
||||
const providersRecord = newProviders;
|
||||
const provider = providersRecord[providerId] || {
|
||||
allowAll: false,
|
||||
models: [],
|
||||
};
|
||||
const models = provider.models || [];
|
||||
|
||||
const isCurrentlyEnabled = models.includes(modelId);
|
||||
|
||||
if (isCurrentlyEnabled) {
|
||||
providersRecord[providerId] = {
|
||||
...provider,
|
||||
enabled: newEnabled,
|
||||
// If provider is disabled, disable all its models
|
||||
models: provider.models.map((model) => ({
|
||||
...model,
|
||||
enabled: newEnabled ? model.enabled : false,
|
||||
})),
|
||||
models: models.filter((m: string) => m !== modelId),
|
||||
};
|
||||
} else {
|
||||
providersRecord[providerId] = {
|
||||
...provider,
|
||||
models: [...models, modelId],
|
||||
};
|
||||
}
|
||||
return provider;
|
||||
});
|
||||
|
||||
setProviders(updatedProviders);
|
||||
const allModels =
|
||||
providerMetadata
|
||||
.find((p) => p.id === providerId)
|
||||
?.models.map((m) => m.id) || [];
|
||||
const enabledModels = providersRecord[providerId].models || [];
|
||||
|
||||
// Call API to update backend with the changed provider status
|
||||
setIsUpdating(true);
|
||||
try {
|
||||
const result = await updateProviderStatus({
|
||||
providerId,
|
||||
enabled: newEnabled,
|
||||
policyVersion,
|
||||
});
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to update provider status');
|
||||
if (
|
||||
allModels.length === enabledModels.length &&
|
||||
allModels.every((m) => enabledModels.includes(m))
|
||||
) {
|
||||
providersRecord[providerId] = { allowAll: true };
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
setProviders(previousProviders);
|
||||
handleApiError(
|
||||
error,
|
||||
'Failed to update provider status. Please try again.',
|
||||
);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
|
||||
setAllowList({ ...allowList, providers: newProviders });
|
||||
}
|
||||
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
const toggleModel = async (providerId: string, modelId: string) => {
|
||||
if (allowAllProviders) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the model to get its current state.
|
||||
const provider = providers.find((p) => p.id === providerId);
|
||||
if (!provider) return;
|
||||
|
||||
const model = provider.models.find((m) => m.id === modelId);
|
||||
if (!model) return;
|
||||
|
||||
const previousProviders = [...providers];
|
||||
const newEnabled = !model.enabled;
|
||||
|
||||
const updatedProviders = providers.map((provider) => {
|
||||
return provider.id === providerId
|
||||
? {
|
||||
...provider,
|
||||
models: provider.models.map((model) =>
|
||||
model.id === modelId ? { ...model, enabled: newEnabled } : model,
|
||||
),
|
||||
}
|
||||
: provider;
|
||||
});
|
||||
|
||||
setProviders(updatedProviders);
|
||||
setIsUpdating(true);
|
||||
|
||||
const saveChanges = async () => {
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const result = await updateModelStatus({
|
||||
providerId,
|
||||
modelId,
|
||||
enabled: newEnabled,
|
||||
policyVersion,
|
||||
const result = await updateOrganization({
|
||||
allowList: allowList,
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to update model status');
|
||||
throw new Error(result.error || 'Failed to update settings');
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['organizationSettings'] });
|
||||
|
||||
toast.success('Provider settings saved successfully');
|
||||
setHasChanges(false);
|
||||
} catch (error) {
|
||||
setProviders(previousProviders);
|
||||
handleApiError(error, 'Failed to update model status. Please try again.');
|
||||
console.error('Failed to save provider settings', error);
|
||||
toast.error('Failed to save provider settings');
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -227,8 +290,9 @@ export const ProviderWhitelist = () => {
|
|||
<div className="mb-4 flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="allow-all-providers"
|
||||
checked={allowAllProviders}
|
||||
onCheckedChange={toggleAllowAllProviders}
|
||||
checked={isAllowAllChecked()}
|
||||
onCheckedChange={toggleAllowAll}
|
||||
disabled={isSaving}
|
||||
/>
|
||||
<Label
|
||||
htmlFor="allow-all-providers"
|
||||
|
|
@ -237,14 +301,14 @@ export const ProviderWhitelist = () => {
|
|||
Allow All Providers
|
||||
</Label>
|
||||
</div>
|
||||
<div className={allowAllProviders ? 'opacity-50' : ''}>
|
||||
{providers.map((provider) => (
|
||||
<div className={isAllowAllChecked() ? 'opacity-50' : ''}>
|
||||
{providerMetadata.map((provider) => (
|
||||
<div key={provider.id} className="mb-4">
|
||||
<div className="mb-2 flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`provider-${provider.id}`}
|
||||
checked={provider.enabled}
|
||||
disabled={allowAllProviders}
|
||||
checked={isProviderChecked(provider.id)}
|
||||
disabled={isAllowAllChecked() || isSaving}
|
||||
onCheckedChange={() => toggleProvider(provider.id)}
|
||||
/>
|
||||
<Label
|
||||
|
|
@ -259,15 +323,19 @@ export const ProviderWhitelist = () => {
|
|||
<div key={model.id} className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id={`model-${model.id}`}
|
||||
checked={allowAllProviders || model.enabled}
|
||||
disabled={allowAllProviders || !provider.enabled}
|
||||
checked={isModelChecked(provider.id, model.id)}
|
||||
disabled={
|
||||
isAllowAllChecked() ||
|
||||
isProviderChecked(provider.id) ||
|
||||
isSaving
|
||||
}
|
||||
onCheckedChange={() =>
|
||||
toggleModel(provider.id, model.id)
|
||||
}
|
||||
/>
|
||||
<Label
|
||||
htmlFor={`model-${model.id}`}
|
||||
className={`text-xs ${allowAllProviders || !provider.enabled ? 'text-muted-foreground' : ''}`}
|
||||
className={`text-xs ${isAllowAllChecked() || !isProviderChecked(provider.id) ? 'text-muted-foreground' : ''}`}
|
||||
>
|
||||
{model.name}
|
||||
</Label>
|
||||
|
|
@ -279,16 +347,58 @@ export const ProviderWhitelist = () => {
|
|||
</div>
|
||||
<div className="flex items-center space-x-2 pt-2">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{`Policy v${policyVersion}`}
|
||||
{`Policy v${orgSettings?.version || 1}`}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{isUpdating
|
||||
{isSaving
|
||||
? 'Updating provider whitelist...'
|
||||
: 'Changes will be pushed to SSE stream within 30 seconds'}
|
||||
: hasChanges
|
||||
? 'You have unsaved changes'
|
||||
: 'Changes will be pushed to SSE stream within 30 seconds'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
onClick={saveChanges}
|
||||
disabled={!hasChanges || isSaving}
|
||||
className="mt-4"
|
||||
>
|
||||
{isSaving ? (
|
||||
<>
|
||||
<span className="mr-2">Saving...</span>
|
||||
<span className="animate-spin">⟳</span>
|
||||
</>
|
||||
) : (
|
||||
'Save Changes'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ProviderWhitelist = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { data: orgSettings } = useQuery({
|
||||
queryKey: ['organizationSettings'],
|
||||
queryFn: getOrganizationSettings,
|
||||
});
|
||||
|
||||
if (!orgSettings) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin text-2xl">⟳</div>
|
||||
<span className="ml-2">Loading settings...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ProviderWhitelistForm
|
||||
orgSettings={orgSettings}
|
||||
queryClient={queryClient}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -26,4 +26,8 @@ const disconnect = async () => {
|
|||
await pool.end();
|
||||
};
|
||||
|
||||
export type DB_OR_TX =
|
||||
| typeof db
|
||||
| Parameters<Parameters<typeof db.transaction>[0]>[0];
|
||||
|
||||
export { db, testDb, disconnect };
|
||||
|
|
|
|||
|
|
@ -1,12 +1,22 @@
|
|||
import { db } from '@/db';
|
||||
import { db, type DB_OR_TX } from '@/db';
|
||||
import { auditLogs, type CreateAuditLog } from '@/db/schema';
|
||||
import { logger } from '@/lib/server/logger';
|
||||
|
||||
export async function createAuditLog(values: CreateAuditLog) {
|
||||
export async function insertAuditLog(
|
||||
db: DB_OR_TX,
|
||||
values: CreateAuditLog,
|
||||
): Promise<void> {
|
||||
await db.insert(auditLogs).values(values);
|
||||
const { userId, orgId, targetType } = values;
|
||||
logger.info({ userId, orgId, targetType });
|
||||
}
|
||||
|
||||
export async function createAuditLog(values: CreateAuditLog): Promise<{
|
||||
success: boolean;
|
||||
error?: string | Record<string, unknown>;
|
||||
}> {
|
||||
try {
|
||||
await db.insert(auditLogs).values(values);
|
||||
const { userId, orgId, targetType } = values;
|
||||
logger.info({ userId, orgId, targetType });
|
||||
await insertAuditLog(db, values);
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
const error =
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
* TimePeriod
|
||||
*/
|
||||
|
||||
import { providerNames } from '@roo-code/types';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const timePeriods = [7, 30, 90] as const;
|
||||
|
|
@ -11,6 +12,7 @@ export type TimePeriod = (typeof timePeriods)[number];
|
|||
export const organizationAllowListSchema = z.object({
|
||||
allowAll: z.boolean(),
|
||||
providers: z.record(
|
||||
z.enum(providerNames),
|
||||
z.object({
|
||||
allowAll: z.boolean(),
|
||||
models: z.array(z.string()).optional(),
|
||||
|
|
@ -29,11 +31,17 @@ export const ORGANIZATION_ALLOW_ALL: OrganizationAllowList = {
|
|||
|
||||
export const organizationDefaultSettingsSchema = z.object({
|
||||
enableCheckpoints: z.boolean().optional(),
|
||||
maxOpenTabsContext: z.number().optional(),
|
||||
maxWorkspaceFiles: z.number().optional(),
|
||||
showRooIgnoredFiles: z.boolean().optional(),
|
||||
maxReadFileLine: z.number().optional(),
|
||||
fuzzyMatchThreshold: z.number().optional(),
|
||||
maxOpenTabsContext: z.number().int().nonnegative().optional(),
|
||||
maxReadFileLine: z.number().int().gte(-1).optional(),
|
||||
maxWorkspaceFiles: z.number().int().nonnegative().optional(),
|
||||
showRooIgnoredFiles: z.boolean().optional(),
|
||||
terminalCommandDelay: z.number().int().nonnegative().optional(),
|
||||
terminalCompressProgressBar: z.boolean().optional(),
|
||||
terminalOutputLineLimit: z.number().int().nonnegative().optional(),
|
||||
terminalShellIntegrationDisabled: z.boolean().optional(),
|
||||
terminalShellIntegrationTimeout: z.number().int().nonnegative().optional(),
|
||||
terminalZshClearEolMark: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type OrganizationDefaultSettings = z.infer<
|
||||
|
|
@ -47,3 +55,9 @@ export const organizationSettingsSchema = z.object({
|
|||
});
|
||||
|
||||
export type OrganizationSettings = z.infer<typeof organizationSettingsSchema>;
|
||||
|
||||
export const ORGANIZATION_DEFAULT: OrganizationSettings = {
|
||||
version: 0,
|
||||
defaultSettings: {},
|
||||
allowList: ORGANIZATION_ALLOW_ALL,
|
||||
} as const;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue