agent.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. export * as AgentV2 from "./agent"
  2. import { Array, Context, Effect, Layer, Schema, Scope } from "effect"
  3. import { castDraft, enableMapSet, type Draft } from "immer"
  4. import { ModelV2 } from "./model"
  5. import { PermissionV2 } from "./permission"
  6. import { ProviderV2 } from "./provider"
  7. import { PositiveInt } from "./schema"
  8. import { State } from "./state"
  9. export const ID = Schema.String.pipe(Schema.brand("AgentV2.ID"))
  10. export type ID = typeof ID.Type
  11. export const Color = Schema.Union([
  12. Schema.String.check(Schema.isPattern(/^#[0-9a-fA-F]{6}$/)),
  13. Schema.Literals(["primary", "secondary", "accent", "success", "warning", "error", "info"]),
  14. ])
  15. export class Info extends Schema.Class<Info>("AgentV2.Info")({
  16. id: ID,
  17. model: ModelV2.Ref.pipe(Schema.optional),
  18. options: ProviderV2.Options,
  19. system: Schema.String.pipe(Schema.optional),
  20. description: Schema.String.pipe(Schema.optional),
  21. mode: Schema.Literals(["subagent", "primary", "all"]),
  22. hidden: Schema.Boolean,
  23. color: Color.pipe(Schema.optional),
  24. steps: PositiveInt.pipe(Schema.optional),
  25. permissions: PermissionV2.Ruleset,
  26. }) {
  27. static empty(id: ID) {
  28. return new Info({
  29. id,
  30. options: {
  31. headers: {},
  32. body: {},
  33. aisdk: {
  34. provider: {},
  35. request: {},
  36. },
  37. },
  38. mode: "all",
  39. hidden: false,
  40. permissions: [],
  41. })
  42. }
  43. }
  44. type Data = {
  45. agents: Map<ID, Info>
  46. }
  47. export type Editor = {
  48. list: () => readonly Info[]
  49. get: (id: ID) => Info | undefined
  50. update: (id: ID, fn: (agent: Draft<Info>) => void) => void
  51. remove: (id: ID) => void
  52. }
  53. export interface Interface {
  54. readonly transform: State.Interface<Data, Editor>["transform"]
  55. readonly update: (update: State.Transform<Editor>) => Effect.Effect<void, never, Scope.Scope>
  56. readonly get: (id: ID) => Effect.Effect<Info | undefined>
  57. readonly all: () => Effect.Effect<Info[]>
  58. }
  59. export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Agent") {}
  60. enableMapSet()
  61. export const layer = Layer.effect(
  62. Service,
  63. Effect.gen(function* () {
  64. const state = State.create<Data, Editor>({
  65. initial: () => ({ agents: new Map() }),
  66. editor: (draft) => ({
  67. list: () => Array.fromIterable(draft.agents.values()) as Info[],
  68. get: (id) => draft.agents.get(id),
  69. update: (id, fn) => {
  70. const current = draft.agents.get(id) ?? castDraft(Info.empty(id))
  71. if (!draft.agents.has(id)) draft.agents.set(id, current)
  72. fn(current)
  73. current.id = id
  74. },
  75. remove: (id) => {
  76. draft.agents.delete(id)
  77. },
  78. }),
  79. })
  80. return Service.of({
  81. transform: state.transform,
  82. update: Effect.fn("AgentV2.update")(function* (update) {
  83. const transform = yield* state.transform()
  84. yield* transform(update)
  85. }),
  86. get: Effect.fn("AgentV2.get")(function* (id) {
  87. return state.get().agents.get(id)
  88. }),
  89. all: Effect.fn("AgentV2.all")(function* () {
  90. return Array.fromIterable(state.get().agents.values())
  91. }),
  92. })
  93. }),
  94. )
  95. export const locationLayer = layer