|
|
@@ -24,389 +24,389 @@ import { InstanceState } from "@/effect"
|
|
|
import * as Option from "effect/Option"
|
|
|
import * as OtelTracer from "@effect/opentelemetry/Tracer"
|
|
|
|
|
|
-export namespace Agent {
|
|
|
- export const Info = z
|
|
|
- .object({
|
|
|
- name: z.string(),
|
|
|
- description: z.string().optional(),
|
|
|
- mode: z.enum(["subagent", "primary", "all"]),
|
|
|
- native: z.boolean().optional(),
|
|
|
- hidden: z.boolean().optional(),
|
|
|
- topP: z.number().optional(),
|
|
|
- temperature: z.number().optional(),
|
|
|
- color: z.string().optional(),
|
|
|
- permission: Permission.Ruleset.zod,
|
|
|
- model: z
|
|
|
- .object({
|
|
|
- modelID: ModelID.zod,
|
|
|
- providerID: ProviderID.zod,
|
|
|
- })
|
|
|
- .optional(),
|
|
|
- variant: z.string().optional(),
|
|
|
- prompt: z.string().optional(),
|
|
|
- options: z.record(z.string(), z.any()),
|
|
|
- steps: z.number().int().positive().optional(),
|
|
|
- })
|
|
|
- .meta({
|
|
|
- ref: "Agent",
|
|
|
- })
|
|
|
- export type Info = z.infer<typeof Info>
|
|
|
+export const Info = z
|
|
|
+ .object({
|
|
|
+ name: z.string(),
|
|
|
+ description: z.string().optional(),
|
|
|
+ mode: z.enum(["subagent", "primary", "all"]),
|
|
|
+ native: z.boolean().optional(),
|
|
|
+ hidden: z.boolean().optional(),
|
|
|
+ topP: z.number().optional(),
|
|
|
+ temperature: z.number().optional(),
|
|
|
+ color: z.string().optional(),
|
|
|
+ permission: Permission.Ruleset.zod,
|
|
|
+ model: z
|
|
|
+ .object({
|
|
|
+ modelID: ModelID.zod,
|
|
|
+ providerID: ProviderID.zod,
|
|
|
+ })
|
|
|
+ .optional(),
|
|
|
+ variant: z.string().optional(),
|
|
|
+ prompt: z.string().optional(),
|
|
|
+ options: z.record(z.string(), z.any()),
|
|
|
+ steps: z.number().int().positive().optional(),
|
|
|
+ })
|
|
|
+ .meta({
|
|
|
+ ref: "Agent",
|
|
|
+ })
|
|
|
+export type Info = z.infer<typeof Info>
|
|
|
|
|
|
- export interface Interface {
|
|
|
- readonly get: (agent: string) => Effect.Effect<Agent.Info>
|
|
|
- readonly list: () => Effect.Effect<Agent.Info[]>
|
|
|
- readonly defaultAgent: () => Effect.Effect<string>
|
|
|
- readonly generate: (input: {
|
|
|
- description: string
|
|
|
- model?: { providerID: ProviderID; modelID: ModelID }
|
|
|
- }) => Effect.Effect<{
|
|
|
- identifier: string
|
|
|
- whenToUse: string
|
|
|
- systemPrompt: string
|
|
|
- }>
|
|
|
- }
|
|
|
+export interface Interface {
|
|
|
+ readonly get: (agent: string) => Effect.Effect<Info>
|
|
|
+ readonly list: () => Effect.Effect<Info[]>
|
|
|
+ readonly defaultAgent: () => Effect.Effect<string>
|
|
|
+ readonly generate: (input: {
|
|
|
+ description: string
|
|
|
+ model?: { providerID: ProviderID; modelID: ModelID }
|
|
|
+ }) => Effect.Effect<{
|
|
|
+ identifier: string
|
|
|
+ whenToUse: string
|
|
|
+ systemPrompt: string
|
|
|
+ }>
|
|
|
+}
|
|
|
|
|
|
- type State = Omit<Interface, "generate">
|
|
|
+type State = Omit<Interface, "generate">
|
|
|
|
|
|
- export class Service extends Context.Service<Service, Interface>()("@opencode/Agent") {}
|
|
|
+export class Service extends Context.Service<Service, Interface>()("@opencode/Agent") {}
|
|
|
|
|
|
- export const layer = Layer.effect(
|
|
|
- Service,
|
|
|
- Effect.gen(function* () {
|
|
|
- const config = yield* Config.Service
|
|
|
- const auth = yield* Auth.Service
|
|
|
- const plugin = yield* Plugin.Service
|
|
|
- const skill = yield* Skill.Service
|
|
|
- const provider = yield* Provider.Service
|
|
|
+export const layer = Layer.effect(
|
|
|
+ Service,
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const config = yield* Config.Service
|
|
|
+ const auth = yield* Auth.Service
|
|
|
+ const plugin = yield* Plugin.Service
|
|
|
+ const skill = yield* Skill.Service
|
|
|
+ const provider = yield* Provider.Service
|
|
|
|
|
|
- const state = yield* InstanceState.make<State>(
|
|
|
- Effect.fn("Agent.state")(function* (_ctx) {
|
|
|
- const cfg = yield* config.get()
|
|
|
- const skillDirs = yield* skill.dirs()
|
|
|
- const whitelistedDirs = [Truncate.GLOB, ...skillDirs.map((dir) => path.join(dir, "*"))]
|
|
|
+ const state = yield* InstanceState.make<State>(
|
|
|
+ Effect.fn("Agent.state")(function* (_ctx) {
|
|
|
+ const cfg = yield* config.get()
|
|
|
+ const skillDirs = yield* skill.dirs()
|
|
|
+ const whitelistedDirs = [Truncate.GLOB, ...skillDirs.map((dir) => path.join(dir, "*"))]
|
|
|
|
|
|
- const defaults = Permission.fromConfig({
|
|
|
+ const defaults = Permission.fromConfig({
|
|
|
+ "*": "allow",
|
|
|
+ doom_loop: "ask",
|
|
|
+ external_directory: {
|
|
|
+ "*": "ask",
|
|
|
+ ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
|
|
|
+ },
|
|
|
+ question: "deny",
|
|
|
+ plan_enter: "deny",
|
|
|
+ plan_exit: "deny",
|
|
|
+ // mirrors github.com/github/gitignore Node.gitignore pattern for .env files
|
|
|
+ read: {
|
|
|
"*": "allow",
|
|
|
- doom_loop: "ask",
|
|
|
- external_directory: {
|
|
|
- "*": "ask",
|
|
|
- ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
|
|
|
- },
|
|
|
- question: "deny",
|
|
|
- plan_enter: "deny",
|
|
|
- plan_exit: "deny",
|
|
|
- // mirrors github.com/github/gitignore Node.gitignore pattern for .env files
|
|
|
- read: {
|
|
|
- "*": "allow",
|
|
|
- "*.env": "ask",
|
|
|
- "*.env.*": "ask",
|
|
|
- "*.env.example": "allow",
|
|
|
- },
|
|
|
- })
|
|
|
+ "*.env": "ask",
|
|
|
+ "*.env.*": "ask",
|
|
|
+ "*.env.example": "allow",
|
|
|
+ },
|
|
|
+ })
|
|
|
|
|
|
- const user = Permission.fromConfig(cfg.permission ?? {})
|
|
|
+ const user = Permission.fromConfig(cfg.permission ?? {})
|
|
|
|
|
|
- const agents: Record<string, Info> = {
|
|
|
- build: {
|
|
|
- name: "build",
|
|
|
- description: "The default agent. Executes tools based on configured permissions.",
|
|
|
- options: {},
|
|
|
- permission: Permission.merge(
|
|
|
- defaults,
|
|
|
- Permission.fromConfig({
|
|
|
- question: "allow",
|
|
|
- plan_enter: "allow",
|
|
|
- }),
|
|
|
- user,
|
|
|
- ),
|
|
|
- mode: "primary",
|
|
|
- native: true,
|
|
|
- },
|
|
|
- plan: {
|
|
|
- name: "plan",
|
|
|
- description: "Plan mode. Disallows all edit tools.",
|
|
|
- options: {},
|
|
|
- permission: Permission.merge(
|
|
|
- defaults,
|
|
|
- Permission.fromConfig({
|
|
|
- question: "allow",
|
|
|
- plan_exit: "allow",
|
|
|
- external_directory: {
|
|
|
- [path.join(Global.Path.data, "plans", "*")]: "allow",
|
|
|
- },
|
|
|
- edit: {
|
|
|
- "*": "deny",
|
|
|
- [path.join(".opencode", "plans", "*.md")]: "allow",
|
|
|
- [path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]:
|
|
|
- "allow",
|
|
|
- },
|
|
|
- }),
|
|
|
- user,
|
|
|
- ),
|
|
|
- mode: "primary",
|
|
|
- native: true,
|
|
|
- },
|
|
|
- general: {
|
|
|
- name: "general",
|
|
|
- description: `General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel.`,
|
|
|
- permission: Permission.merge(
|
|
|
- defaults,
|
|
|
- Permission.fromConfig({
|
|
|
- todowrite: "deny",
|
|
|
- }),
|
|
|
- user,
|
|
|
- ),
|
|
|
- options: {},
|
|
|
- mode: "subagent",
|
|
|
- native: true,
|
|
|
- },
|
|
|
- explore: {
|
|
|
- name: "explore",
|
|
|
- permission: Permission.merge(
|
|
|
- defaults,
|
|
|
- Permission.fromConfig({
|
|
|
- "*": "deny",
|
|
|
- grep: "allow",
|
|
|
- glob: "allow",
|
|
|
- list: "allow",
|
|
|
- bash: "allow",
|
|
|
- webfetch: "allow",
|
|
|
- websearch: "allow",
|
|
|
- codesearch: "allow",
|
|
|
- read: "allow",
|
|
|
- external_directory: {
|
|
|
- "*": "ask",
|
|
|
- ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
|
|
|
- },
|
|
|
- }),
|
|
|
- user,
|
|
|
- ),
|
|
|
- description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,
|
|
|
- prompt: PROMPT_EXPLORE,
|
|
|
- options: {},
|
|
|
- mode: "subagent",
|
|
|
- native: true,
|
|
|
- },
|
|
|
- compaction: {
|
|
|
- name: "compaction",
|
|
|
- mode: "primary",
|
|
|
- native: true,
|
|
|
- hidden: true,
|
|
|
- prompt: PROMPT_COMPACTION,
|
|
|
- permission: Permission.merge(
|
|
|
- defaults,
|
|
|
- Permission.fromConfig({
|
|
|
- "*": "deny",
|
|
|
- }),
|
|
|
- user,
|
|
|
- ),
|
|
|
- options: {},
|
|
|
- },
|
|
|
- title: {
|
|
|
- name: "title",
|
|
|
- mode: "primary",
|
|
|
- options: {},
|
|
|
- native: true,
|
|
|
- hidden: true,
|
|
|
- temperature: 0.5,
|
|
|
- permission: Permission.merge(
|
|
|
- defaults,
|
|
|
- Permission.fromConfig({
|
|
|
- "*": "deny",
|
|
|
- }),
|
|
|
- user,
|
|
|
- ),
|
|
|
- prompt: PROMPT_TITLE,
|
|
|
- },
|
|
|
- summary: {
|
|
|
- name: "summary",
|
|
|
- mode: "primary",
|
|
|
- options: {},
|
|
|
- native: true,
|
|
|
- hidden: true,
|
|
|
- permission: Permission.merge(
|
|
|
- defaults,
|
|
|
- Permission.fromConfig({
|
|
|
+ const agents: Record<string, Info> = {
|
|
|
+ build: {
|
|
|
+ name: "build",
|
|
|
+ description: "The default agent. Executes tools based on configured permissions.",
|
|
|
+ options: {},
|
|
|
+ permission: Permission.merge(
|
|
|
+ defaults,
|
|
|
+ Permission.fromConfig({
|
|
|
+ question: "allow",
|
|
|
+ plan_enter: "allow",
|
|
|
+ }),
|
|
|
+ user,
|
|
|
+ ),
|
|
|
+ mode: "primary",
|
|
|
+ native: true,
|
|
|
+ },
|
|
|
+ plan: {
|
|
|
+ name: "plan",
|
|
|
+ description: "Plan mode. Disallows all edit tools.",
|
|
|
+ options: {},
|
|
|
+ permission: Permission.merge(
|
|
|
+ defaults,
|
|
|
+ Permission.fromConfig({
|
|
|
+ question: "allow",
|
|
|
+ plan_exit: "allow",
|
|
|
+ external_directory: {
|
|
|
+ [path.join(Global.Path.data, "plans", "*")]: "allow",
|
|
|
+ },
|
|
|
+ edit: {
|
|
|
"*": "deny",
|
|
|
- }),
|
|
|
- user,
|
|
|
- ),
|
|
|
- prompt: PROMPT_SUMMARY,
|
|
|
- },
|
|
|
- }
|
|
|
+ [path.join(".opencode", "plans", "*.md")]: "allow",
|
|
|
+ [path.relative(Instance.worktree, path.join(Global.Path.data, path.join("plans", "*.md")))]:
|
|
|
+ "allow",
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ user,
|
|
|
+ ),
|
|
|
+ mode: "primary",
|
|
|
+ native: true,
|
|
|
+ },
|
|
|
+ general: {
|
|
|
+ name: "general",
|
|
|
+ description: `General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel.`,
|
|
|
+ permission: Permission.merge(
|
|
|
+ defaults,
|
|
|
+ Permission.fromConfig({
|
|
|
+ todowrite: "deny",
|
|
|
+ }),
|
|
|
+ user,
|
|
|
+ ),
|
|
|
+ options: {},
|
|
|
+ mode: "subagent",
|
|
|
+ native: true,
|
|
|
+ },
|
|
|
+ explore: {
|
|
|
+ name: "explore",
|
|
|
+ permission: Permission.merge(
|
|
|
+ defaults,
|
|
|
+ Permission.fromConfig({
|
|
|
+ "*": "deny",
|
|
|
+ grep: "allow",
|
|
|
+ glob: "allow",
|
|
|
+ list: "allow",
|
|
|
+ bash: "allow",
|
|
|
+ webfetch: "allow",
|
|
|
+ websearch: "allow",
|
|
|
+ codesearch: "allow",
|
|
|
+ read: "allow",
|
|
|
+ external_directory: {
|
|
|
+ "*": "ask",
|
|
|
+ ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ user,
|
|
|
+ ),
|
|
|
+ description: `Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (eg. "src/components/**/*.tsx"), search code for keywords (eg. "API endpoints"), or answer questions about the codebase (eg. "how do API endpoints work?"). When calling this agent, specify the desired thoroughness level: "quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis across multiple locations and naming conventions.`,
|
|
|
+ prompt: PROMPT_EXPLORE,
|
|
|
+ options: {},
|
|
|
+ mode: "subagent",
|
|
|
+ native: true,
|
|
|
+ },
|
|
|
+ compaction: {
|
|
|
+ name: "compaction",
|
|
|
+ mode: "primary",
|
|
|
+ native: true,
|
|
|
+ hidden: true,
|
|
|
+ prompt: PROMPT_COMPACTION,
|
|
|
+ permission: Permission.merge(
|
|
|
+ defaults,
|
|
|
+ Permission.fromConfig({
|
|
|
+ "*": "deny",
|
|
|
+ }),
|
|
|
+ user,
|
|
|
+ ),
|
|
|
+ options: {},
|
|
|
+ },
|
|
|
+ title: {
|
|
|
+ name: "title",
|
|
|
+ mode: "primary",
|
|
|
+ options: {},
|
|
|
+ native: true,
|
|
|
+ hidden: true,
|
|
|
+ temperature: 0.5,
|
|
|
+ permission: Permission.merge(
|
|
|
+ defaults,
|
|
|
+ Permission.fromConfig({
|
|
|
+ "*": "deny",
|
|
|
+ }),
|
|
|
+ user,
|
|
|
+ ),
|
|
|
+ prompt: PROMPT_TITLE,
|
|
|
+ },
|
|
|
+ summary: {
|
|
|
+ name: "summary",
|
|
|
+ mode: "primary",
|
|
|
+ options: {},
|
|
|
+ native: true,
|
|
|
+ hidden: true,
|
|
|
+ permission: Permission.merge(
|
|
|
+ defaults,
|
|
|
+ Permission.fromConfig({
|
|
|
+ "*": "deny",
|
|
|
+ }),
|
|
|
+ user,
|
|
|
+ ),
|
|
|
+ prompt: PROMPT_SUMMARY,
|
|
|
+ },
|
|
|
+ }
|
|
|
|
|
|
- for (const [key, value] of Object.entries(cfg.agent ?? {})) {
|
|
|
- if (value.disable) {
|
|
|
- delete agents[key]
|
|
|
- continue
|
|
|
- }
|
|
|
- let item = agents[key]
|
|
|
- if (!item)
|
|
|
- item = agents[key] = {
|
|
|
- name: key,
|
|
|
- mode: "all",
|
|
|
- permission: Permission.merge(defaults, user),
|
|
|
- options: {},
|
|
|
- native: false,
|
|
|
- }
|
|
|
- if (value.model) item.model = Provider.parseModel(value.model)
|
|
|
- item.variant = value.variant ?? item.variant
|
|
|
- item.prompt = value.prompt ?? item.prompt
|
|
|
- item.description = value.description ?? item.description
|
|
|
- item.temperature = value.temperature ?? item.temperature
|
|
|
- item.topP = value.top_p ?? item.topP
|
|
|
- item.mode = value.mode ?? item.mode
|
|
|
- item.color = value.color ?? item.color
|
|
|
- item.hidden = value.hidden ?? item.hidden
|
|
|
- item.name = value.name ?? item.name
|
|
|
- item.steps = value.steps ?? item.steps
|
|
|
- item.options = mergeDeep(item.options, value.options ?? {})
|
|
|
- item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {}))
|
|
|
+ for (const [key, value] of Object.entries(cfg.agent ?? {})) {
|
|
|
+ if (value.disable) {
|
|
|
+ delete agents[key]
|
|
|
+ continue
|
|
|
}
|
|
|
+ let item = agents[key]
|
|
|
+ if (!item)
|
|
|
+ item = agents[key] = {
|
|
|
+ name: key,
|
|
|
+ mode: "all",
|
|
|
+ permission: Permission.merge(defaults, user),
|
|
|
+ options: {},
|
|
|
+ native: false,
|
|
|
+ }
|
|
|
+ if (value.model) item.model = Provider.parseModel(value.model)
|
|
|
+ item.variant = value.variant ?? item.variant
|
|
|
+ item.prompt = value.prompt ?? item.prompt
|
|
|
+ item.description = value.description ?? item.description
|
|
|
+ item.temperature = value.temperature ?? item.temperature
|
|
|
+ item.topP = value.top_p ?? item.topP
|
|
|
+ item.mode = value.mode ?? item.mode
|
|
|
+ item.color = value.color ?? item.color
|
|
|
+ item.hidden = value.hidden ?? item.hidden
|
|
|
+ item.name = value.name ?? item.name
|
|
|
+ item.steps = value.steps ?? item.steps
|
|
|
+ item.options = mergeDeep(item.options, value.options ?? {})
|
|
|
+ item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {}))
|
|
|
+ }
|
|
|
|
|
|
- // Ensure Truncate.GLOB is allowed unless explicitly configured
|
|
|
- for (const name in agents) {
|
|
|
- const agent = agents[name]
|
|
|
- const explicit = agent.permission.some((r) => {
|
|
|
- if (r.permission !== "external_directory") return false
|
|
|
- if (r.action !== "deny") return false
|
|
|
- return r.pattern === Truncate.GLOB
|
|
|
- })
|
|
|
- if (explicit) continue
|
|
|
+ // Ensure Truncate.GLOB is allowed unless explicitly configured
|
|
|
+ for (const name in agents) {
|
|
|
+ const agent = agents[name]
|
|
|
+ const explicit = agent.permission.some((r) => {
|
|
|
+ if (r.permission !== "external_directory") return false
|
|
|
+ if (r.action !== "deny") return false
|
|
|
+ return r.pattern === Truncate.GLOB
|
|
|
+ })
|
|
|
+ if (explicit) continue
|
|
|
|
|
|
- agents[name].permission = Permission.merge(
|
|
|
- agents[name].permission,
|
|
|
- Permission.fromConfig({ external_directory: { [Truncate.GLOB]: "allow" } }),
|
|
|
- )
|
|
|
- }
|
|
|
+ agents[name].permission = Permission.merge(
|
|
|
+ agents[name].permission,
|
|
|
+ Permission.fromConfig({ external_directory: { [Truncate.GLOB]: "allow" } }),
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
- const get = Effect.fnUntraced(function* (agent: string) {
|
|
|
- return agents[agent]
|
|
|
- })
|
|
|
+ const get = Effect.fnUntraced(function* (agent: string) {
|
|
|
+ return agents[agent]
|
|
|
+ })
|
|
|
|
|
|
- const list = Effect.fnUntraced(function* () {
|
|
|
- const cfg = yield* config.get()
|
|
|
- return pipe(
|
|
|
- agents,
|
|
|
- values(),
|
|
|
- sortBy(
|
|
|
- [(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "build"), "desc"],
|
|
|
- [(x) => x.name, "asc"],
|
|
|
- ),
|
|
|
- )
|
|
|
- })
|
|
|
+ const list = Effect.fnUntraced(function* () {
|
|
|
+ const cfg = yield* config.get()
|
|
|
+ return pipe(
|
|
|
+ agents,
|
|
|
+ values(),
|
|
|
+ sortBy(
|
|
|
+ [(x) => (cfg.default_agent ? x.name === cfg.default_agent : x.name === "build"), "desc"],
|
|
|
+ [(x) => x.name, "asc"],
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ })
|
|
|
|
|
|
- const defaultAgent = Effect.fnUntraced(function* () {
|
|
|
- const c = yield* config.get()
|
|
|
- if (c.default_agent) {
|
|
|
- const agent = agents[c.default_agent]
|
|
|
- if (!agent) throw new Error(`default agent "${c.default_agent}" not found`)
|
|
|
- if (agent.mode === "subagent") throw new Error(`default agent "${c.default_agent}" is a subagent`)
|
|
|
- if (agent.hidden === true) throw new Error(`default agent "${c.default_agent}" is hidden`)
|
|
|
- return agent.name
|
|
|
- }
|
|
|
- const visible = Object.values(agents).find((a) => a.mode !== "subagent" && a.hidden !== true)
|
|
|
- if (!visible) throw new Error("no primary visible agent found")
|
|
|
- return visible.name
|
|
|
- })
|
|
|
+ const defaultAgent = Effect.fnUntraced(function* () {
|
|
|
+ const c = yield* config.get()
|
|
|
+ if (c.default_agent) {
|
|
|
+ const agent = agents[c.default_agent]
|
|
|
+ if (!agent) throw new Error(`default agent "${c.default_agent}" not found`)
|
|
|
+ if (agent.mode === "subagent") throw new Error(`default agent "${c.default_agent}" is a subagent`)
|
|
|
+ if (agent.hidden === true) throw new Error(`default agent "${c.default_agent}" is hidden`)
|
|
|
+ return agent.name
|
|
|
+ }
|
|
|
+ const visible = Object.values(agents).find((a) => a.mode !== "subagent" && a.hidden !== true)
|
|
|
+ if (!visible) throw new Error("no primary visible agent found")
|
|
|
+ return visible.name
|
|
|
+ })
|
|
|
|
|
|
- return {
|
|
|
- get,
|
|
|
- list,
|
|
|
- defaultAgent,
|
|
|
- } satisfies State
|
|
|
- }),
|
|
|
- )
|
|
|
+ return {
|
|
|
+ get,
|
|
|
+ list,
|
|
|
+ defaultAgent,
|
|
|
+ } satisfies State
|
|
|
+ }),
|
|
|
+ )
|
|
|
|
|
|
- return Service.of({
|
|
|
- get: Effect.fn("Agent.get")(function* (agent: string) {
|
|
|
- return yield* InstanceState.useEffect(state, (s) => s.get(agent))
|
|
|
- }),
|
|
|
- list: Effect.fn("Agent.list")(function* () {
|
|
|
- return yield* InstanceState.useEffect(state, (s) => s.list())
|
|
|
- }),
|
|
|
- defaultAgent: Effect.fn("Agent.defaultAgent")(function* () {
|
|
|
- return yield* InstanceState.useEffect(state, (s) => s.defaultAgent())
|
|
|
- }),
|
|
|
- generate: Effect.fn("Agent.generate")(function* (input: {
|
|
|
- description: string
|
|
|
- model?: { providerID: ProviderID; modelID: ModelID }
|
|
|
- }) {
|
|
|
- const cfg = yield* config.get()
|
|
|
- const model = input.model ?? (yield* provider.defaultModel())
|
|
|
- const resolved = yield* provider.getModel(model.providerID, model.modelID)
|
|
|
- const language = yield* provider.getLanguage(resolved)
|
|
|
- const tracer = cfg.experimental?.openTelemetry
|
|
|
- ? Option.getOrUndefined(yield* Effect.serviceOption(OtelTracer.OtelTracer))
|
|
|
- : undefined
|
|
|
+ return Service.of({
|
|
|
+ get: Effect.fn("Agent.get")(function* (agent: string) {
|
|
|
+ return yield* InstanceState.useEffect(state, (s) => s.get(agent))
|
|
|
+ }),
|
|
|
+ list: Effect.fn("Agent.list")(function* () {
|
|
|
+ return yield* InstanceState.useEffect(state, (s) => s.list())
|
|
|
+ }),
|
|
|
+ defaultAgent: Effect.fn("Agent.defaultAgent")(function* () {
|
|
|
+ return yield* InstanceState.useEffect(state, (s) => s.defaultAgent())
|
|
|
+ }),
|
|
|
+ generate: Effect.fn("Agent.generate")(function* (input: {
|
|
|
+ description: string
|
|
|
+ model?: { providerID: ProviderID; modelID: ModelID }
|
|
|
+ }) {
|
|
|
+ const cfg = yield* config.get()
|
|
|
+ const model = input.model ?? (yield* provider.defaultModel())
|
|
|
+ const resolved = yield* provider.getModel(model.providerID, model.modelID)
|
|
|
+ const language = yield* provider.getLanguage(resolved)
|
|
|
+ const tracer = cfg.experimental?.openTelemetry
|
|
|
+ ? Option.getOrUndefined(yield* Effect.serviceOption(OtelTracer.OtelTracer))
|
|
|
+ : undefined
|
|
|
|
|
|
- const system = [PROMPT_GENERATE]
|
|
|
- yield* plugin.trigger("experimental.chat.system.transform", { model: resolved }, { system })
|
|
|
- const existing = yield* InstanceState.useEffect(state, (s) => s.list())
|
|
|
+ const system = [PROMPT_GENERATE]
|
|
|
+ yield* plugin.trigger("experimental.chat.system.transform", { model: resolved }, { system })
|
|
|
+ const existing = yield* InstanceState.useEffect(state, (s) => s.list())
|
|
|
|
|
|
- // TODO: clean this up so provider specific logic doesnt bleed over
|
|
|
- const authInfo = yield* auth.get(model.providerID).pipe(Effect.orDie)
|
|
|
- const isOpenaiOauth = model.providerID === "openai" && authInfo?.type === "oauth"
|
|
|
+ // TODO: clean this up so provider specific logic doesnt bleed over
|
|
|
+ const authInfo = yield* auth.get(model.providerID).pipe(Effect.orDie)
|
|
|
+ const isOpenaiOauth = model.providerID === "openai" && authInfo?.type === "oauth"
|
|
|
|
|
|
- const params = {
|
|
|
- experimental_telemetry: {
|
|
|
- isEnabled: cfg.experimental?.openTelemetry,
|
|
|
- tracer,
|
|
|
- metadata: {
|
|
|
- userId: cfg.username ?? "unknown",
|
|
|
- },
|
|
|
+ const params = {
|
|
|
+ experimental_telemetry: {
|
|
|
+ isEnabled: cfg.experimental?.openTelemetry,
|
|
|
+ tracer,
|
|
|
+ metadata: {
|
|
|
+ userId: cfg.username ?? "unknown",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ temperature: 0.3,
|
|
|
+ messages: [
|
|
|
+ ...(isOpenaiOauth
|
|
|
+ ? []
|
|
|
+ : system.map(
|
|
|
+ (item): ModelMessage => ({
|
|
|
+ role: "system",
|
|
|
+ content: item,
|
|
|
+ }),
|
|
|
+ )),
|
|
|
+ {
|
|
|
+ role: "user",
|
|
|
+ content: `Create an agent configuration based on this request: "${input.description}".\n\nIMPORTANT: The following identifiers already exist and must NOT be used: ${existing.map((i) => i.name).join(", ")}\n Return ONLY the JSON object, no other text, do not wrap in backticks`,
|
|
|
},
|
|
|
- temperature: 0.3,
|
|
|
- messages: [
|
|
|
- ...(isOpenaiOauth
|
|
|
- ? []
|
|
|
- : system.map(
|
|
|
- (item): ModelMessage => ({
|
|
|
- role: "system",
|
|
|
- content: item,
|
|
|
- }),
|
|
|
- )),
|
|
|
- {
|
|
|
- role: "user",
|
|
|
- content: `Create an agent configuration based on this request: "${input.description}".\n\nIMPORTANT: The following identifiers already exist and must NOT be used: ${existing.map((i) => i.name).join(", ")}\n Return ONLY the JSON object, no other text, do not wrap in backticks`,
|
|
|
- },
|
|
|
- ],
|
|
|
- model: language,
|
|
|
- schema: z.object({
|
|
|
- identifier: z.string(),
|
|
|
- whenToUse: z.string(),
|
|
|
- systemPrompt: z.string(),
|
|
|
- }),
|
|
|
- } satisfies Parameters<typeof generateObject>[0]
|
|
|
+ ],
|
|
|
+ model: language,
|
|
|
+ schema: z.object({
|
|
|
+ identifier: z.string(),
|
|
|
+ whenToUse: z.string(),
|
|
|
+ systemPrompt: z.string(),
|
|
|
+ }),
|
|
|
+ } satisfies Parameters<typeof generateObject>[0]
|
|
|
|
|
|
- if (isOpenaiOauth) {
|
|
|
- return yield* Effect.promise(async () => {
|
|
|
- const result = streamObject({
|
|
|
- ...params,
|
|
|
- providerOptions: ProviderTransform.providerOptions(resolved, {
|
|
|
- instructions: system.join("\n"),
|
|
|
- store: false,
|
|
|
- }),
|
|
|
- onError: () => {},
|
|
|
- })
|
|
|
- for await (const part of result.fullStream) {
|
|
|
- if (part.type === "error") throw part.error
|
|
|
- }
|
|
|
- return result.object
|
|
|
+ if (isOpenaiOauth) {
|
|
|
+ return yield* Effect.promise(async () => {
|
|
|
+ const result = streamObject({
|
|
|
+ ...params,
|
|
|
+ providerOptions: ProviderTransform.providerOptions(resolved, {
|
|
|
+ instructions: system.join("\n"),
|
|
|
+ store: false,
|
|
|
+ }),
|
|
|
+ onError: () => {},
|
|
|
})
|
|
|
- }
|
|
|
+ for await (const part of result.fullStream) {
|
|
|
+ if (part.type === "error") throw part.error
|
|
|
+ }
|
|
|
+ return result.object
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
- return yield* Effect.promise(() => generateObject(params).then((r) => r.object))
|
|
|
- }),
|
|
|
- })
|
|
|
- }),
|
|
|
- )
|
|
|
+ return yield* Effect.promise(() => generateObject(params).then((r) => r.object))
|
|
|
+ }),
|
|
|
+ })
|
|
|
+ }),
|
|
|
+)
|
|
|
|
|
|
- export const defaultLayer = layer.pipe(
|
|
|
- Layer.provide(Plugin.defaultLayer),
|
|
|
- Layer.provide(Provider.defaultLayer),
|
|
|
- Layer.provide(Auth.defaultLayer),
|
|
|
- Layer.provide(Config.defaultLayer),
|
|
|
- Layer.provide(Skill.defaultLayer),
|
|
|
- )
|
|
|
-}
|
|
|
+export const defaultLayer = layer.pipe(
|
|
|
+ Layer.provide(Plugin.defaultLayer),
|
|
|
+ Layer.provide(Provider.defaultLayer),
|
|
|
+ Layer.provide(Auth.defaultLayer),
|
|
|
+ Layer.provide(Config.defaultLayer),
|
|
|
+ Layer.provide(Skill.defaultLayer),
|
|
|
+)
|
|
|
+
|
|
|
+export * as Agent from "./agent"
|