agent.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  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 { PermissionSchema } from "./permission/schema"
  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. request: ProviderV2.Request,
  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: PermissionSchema.Ruleset,
  26. }) {
  27. static empty(id: ID) {
  28. return new Info({
  29. id,
  30. request: {
  31. headers: {},
  32. body: {},
  33. },
  34. mode: "all",
  35. hidden: false,
  36. permissions: [],
  37. })
  38. }
  39. }
  40. type Data = {
  41. agents: Map<ID, Info>
  42. }
  43. export type Editor = {
  44. list: () => readonly Info[]
  45. get: (id: ID) => Info | undefined
  46. update: (id: ID, fn: (agent: Draft<Info>) => void) => void
  47. remove: (id: ID) => void
  48. }
  49. export interface Interface {
  50. readonly transform: State.Interface<Data, Editor>["transform"]
  51. readonly update: (update: State.Transform<Editor>) => Effect.Effect<void, never, Scope.Scope>
  52. readonly get: (id: ID) => Effect.Effect<Info | undefined>
  53. readonly all: () => Effect.Effect<Info[]>
  54. }
  55. export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Agent") {}
  56. enableMapSet()
  57. export const layer = Layer.effect(
  58. Service,
  59. Effect.gen(function* () {
  60. const state = State.create<Data, Editor>({
  61. initial: () => ({ agents: new Map() }),
  62. editor: (draft) => ({
  63. list: () => Array.fromIterable(draft.agents.values()) as Info[],
  64. get: (id) => draft.agents.get(id),
  65. update: (id, fn) => {
  66. const current = draft.agents.get(id) ?? castDraft(Info.empty(id))
  67. if (!draft.agents.has(id)) draft.agents.set(id, current)
  68. fn(current)
  69. current.id = id
  70. },
  71. remove: (id) => {
  72. draft.agents.delete(id)
  73. },
  74. }),
  75. })
  76. return Service.of({
  77. transform: state.transform,
  78. update: Effect.fn("AgentV2.update")(function* (update) {
  79. const transform = yield* state.transform()
  80. yield* transform(update)
  81. }),
  82. get: Effect.fn("AgentV2.get")(function* (id) {
  83. return state.get().agents.get(id)
  84. }),
  85. all: Effect.fn("AgentV2.all")(function* () {
  86. return Array.fromIterable(state.get().agents.values())
  87. }),
  88. })
  89. }),
  90. )
  91. export const locationLayer = layer