agent.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. export * as AgentV2 from "./agent"
  2. import { makeLocationNode } from "./effect/app-node"
  3. import { Array, Context, Effect, Layer, Types } from "effect"
  4. import { Agent } from "@opencode-ai/schema/agent"
  5. import { State } from "./state"
  6. export const ID = Agent.ID
  7. export type ID = typeof ID.Type
  8. export const defaultID = ID.make("build")
  9. export const Color = Agent.Color
  10. export const Info = Agent.Info
  11. export type Info = Agent.Info
  12. export interface Selection {
  13. readonly id: ID
  14. readonly info: Info | undefined
  15. }
  16. type Data = {
  17. agents: Map<ID, Types.DeepMutable<Info>>
  18. default?: ID
  19. }
  20. export type Draft = {
  21. list: () => readonly Info[]
  22. get: (id: ID) => Info | undefined
  23. default: (id: ID | undefined) => void
  24. update: (id: ID, fn: (agent: Types.DeepMutable<Info>) => void) => void
  25. remove: (id: ID) => void
  26. }
  27. export interface Interface extends State.Transformable<Draft> {
  28. readonly get: (id: ID) => Effect.Effect<Info | undefined>
  29. readonly default: () => Effect.Effect<Info | undefined>
  30. readonly resolve: (id?: ID | string) => Effect.Effect<Info | undefined>
  31. readonly select: (id?: ID | string) => Effect.Effect<Selection>
  32. readonly all: () => Effect.Effect<Info[]>
  33. }
  34. export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Agent") {}
  35. const layer = Layer.effect(
  36. Service,
  37. Effect.gen(function* () {
  38. const state = State.create<Data, Draft>({
  39. initial: () => ({ agents: new Map() }),
  40. draft: (draft) => ({
  41. list: () => Array.fromIterable(draft.agents.values()) as Info[],
  42. get: (id) => draft.agents.get(id),
  43. default: (id) => {
  44. draft.default = id
  45. },
  46. update: (id, fn) => {
  47. const current = draft.agents.get(id) ?? (Info.empty(id) as Types.DeepMutable<Info>)
  48. if (!draft.agents.has(id)) draft.agents.set(id, current)
  49. fn(current)
  50. current.id = id
  51. },
  52. remove: (id) => {
  53. draft.agents.delete(id)
  54. },
  55. }),
  56. })
  57. const selectable = (agent: Info | undefined) =>
  58. agent && agent.mode !== "subagent" && !agent.hidden ? agent : undefined
  59. const selectedDefault = () => {
  60. const data = state.get()
  61. const configured = data.default ? selectable(data.agents.get(data.default)) : undefined
  62. if (configured) return configured
  63. const build = selectable(data.agents.get(ID.make("build")))
  64. if (build) return build
  65. for (const agent of data.agents.values()) {
  66. const fallback = selectable(agent)
  67. if (fallback) return fallback
  68. }
  69. }
  70. return Service.of({
  71. transform: state.transform,
  72. reload: state.reload,
  73. get: Effect.fn("AgentV2.get")(function* (id) {
  74. return state.get().agents.get(id)
  75. }),
  76. default: Effect.fn("AgentV2.default")(function* () {
  77. return selectedDefault()
  78. }),
  79. resolve: Effect.fn("AgentV2.resolve")(function* (id) {
  80. if (id !== undefined) return state.get().agents.get(ID.make(id))
  81. return selectedDefault()
  82. }),
  83. select: Effect.fn("AgentV2.select")(function* (id) {
  84. if (id !== undefined) {
  85. const selected = ID.make(id)
  86. return { id: selected, info: state.get().agents.get(selected) }
  87. }
  88. const info = selectedDefault()
  89. return { id: info?.id ?? defaultID, info }
  90. }),
  91. all: Effect.fn("AgentV2.all")(function* () {
  92. return Array.fromIterable(state.get().agents.values())
  93. }),
  94. })
  95. }),
  96. )
  97. export const locationLayer = layer
  98. export const node = makeLocationNode({ service: Service, layer, deps: [] })