Skip to content
Merged
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
1 change: 1 addition & 0 deletions .oxlintrc.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
],
"ignorePatterns": ["**/node_modules/**", "**/dist/**", ".release/**", "tools/oxlint/**"],
"rules": {
"anti-slop/no-conditional-empty-object-spread": "error",
"anti-slop/no-module-mocking": "error",
"anti-slop/no-object-parameters": "error",
"automation/no-shadowed-standard-array-static": "error",
Expand Down
16 changes: 11 additions & 5 deletions packages/fold-agent/src/Compatibility/GrokCompatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ export type GrokCompatibility = {
export const loadGrokCompatibility = Effect.fn('fold.grok_compatibility.load')(function* (
options: GrokCompatibilityOptions,
) {
const pluginOptions = {
const pluginOptions: {
cwd: string
home?: string
grokHome?: string
projectRoot?: string
configuredPaths?: ReadonlyArray<string>
} = {
cwd: options.cwd,
...(options.home === undefined ? {} : { home: options.home }),
...(options.grokHome === undefined ? {} : { grokHome: options.grokHome }),
...(options.projectRoot === undefined ? {} : { projectRoot: options.projectRoot }),
...(options.configuredPluginPaths === undefined ? {} : { configuredPaths: options.configuredPluginPaths }),
}
if (options.home !== undefined) pluginOptions.home = options.home
if (options.grokHome !== undefined) pluginOptions.grokHome = options.grokHome
if (options.projectRoot !== undefined) pluginOptions.projectRoot = options.projectRoot
if (options.configuredPluginPaths !== undefined) pluginOptions.configuredPaths = options.configuredPluginPaths
const plugins = yield* discoverGrokPluginSkillRoots(pluginOptions)
const instructions = yield* loadGrokInstructions(options)
const skills = yield* makeGrokSkillSource({
Expand Down
6 changes: 3 additions & 3 deletions packages/fold-agent/src/Config/ConfigSchemaJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ export const configSchemaPath = (foldHome?: string): string => join(foldHome ??
export const foldConfigJsonSchema = (): Record<string, unknown> => {
const document = JsonSchema.toDocumentDraft07(Schema.toJsonSchemaDocument(FoldConfig))
const hasDefinitions = Object.keys(document.definitions).length > 0

return {
const schema: Record<string, unknown> = {
$schema: JsonSchema.META_SCHEMA_URI_DRAFT_07,
...document.schema,
...(hasDefinitions ? { definitions: document.definitions } : {}),
}
if (hasDefinitions) schema.definitions = document.definitions
return schema
}

/** The generated schema serialized as JSON text (tab-indented, trailing newline). */
Expand Down
43 changes: 27 additions & 16 deletions packages/fold-agent/src/Config/ModelSelections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,23 @@ import { DEFAULT_CODEX_MODEL_ID } from '@humanlayer/fold-codex'
import { DEFAULT_ANTHROPIC_MODEL_ID, type ModelCatalogEntry, type FoldModel } from '@humanlayer/fold-core'
import { DEFAULT_OPENCODE_MODEL_ID, GROK_BUILD_MODEL_ID } from '@humanlayer/fold-opencode'
import { DEFAULT_XAI_MODEL_ID, XAI_FRONTIER_MODELS } from '@humanlayer/fold-xai'
import { Effect, Match, Predicate } from 'effect'
import { Data, Effect, Match, Predicate } from 'effect'

import { agentModelsFromConfig, type AgentModelsOptions, RoleResolutionError } from './AgentModels'
import type { ConfigRole, ProfileConfig, ProfileModeName, RoleBinding, FoldConfig } from './ConfigSchema'

type RoleBindingBuilder = {
provider: string
model?: string
reasoning?: NonNullable<RoleBinding['reasoning']>
}

type RolesBuilder = {
smart: RoleBinding
fast: RoleBinding
orchestrator?: RoleBinding
}

export type ProfileModelSelection = { readonly _tag: 'profile'; readonly profile: string }
export type DirectModelSelection = {
readonly _tag: 'direct'
Expand All @@ -15,6 +27,7 @@ export type DirectModelSelection = {
readonly reasoning?: RoleBinding['reasoning']
}
export type ConfiguredModelSelection = ProfileModelSelection | DirectModelSelection
export const ConfiguredModelSelection = Data.taggedEnum<ConfiguredModelSelection>()

export type ModelConfiguration = {
readonly profiles: ReadonlyArray<{ readonly name: string; readonly mode: ProfileConfig['mode'] | null }>
Expand Down Expand Up @@ -102,13 +115,11 @@ export const describeModelConfiguration = (
const rolesForProfile = (config: FoldConfig, name: string): FoldConfig['roles'] | null => {
if (name === 'default') return config.roles
const profile = config.profiles?.[name]
return profile === undefined
? null
: {
smart: profile.smart,
fast: profile.fast,
...(profile.orchestrator === undefined ? {} : { orchestrator: profile.orchestrator }),
}
if (profile === undefined) return null

const roles: RolesBuilder = { smart: profile.smart, fast: profile.fast }
if (profile.orchestrator !== undefined) roles.orchestrator = profile.orchestrator
return roles
}

type DirectProviderSelection = {
Expand Down Expand Up @@ -143,15 +154,15 @@ export const rolesForDirectProviderSelection = (
selection: DirectProviderSelection,
): FoldConfig['roles'] => {
const models = defaultModelsForProvider(config, selection)
const bindingFor = (role: ConfigRole): RoleBinding => ({
provider: selection.provider,
...(models[role] === undefined ? {} : { model: models[role] }),
})
const root: RoleBinding = {
...bindingFor(rootRole),
...(selection.model === undefined ? {} : { model: selection.model }),
...(selection.reasoning === undefined ? {} : { reasoning: selection.reasoning }),
const bindingFor = (role: ConfigRole): RoleBindingBuilder => {
const model = models[role]
const binding: RoleBindingBuilder = { provider: selection.provider }
if (model !== undefined) binding.model = model
return binding
}
const root = bindingFor(rootRole)
if (selection.model !== undefined) root.model = selection.model
if (selection.reasoning !== undefined) root.reasoning = selection.reasoning
return {
orchestrator: rootRole === 'orchestrator' ? root : bindingFor('orchestrator'),
smart: rootRole === 'smart' ? root : bindingFor('smart'),
Expand Down
14 changes: 10 additions & 4 deletions packages/fold-agent/src/Config/ProviderConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,19 @@ export const configureProvider = (
const config = yield* loadFoldConfig(options)
const previousModels = config.providers[name]?.configuredModels ?? []
const configuredModels = model === undefined ? previousModels : [...new Set([...previousModels, model])]
const provider = {
const provider: {
kind: ProviderKind
baseUrl: string
apiKey?: string
apiKeyEnv?: string
configuredModels?: ReadonlyArray<string>
} = {
kind: input.kind,
baseUrl,
...(apiKey === undefined ? {} : { apiKey }),
...(apiKeyEnv === undefined ? {} : { apiKeyEnv }),
...(configuredModels.length === 0 ? {} : { configuredModels }),
}
if (apiKey !== undefined) provider.apiKey = apiKey
if (apiKeyEnv !== undefined) provider.apiKeyEnv = apiKeyEnv
if (configuredModels.length > 0) provider.configuredModels = configuredModels
const updated: FoldConfig = { ...config, providers: { ...config.providers, [name]: provider } }

yield* writeConfig(updated, options)
Expand Down
20 changes: 12 additions & 8 deletions packages/fold-agent/src/EventLog/JsonlLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,18 @@ const unavailableError = (
cause,
})

const corruptEntryError = (line: number, message: string, cause?: unknown, seq?: number) =>
new EventLogCorruptEntryError({
operation: 'entries',
message,
line,
...(seq === undefined ? {} : { seq }),
...(cause === undefined ? {} : { cause }),
})
const corruptEntryError = (line: number, message: string, cause?: unknown, seq?: number) => {
const input: {
operation: 'entries'
message: string
line: number
seq?: number
cause?: unknown
} = { operation: 'entries', message, line }
if (seq !== undefined) input.seq = seq
if (cause !== undefined) input.cause = cause
return new EventLogCorruptEntryError(input)
}

const invalidEntryError = (message: string, cause: unknown) =>
new EventLogInvalidEntryError({
Expand Down
Loading
Loading