mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-10 06:35:01 -05:00
Compare commits
7 Commits
v0.5.83
...
cursor/dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9694dbd4f9 | ||
|
|
8361b74d7e | ||
|
|
654cb2b407 | ||
|
|
c74922997c | ||
|
|
4193007ab7 | ||
|
|
6c66521d64 | ||
|
|
f9b885f6d5 |
45
.cursor/CLOUD.md
Normal file
45
.cursor/CLOUD.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Sim Studio - Cloud Agent Development Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Sim Studio is a monorepo for building and deploying AI agent workflows. See `CLAUDE.md` and `README.md` for architecture, coding standards, and integration guidelines.
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
| Service | Command | Port | Notes |
|
||||||
|
|---------|---------|------|-------|
|
||||||
|
| Next.js app | `bun run dev` (from root) | 3000 | Main application |
|
||||||
|
| Socket.IO realtime | `bun run dev:sockets` (from `apps/sim`) | 3002 | Collaborative editing |
|
||||||
|
| Both together | `bun run dev:full` (from `apps/sim` or root) | 3000, 3002 | Recommended for development |
|
||||||
|
| Docs | `bun run dev` (from `apps/docs`) | 3001 | Optional documentation site |
|
||||||
|
| PostgreSQL + pgvector | Docker container | 5432 | Required database |
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
- **Lint**: `bun run lint:check` (biome check, no auto-fix) or `bun run lint` (with auto-fix)
|
||||||
|
- **Format**: `bun run format:check` or `bun run format` (with auto-fix)
|
||||||
|
- **Test**: `bun run test` (runs vitest via turbo across packages)
|
||||||
|
- **Type-check**: `bun run type-check` (runs tsc --noEmit via turbo)
|
||||||
|
- **Dev**: `bun run dev:full` from root or `apps/sim` (starts Next.js + Socket.IO)
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
- PostgreSQL with pgvector extension, connection via `DATABASE_URL` in `.env` files
|
||||||
|
- Migrations: `cd packages/db && bunx drizzle-kit migrate --config=./drizzle.config.ts`
|
||||||
|
- Schema push (no migration files): `cd packages/db && bunx drizzle-kit push --config=./drizzle.config.ts`
|
||||||
|
- Two `.env` files are needed: `apps/sim/.env` and `packages/db/.env` (both need `DATABASE_URL`)
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Framework: Vitest (config at `apps/sim/vitest.config.ts`)
|
||||||
|
- Use `@sim/testing` mocks/factories (see `.cursor/rules/sim-testing.mdc`)
|
||||||
|
- Tests run in parallel with thread pool
|
||||||
|
- Run specific test: `cd apps/sim && bunx vitest run path/to/test.test.ts`
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- Package manager is **bun** (not npm/pnpm). Use `bun` and `bunx` commands.
|
||||||
|
- The docs app requires `fumadocs-mdx` generation before type-check: `cd apps/docs && bunx fumadocs-mdx`
|
||||||
|
- Pre-commit hook runs `bunx lint-staged` which applies biome check with auto-fix
|
||||||
|
- Social provider warnings (GitHub/Google missing clientId) during startup are expected in local dev
|
||||||
|
- Redis warnings ("REDIS_URL not configured") are expected - app runs in single-pod mode locally
|
||||||
@@ -28,7 +28,6 @@ interface ApiDeployProps {
|
|||||||
deploymentInfo: WorkflowDeploymentInfo | null
|
deploymentInfo: WorkflowDeploymentInfo | null
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
needsRedeployment: boolean
|
needsRedeployment: boolean
|
||||||
apiDeployError: string | null
|
|
||||||
getInputFormatExample: (includeStreaming?: boolean) => string
|
getInputFormatExample: (includeStreaming?: boolean) => string
|
||||||
selectedStreamingOutputs: string[]
|
selectedStreamingOutputs: string[]
|
||||||
onSelectedStreamingOutputsChange: (outputs: string[]) => void
|
onSelectedStreamingOutputsChange: (outputs: string[]) => void
|
||||||
@@ -63,7 +62,6 @@ export function ApiDeploy({
|
|||||||
deploymentInfo,
|
deploymentInfo,
|
||||||
isLoading,
|
isLoading,
|
||||||
needsRedeployment,
|
needsRedeployment,
|
||||||
apiDeployError,
|
|
||||||
getInputFormatExample,
|
getInputFormatExample,
|
||||||
selectedStreamingOutputs,
|
selectedStreamingOutputs,
|
||||||
onSelectedStreamingOutputsChange,
|
onSelectedStreamingOutputsChange,
|
||||||
@@ -419,12 +417,6 @@ console.log(limits);`
|
|||||||
if (isLoading || !info) {
|
if (isLoading || !info) {
|
||||||
return (
|
return (
|
||||||
<div className='space-y-[16px]'>
|
<div className='space-y-[16px]'>
|
||||||
{apiDeployError && (
|
|
||||||
<div className='rounded-[4px] border border-destructive/30 bg-destructive/10 p-3 text-destructive text-sm'>
|
|
||||||
<div className='font-semibold'>API Deployment Error</div>
|
|
||||||
<div>{apiDeployError}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
<div>
|
||||||
<Skeleton className='mb-[6.5px] h-[16px] w-[62px]' />
|
<Skeleton className='mb-[6.5px] h-[16px] w-[62px]' />
|
||||||
<Skeleton className='h-[28px] w-[260px] rounded-[4px]' />
|
<Skeleton className='h-[28px] w-[260px] rounded-[4px]' />
|
||||||
@@ -443,13 +435,6 @@ console.log(limits);`
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='space-y-[16px]'>
|
<div className='space-y-[16px]'>
|
||||||
{apiDeployError && (
|
|
||||||
<div className='rounded-[4px] border border-destructive/30 bg-destructive/10 p-3 text-destructive text-sm'>
|
|
||||||
<div className='font-semibold'>API Deployment Error</div>
|
|
||||||
<div>{apiDeployError}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className='mb-[6.5px] flex items-center justify-between'>
|
<div className='mb-[6.5px] flex items-center justify-between'>
|
||||||
<Label className='block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]'>
|
<Label className='block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]'>
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ export function DeployModal({
|
|||||||
const workflowWorkspaceId = workflowMetadata?.workspaceId ?? null
|
const workflowWorkspaceId = workflowMetadata?.workspaceId ?? null
|
||||||
const [activeTab, setActiveTab] = useState<TabView>('general')
|
const [activeTab, setActiveTab] = useState<TabView>('general')
|
||||||
const [chatSubmitting, setChatSubmitting] = useState(false)
|
const [chatSubmitting, setChatSubmitting] = useState(false)
|
||||||
const [apiDeployError, setApiDeployError] = useState<string | null>(null)
|
const [deployError, setDeployError] = useState<string | null>(null)
|
||||||
const [apiDeployWarnings, setApiDeployWarnings] = useState<string[]>([])
|
const [deployWarnings, setDeployWarnings] = useState<string[]>([])
|
||||||
const [isChatFormValid, setIsChatFormValid] = useState(false)
|
const [isChatFormValid, setIsChatFormValid] = useState(false)
|
||||||
const [selectedStreamingOutputs, setSelectedStreamingOutputs] = useState<string[]>([])
|
const [selectedStreamingOutputs, setSelectedStreamingOutputs] = useState<string[]>([])
|
||||||
|
|
||||||
@@ -225,8 +225,8 @@ export function DeployModal({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && workflowId) {
|
if (open && workflowId) {
|
||||||
setActiveTab('general')
|
setActiveTab('general')
|
||||||
setApiDeployError(null)
|
setDeployError(null)
|
||||||
setApiDeployWarnings([])
|
setDeployWarnings([])
|
||||||
}
|
}
|
||||||
}, [open, workflowId])
|
}, [open, workflowId])
|
||||||
|
|
||||||
@@ -281,19 +281,19 @@ export function DeployModal({
|
|||||||
const onDeploy = useCallback(async () => {
|
const onDeploy = useCallback(async () => {
|
||||||
if (!workflowId) return
|
if (!workflowId) return
|
||||||
|
|
||||||
setApiDeployError(null)
|
setDeployError(null)
|
||||||
setApiDeployWarnings([])
|
setDeployWarnings([])
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await deployMutation.mutateAsync({ workflowId, deployChatEnabled: false })
|
const result = await deployMutation.mutateAsync({ workflowId, deployChatEnabled: false })
|
||||||
if (result.warnings && result.warnings.length > 0) {
|
if (result.warnings && result.warnings.length > 0) {
|
||||||
setApiDeployWarnings(result.warnings)
|
setDeployWarnings(result.warnings)
|
||||||
}
|
}
|
||||||
await refetchDeployedState()
|
await refetchDeployedState()
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('Error deploying workflow:', { error })
|
logger.error('Error deploying workflow:', { error })
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Failed to deploy workflow'
|
const errorMessage = error instanceof Error ? error.message : 'Failed to deploy workflow'
|
||||||
setApiDeployError(errorMessage)
|
setDeployError(errorMessage)
|
||||||
}
|
}
|
||||||
}, [workflowId, deployMutation, refetchDeployedState])
|
}, [workflowId, deployMutation, refetchDeployedState])
|
||||||
|
|
||||||
@@ -301,12 +301,12 @@ export function DeployModal({
|
|||||||
async (version: number) => {
|
async (version: number) => {
|
||||||
if (!workflowId) return
|
if (!workflowId) return
|
||||||
|
|
||||||
setApiDeployWarnings([])
|
setDeployWarnings([])
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await activateVersionMutation.mutateAsync({ workflowId, version })
|
const result = await activateVersionMutation.mutateAsync({ workflowId, version })
|
||||||
if (result.warnings && result.warnings.length > 0) {
|
if (result.warnings && result.warnings.length > 0) {
|
||||||
setApiDeployWarnings(result.warnings)
|
setDeployWarnings(result.warnings)
|
||||||
}
|
}
|
||||||
await refetchDeployedState()
|
await refetchDeployedState()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -332,26 +332,26 @@ export function DeployModal({
|
|||||||
const handleRedeploy = useCallback(async () => {
|
const handleRedeploy = useCallback(async () => {
|
||||||
if (!workflowId) return
|
if (!workflowId) return
|
||||||
|
|
||||||
setApiDeployError(null)
|
setDeployError(null)
|
||||||
setApiDeployWarnings([])
|
setDeployWarnings([])
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await deployMutation.mutateAsync({ workflowId, deployChatEnabled: false })
|
const result = await deployMutation.mutateAsync({ workflowId, deployChatEnabled: false })
|
||||||
if (result.warnings && result.warnings.length > 0) {
|
if (result.warnings && result.warnings.length > 0) {
|
||||||
setApiDeployWarnings(result.warnings)
|
setDeployWarnings(result.warnings)
|
||||||
}
|
}
|
||||||
await refetchDeployedState()
|
await refetchDeployedState()
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error('Error redeploying workflow:', { error })
|
logger.error('Error redeploying workflow:', { error })
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Failed to redeploy workflow'
|
const errorMessage = error instanceof Error ? error.message : 'Failed to redeploy workflow'
|
||||||
setApiDeployError(errorMessage)
|
setDeployError(errorMessage)
|
||||||
}
|
}
|
||||||
}, [workflowId, deployMutation, refetchDeployedState])
|
}, [workflowId, deployMutation, refetchDeployedState])
|
||||||
|
|
||||||
const handleCloseModal = useCallback(() => {
|
const handleCloseModal = useCallback(() => {
|
||||||
setChatSubmitting(false)
|
setChatSubmitting(false)
|
||||||
setApiDeployError(null)
|
setDeployError(null)
|
||||||
setApiDeployWarnings([])
|
setDeployWarnings([])
|
||||||
onOpenChange(false)
|
onOpenChange(false)
|
||||||
}, [onOpenChange])
|
}, [onOpenChange])
|
||||||
|
|
||||||
@@ -483,17 +483,23 @@ export function DeployModal({
|
|||||||
</ModalTabsList>
|
</ModalTabsList>
|
||||||
|
|
||||||
<ModalBody className='min-h-0 flex-1'>
|
<ModalBody className='min-h-0 flex-1'>
|
||||||
{apiDeployError && (
|
{(deployError || deployWarnings.length > 0) && (
|
||||||
<div className='mb-3 rounded-[4px] border border-destructive/30 bg-destructive/10 p-3 text-destructive text-sm'>
|
<div className='mb-3 flex flex-col gap-2'>
|
||||||
<div className='font-semibold'>Deployment Error</div>
|
{deployError && (
|
||||||
<div>{apiDeployError}</div>
|
<Badge variant='red' size='lg' dot className='max-w-full truncate'>
|
||||||
</div>
|
{deployError}
|
||||||
)}
|
</Badge>
|
||||||
{apiDeployWarnings.length > 0 && (
|
)}
|
||||||
<div className='mb-3 rounded-[4px] border border-amber-500/30 bg-amber-500/10 p-3 text-amber-700 text-sm dark:text-amber-400'>
|
{deployWarnings.map((warning, index) => (
|
||||||
<div className='font-semibold'>Deployment Warning</div>
|
<Badge
|
||||||
{apiDeployWarnings.map((warning, index) => (
|
key={index}
|
||||||
<div key={index}>{warning}</div>
|
variant='amber'
|
||||||
|
size='lg'
|
||||||
|
dot
|
||||||
|
className='max-w-full truncate'
|
||||||
|
>
|
||||||
|
{warning}
|
||||||
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -515,7 +521,6 @@ export function DeployModal({
|
|||||||
deploymentInfo={deploymentInfo}
|
deploymentInfo={deploymentInfo}
|
||||||
isLoading={isLoadingDeploymentInfo}
|
isLoading={isLoadingDeploymentInfo}
|
||||||
needsRedeployment={needsRedeployment}
|
needsRedeployment={needsRedeployment}
|
||||||
apiDeployError={apiDeployError}
|
|
||||||
getInputFormatExample={getInputFormatExample}
|
getInputFormatExample={getInputFormatExample}
|
||||||
selectedStreamingOutputs={selectedStreamingOutputs}
|
selectedStreamingOutputs={selectedStreamingOutputs}
|
||||||
onSelectedStreamingOutputsChange={setSelectedStreamingOutputs}
|
onSelectedStreamingOutputsChange={setSelectedStreamingOutputs}
|
||||||
|
|||||||
@@ -1151,7 +1151,7 @@ export const Terminal = memo(function Terminal() {
|
|||||||
<aside
|
<aside
|
||||||
ref={terminalRef}
|
ref={terminalRef}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'terminal-container fixed right-[var(--panel-width)] bottom-0 left-[var(--sidebar-width)] z-10 overflow-hidden bg-[var(--surface-1)]',
|
'terminal-container fixed right-[var(--panel-width)] bottom-0 left-[var(--sidebar-width)] z-10 overflow-hidden border-[var(--border)] border-t bg-[var(--surface-1)]',
|
||||||
isToggling && 'transition-[height] duration-100 ease-out'
|
isToggling && 'transition-[height] duration-100 ease-out'
|
||||||
)}
|
)}
|
||||||
onTransitionEnd={handleTransitionEnd}
|
onTransitionEnd={handleTransitionEnd}
|
||||||
@@ -1160,7 +1160,7 @@ export const Terminal = memo(function Terminal() {
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
aria-label='Terminal'
|
aria-label='Terminal'
|
||||||
>
|
>
|
||||||
<div className='relative flex h-full border-[var(--border)] border-t'>
|
<div className='relative flex h-full'>
|
||||||
{/* Left Section - Logs */}
|
{/* Left Section - Logs */}
|
||||||
<div
|
<div
|
||||||
className={clsx('flex flex-col', !selectedEntry && 'flex-1')}
|
className={clsx('flex flex-col', !selectedEntry && 'flex-1')}
|
||||||
|
|||||||
@@ -111,6 +111,16 @@ function isFieldRequired(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveTriggerId(block: BlockState): string | undefined {
|
function resolveTriggerId(block: BlockState): string | undefined {
|
||||||
|
const blockConfig = getBlock(block.type)
|
||||||
|
|
||||||
|
if (blockConfig?.category === 'triggers' && isTriggerValid(block.type)) {
|
||||||
|
return block.type
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!block.triggerMode) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
const selectedTriggerId = getSubBlockValue(block, 'selectedTriggerId')
|
const selectedTriggerId = getSubBlockValue(block, 'selectedTriggerId')
|
||||||
if (typeof selectedTriggerId === 'string' && isTriggerValid(selectedTriggerId)) {
|
if (typeof selectedTriggerId === 'string' && isTriggerValid(selectedTriggerId)) {
|
||||||
return selectedTriggerId
|
return selectedTriggerId
|
||||||
@@ -121,12 +131,7 @@ function resolveTriggerId(block: BlockState): string | undefined {
|
|||||||
return storedTriggerId
|
return storedTriggerId
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockConfig = getBlock(block.type)
|
if (blockConfig?.triggers?.enabled) {
|
||||||
if (blockConfig?.category === 'triggers' && isTriggerValid(block.type)) {
|
|
||||||
return block.type
|
|
||||||
}
|
|
||||||
|
|
||||||
if (block.triggerMode && blockConfig?.triggers?.enabled) {
|
|
||||||
const configuredTriggerId =
|
const configuredTriggerId =
|
||||||
typeof selectedTriggerId === 'string' ? selectedTriggerId : undefined
|
typeof selectedTriggerId === 'string' ? selectedTriggerId : undefined
|
||||||
if (configuredTriggerId && isTriggerValid(configuredTriggerId)) {
|
if (configuredTriggerId && isTriggerValid(configuredTriggerId)) {
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import {
|
|||||||
calculateCost,
|
calculateCost,
|
||||||
generateStructuredOutputInstructions,
|
generateStructuredOutputInstructions,
|
||||||
shouldBillModelUsage,
|
shouldBillModelUsage,
|
||||||
|
supportsReasoningEffort,
|
||||||
supportsTemperature,
|
supportsTemperature,
|
||||||
|
supportsThinking,
|
||||||
|
supportsVerbosity,
|
||||||
} from '@/providers/utils'
|
} from '@/providers/utils'
|
||||||
|
|
||||||
const logger = createLogger('Providers')
|
const logger = createLogger('Providers')
|
||||||
@@ -21,11 +24,24 @@ export const MAX_TOOL_ITERATIONS = 20
|
|||||||
|
|
||||||
function sanitizeRequest(request: ProviderRequest): ProviderRequest {
|
function sanitizeRequest(request: ProviderRequest): ProviderRequest {
|
||||||
const sanitizedRequest = { ...request }
|
const sanitizedRequest = { ...request }
|
||||||
|
const model = sanitizedRequest.model
|
||||||
|
|
||||||
if (sanitizedRequest.model && !supportsTemperature(sanitizedRequest.model)) {
|
if (model && !supportsTemperature(model)) {
|
||||||
sanitizedRequest.temperature = undefined
|
sanitizedRequest.temperature = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (model && !supportsReasoningEffort(model)) {
|
||||||
|
sanitizedRequest.reasoningEffort = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model && !supportsVerbosity(model)) {
|
||||||
|
sanitizedRequest.verbosity = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model && !supportsThinking(model)) {
|
||||||
|
sanitizedRequest.thinkingLevel = undefined
|
||||||
|
}
|
||||||
|
|
||||||
return sanitizedRequest
|
return sanitizedRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,11 @@ import {
|
|||||||
prepareToolExecution,
|
prepareToolExecution,
|
||||||
prepareToolsWithUsageControl,
|
prepareToolsWithUsageControl,
|
||||||
shouldBillModelUsage,
|
shouldBillModelUsage,
|
||||||
|
supportsReasoningEffort,
|
||||||
supportsTemperature,
|
supportsTemperature,
|
||||||
|
supportsThinking,
|
||||||
supportsToolUsageControl,
|
supportsToolUsageControl,
|
||||||
|
supportsVerbosity,
|
||||||
updateOllamaProviderModels,
|
updateOllamaProviderModels,
|
||||||
} from '@/providers/utils'
|
} from '@/providers/utils'
|
||||||
|
|
||||||
@@ -333,6 +336,82 @@ describe('Model Capabilities', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('supportsReasoningEffort', () => {
|
||||||
|
it.concurrent('should return true for models with reasoning effort capability', () => {
|
||||||
|
expect(supportsReasoningEffort('gpt-5')).toBe(true)
|
||||||
|
expect(supportsReasoningEffort('gpt-5-mini')).toBe(true)
|
||||||
|
expect(supportsReasoningEffort('gpt-5.1')).toBe(true)
|
||||||
|
expect(supportsReasoningEffort('gpt-5.2')).toBe(true)
|
||||||
|
expect(supportsReasoningEffort('o3')).toBe(true)
|
||||||
|
expect(supportsReasoningEffort('o4-mini')).toBe(true)
|
||||||
|
expect(supportsReasoningEffort('azure/gpt-5')).toBe(true)
|
||||||
|
expect(supportsReasoningEffort('azure/o3')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.concurrent('should return false for models without reasoning effort capability', () => {
|
||||||
|
expect(supportsReasoningEffort('gpt-4o')).toBe(false)
|
||||||
|
expect(supportsReasoningEffort('gpt-4.1')).toBe(false)
|
||||||
|
expect(supportsReasoningEffort('claude-sonnet-4-5')).toBe(false)
|
||||||
|
expect(supportsReasoningEffort('claude-opus-4-6')).toBe(false)
|
||||||
|
expect(supportsReasoningEffort('gemini-2.5-flash')).toBe(false)
|
||||||
|
expect(supportsReasoningEffort('unknown-model')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.concurrent('should be case-insensitive', () => {
|
||||||
|
expect(supportsReasoningEffort('GPT-5')).toBe(true)
|
||||||
|
expect(supportsReasoningEffort('O3')).toBe(true)
|
||||||
|
expect(supportsReasoningEffort('GPT-4O')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('supportsVerbosity', () => {
|
||||||
|
it.concurrent('should return true for models with verbosity capability', () => {
|
||||||
|
expect(supportsVerbosity('gpt-5')).toBe(true)
|
||||||
|
expect(supportsVerbosity('gpt-5-mini')).toBe(true)
|
||||||
|
expect(supportsVerbosity('gpt-5.1')).toBe(true)
|
||||||
|
expect(supportsVerbosity('gpt-5.2')).toBe(true)
|
||||||
|
expect(supportsVerbosity('azure/gpt-5')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.concurrent('should return false for models without verbosity capability', () => {
|
||||||
|
expect(supportsVerbosity('gpt-4o')).toBe(false)
|
||||||
|
expect(supportsVerbosity('o3')).toBe(false)
|
||||||
|
expect(supportsVerbosity('o4-mini')).toBe(false)
|
||||||
|
expect(supportsVerbosity('claude-sonnet-4-5')).toBe(false)
|
||||||
|
expect(supportsVerbosity('unknown-model')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.concurrent('should be case-insensitive', () => {
|
||||||
|
expect(supportsVerbosity('GPT-5')).toBe(true)
|
||||||
|
expect(supportsVerbosity('GPT-4O')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('supportsThinking', () => {
|
||||||
|
it.concurrent('should return true for models with thinking capability', () => {
|
||||||
|
expect(supportsThinking('claude-opus-4-6')).toBe(true)
|
||||||
|
expect(supportsThinking('claude-opus-4-5')).toBe(true)
|
||||||
|
expect(supportsThinking('claude-sonnet-4-5')).toBe(true)
|
||||||
|
expect(supportsThinking('claude-sonnet-4-0')).toBe(true)
|
||||||
|
expect(supportsThinking('claude-haiku-4-5')).toBe(true)
|
||||||
|
expect(supportsThinking('gemini-3-pro-preview')).toBe(true)
|
||||||
|
expect(supportsThinking('gemini-3-flash-preview')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.concurrent('should return false for models without thinking capability', () => {
|
||||||
|
expect(supportsThinking('gpt-4o')).toBe(false)
|
||||||
|
expect(supportsThinking('gpt-5')).toBe(false)
|
||||||
|
expect(supportsThinking('o3')).toBe(false)
|
||||||
|
expect(supportsThinking('deepseek-v3')).toBe(false)
|
||||||
|
expect(supportsThinking('unknown-model')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it.concurrent('should be case-insensitive', () => {
|
||||||
|
expect(supportsThinking('CLAUDE-OPUS-4-6')).toBe(true)
|
||||||
|
expect(supportsThinking('GPT-4O')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Model Constants', () => {
|
describe('Model Constants', () => {
|
||||||
it.concurrent('should have correct models in MODELS_TEMP_RANGE_0_2', () => {
|
it.concurrent('should have correct models in MODELS_TEMP_RANGE_0_2', () => {
|
||||||
expect(MODELS_TEMP_RANGE_0_2).toContain('gpt-4o')
|
expect(MODELS_TEMP_RANGE_0_2).toContain('gpt-4o')
|
||||||
|
|||||||
@@ -959,6 +959,18 @@ export function supportsTemperature(model: string): boolean {
|
|||||||
return supportsTemperatureFromDefinitions(model)
|
return supportsTemperatureFromDefinitions(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function supportsReasoningEffort(model: string): boolean {
|
||||||
|
return MODELS_WITH_REASONING_EFFORT.includes(model.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
export function supportsVerbosity(model: string): boolean {
|
||||||
|
return MODELS_WITH_VERBOSITY.includes(model.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
|
export function supportsThinking(model: string): boolean {
|
||||||
|
return MODELS_WITH_THINKING.includes(model.toLowerCase())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the maximum temperature value for a model
|
* Get the maximum temperature value for a model
|
||||||
* @returns Maximum temperature value (1 or 2) or undefined if temperature not supported
|
* @returns Maximum temperature value (1 or 2) or undefined if temperature not supported
|
||||||
|
|||||||
Reference in New Issue
Block a user