openAgent
gitlabThe open source UNIVERSAL agent.
clone
https://gitlab.com/bitzzdev/openAgent.gitAIagentautomationopen-sourceuniversal-agent
export * as Config from "./config"import { makeLocationNode } from "./effect/app-node"import path from "path"import { type ParseError, parse } from "jsonc-parser"import { Context, Effect, Layer, Option, Schema } from "effect"import { Permission } from "@opencode-ai/schema/permission"import { FSUtil } from "./fs-util"import { Global } from "./global"import { Location } from "./location"import { Policy } from "./policy"import { AbsolutePath } from "./schema"import { ConfigAgent } from "./config/agent"import { ConfigAttachments } from "./config/attachments"import { ConfigCompaction } from "./config/compaction"import { ConfigCommand } from "./config/command"import { ConfigExperimental } from "./config/experimental"import { ConfigFormatter } from "./config/formatter"import { ConfigLSP } from "./config/lsp"import { ConfigMCP } from "./config/mcp"import { ConfigPlugin } from "./config/plugin"import { ConfigProvider } from "./config/provider"import { ConfigReference } from "./config/reference"import { ConfigToolOutput } from "./config/tool-output"import { ConfigWatcher } from "./config/watcher"import { ConfigV1 } from "./v1/config/config"import { ConfigMigrateV1 } from "./v1/config/migrate"export class Info extends Schema.Class<Info>("Config.Info")({$schema: Schema.optional(Schema.String).annotate({description: "JSON schema reference for configuration validation",}),shell: Schema.String.pipe(Schema.optional).annotate({description: "Default shell to use for terminal and shell tool execution",}),model: Schema.String.pipe(Schema.optional).annotate({description: "Default model to use when no session or agent model is selected",}),default_agent: Schema.String.pipe(Schema.optional).annotate({description: "Default primary agent to use when no session agent is selected",}),autoupdate: Schema.Union([Schema.Boolean, Schema.Literal("notify")]).pipe(Schema.optional).annotate({description: "Automatically update or notify when a new version is available",}),share: Schema.Literals(["manual", "auto", "disabled"]).pipe(Schema.optional).annotate({description: "Control whether sessions may be shared manually, automatically, or not at all",}),enterprise: Schema.Struct({url: Schema.String.pipe(Schema.optional),}).pipe(Schema.optional).annotate({description: "Enterprise sharing service configuration",}),username: Schema.String.pipe(Schema.optional).annotate({description: "Username displayed in conversations and used for telemetry identity",}),permissions: Permission.Ruleset.pipe(Schema.optional).annotate({description: "Ordered tool permission rules applied to agent tool use",}),agents: Schema.Record(Schema.String, ConfigAgent.Info).pipe(Schema.optional).annotate({description: "Named built-in agent overrides and custom agent definitions",}),snapshots: Schema.Boolean.pipe(Schema.optional).annotate({description: "Enable snapshots used for undo and revert behavior",}),watcher: ConfigWatcher.Info.pipe(Schema.optional).annotate({description: "Filesystem watcher configuration",}),formatter: ConfigFormatter.Info.pipe(Schema.optional).annotate({description: "Enable built-in formatters or configure formatter overrides",}),lsp: ConfigLSP.Info.pipe(Schema.optional).annotate({description: "Enable built-in language servers or configure server overrides",}),attachments: ConfigAttachments.Info.pipe(Schema.optional).annotate({description: "Attachment processing configuration",}),tool_output: ConfigToolOutput.Info.pipe(Schema.optional).annotate({description: "Tool output truncation thresholds",}),mcp: ConfigMCP.Info.pipe(Schema.optional).annotate({description: "MCP server configuration",}),compaction: ConfigCompaction.Info.pipe(Schema.optional).annotate({description: "Conversation compaction behavior",}),skills: Schema.String.pipe(Schema.Array, Schema.optional).annotate({description: "Additional paths or URLs to discover skills from",}),commands: Schema.Record(Schema.String, ConfigCommand.Info).pipe(Schema.optional).annotate({description: "Named slash command definitions",}),instructions: Schema.String.pipe(Schema.Array, Schema.optional).annotate({description: "Additional paths or URLs supplying ambient instructions",}),references: ConfigReference.Info.pipe(Schema.optional).annotate({description: "Named local directories or Git repositories available as external context",}),plugins: ConfigPlugin.Plugins.pipe(Schema.optional).annotate({description: "Ordered external plugin packages to load",}),experimental: ConfigExperimental.Experimental.pipe(Schema.optional),providers: Schema.Record(Schema.String, ConfigProvider.Info).pipe(Schema.optional),}) {}export class Document extends Schema.Class<Document>("Config.Document")({type: Schema.Literal("document"),path: Schema.String.pipe(Schema.optional),info: Info,}) {}export class Directory extends Schema.Class<Directory>("Config.Directory")({type: Schema.Literal("directory"),path: AbsolutePath,}) {}export type Entry = Document | Directoryexport function latest<K extends keyof Info>(entries: readonly Entry[], key: K): Info[K] | undefined {return entries.filter((entry): entry is Document => entry.type === "document").findLast((entry) => entry.info[key] !== undefined)?.info[key]}export interface Interface {/** Returns location config documents and supplemental directories from lowest to highest priority. */readonly entries: () => Effect.Effect<Entry[]>}export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Config") {}const layer = Layer.effect(Service,Effect.gen(function* () {const fs = yield* FSUtil.Serviceconst global = yield* Global.Serviceconst location = yield* Location.Serviceconst policy = yield* Policy.Serviceconst names = ["opencode.json", "opencode.jsonc"]const decodeOptions = { errors: "all", onExcessProperty: "ignore", propertyOrder: "original" } as constconst decodeInfo = Schema.decodeUnknownOption(Info, decodeOptions)const decodeV1Info = Schema.decodeUnknownOption(ConfigV1.Info, decodeOptions)const loadFile = Effect.fnUntraced(function* (filepath: string) {const text = yield* fs.readFileStringSafe(filepath)if (!text) returnconst errors: ParseError[] = []const input: unknown = parse(text, errors, { allowTrailingComma: true })if (errors.length) returnconst info = Option.getOrUndefined(ConfigMigrateV1.isV1(input)? decodeV1Info(input).pipe(Option.map(ConfigMigrateV1.migrate), Option.flatMap(decodeInfo)): decodeInfo(input),)if (!info) returnreturn new Document({ type: "document", path: filepath, info })})const loadDirectory = Effect.fnUntraced(function* (directory: AbsolutePath) {return [...(yield* Effect.forEach(names, (file) => loadFile(path.join(directory, file))).pipe(Effect.map((configs) => configs.filter((config): config is Document => config !== undefined)),)),new Directory({ type: "directory", path: directory }),]})const globalDirectory = AbsolutePath.make(global.config)const locationIsGlobal = path.resolve(location.directory) === path.resolve(global.config)// Read configuration once when this location opens. Later calls reuse these// values until the location is reopened.const discovered = locationIsGlobal? []: yield* fs.up({targets: [".opencode", ...names.toReversed()],start: location.directory,stop: location.project.directory,}).pipe(Effect.orDie)const directories = [globalDirectory,...discovered.filter((item) => path.basename(item) === ".opencode").toReversed().map((directory) => AbsolutePath.make(directory)),]// A config closer to the opened directory should win over one higher up.// Search starts nearby, so reverse the results before applying them.const directPaths = discovered.filter((item) => path.basename(item) !== ".opencode").toReversed()const direct = yield* Effect.forEach(directPaths, loadFile).pipe(Effect.orDie,Effect.map((configs) => configs.filter((config): config is Document => config !== undefined)),)const supplementary = yield* Effect.forEach(directories, loadDirectory).pipe(Effect.orDie)// Apply general settings first and more specific settings last:// global config, project files, then `.opencode` files.const configs = [...(supplementary[0] ?? []), ...direct, ...supplementary.slice(1).flat()]// Rules use the opposite order so a user-global rule can override a// repository rule. Statement order inside each file stays unchanged.yield* policy.load(configs.filter((config): config is Document => config.type === "document").toReversed().flatMap((config) => config.info.experimental?.policies ?? []),)return Service.of({entries: Effect.fn("Config.entries")(function* () {return configs}),})}),)export const locationLayer = layer.pipe(Layer.provideMerge(Policy.locationLayer))export const node = makeLocationNode({service: Service,layer,deps: [FSUtil.node, Global.node, Location.node, Policy.node],})