sdk.ts 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import type { Ability, AbilityExecution, ExecutorContext, LoadedAbility, InputValues } from './types/index.js'
  2. import { loadAbilities, listAbilities } from './loader/index.js'
  3. import { validateAbility, validateInputs } from './validator/index.js'
  4. import { executeAbility, formatExecutionResult } from './executor/index.js'
  5. import { ExecutionManager } from './executor/execution-manager.js'
  6. export interface AbilitiesSDKOptions {
  7. projectDir?: string
  8. globalDir?: string
  9. includeGlobal?: boolean
  10. }
  11. export interface AbilityInfo {
  12. name: string
  13. description: string
  14. source: 'project' | 'global'
  15. triggers?: string[]
  16. inputCount: number
  17. stepCount: number
  18. }
  19. export interface ExecutionResult {
  20. id: string
  21. status: 'completed' | 'failed' | 'cancelled'
  22. ability: string
  23. duration: number
  24. steps: Array<{
  25. id: string
  26. status: string
  27. duration?: number
  28. output?: string
  29. error?: string
  30. }>
  31. error?: string
  32. formatted: string
  33. }
  34. export class AbilitiesSDK {
  35. private abilities: Map<string, LoadedAbility> = new Map()
  36. private executionManager: ExecutionManager
  37. private initialized = false
  38. private options: AbilitiesSDKOptions
  39. constructor(options: AbilitiesSDKOptions = {}) {
  40. this.options = options
  41. this.executionManager = new ExecutionManager()
  42. }
  43. async initialize(): Promise<void> {
  44. if (this.initialized) return
  45. const loaded = await loadAbilities({
  46. projectDir: this.options.projectDir,
  47. globalDir: this.options.globalDir,
  48. includeGlobal: this.options.includeGlobal ?? true,
  49. })
  50. for (const [name, ability] of loaded) {
  51. this.abilities.set(name, ability)
  52. }
  53. this.initialized = true
  54. }
  55. async list(): Promise<AbilityInfo[]> {
  56. await this.initialize()
  57. return listAbilities(this.abilities).map(item => ({
  58. name: item.name,
  59. description: item.description,
  60. source: item.source,
  61. triggers: item.triggers,
  62. inputCount: item.inputCount,
  63. stepCount: item.stepCount,
  64. }))
  65. }
  66. async get(name: string): Promise<Ability | undefined> {
  67. await this.initialize()
  68. return this.abilities.get(name)?.ability
  69. }
  70. async validate(name: string): Promise<{ valid: boolean; errors: string[] }> {
  71. await this.initialize()
  72. const loaded = this.abilities.get(name)
  73. if (!loaded) {
  74. return { valid: false, errors: [`Ability '${name}' not found`] }
  75. }
  76. const result = validateAbility(loaded.ability)
  77. return {
  78. valid: result.valid,
  79. errors: result.errors.map(e => `${e.path}: ${e.message}`),
  80. }
  81. }
  82. async execute(
  83. name: string,
  84. inputs: InputValues = {},
  85. context?: Partial<ExecutorContext>
  86. ): Promise<ExecutionResult> {
  87. await this.initialize()
  88. const loaded = this.abilities.get(name)
  89. if (!loaded) {
  90. return {
  91. id: '',
  92. status: 'failed',
  93. ability: name,
  94. duration: 0,
  95. steps: [],
  96. error: `Ability '${name}' not found`,
  97. formatted: `Error: Ability '${name}' not found`,
  98. }
  99. }
  100. const ability = loaded.ability
  101. const inputErrors = validateInputs(ability, inputs)
  102. if (inputErrors.length > 0) {
  103. return {
  104. id: '',
  105. status: 'failed',
  106. ability: name,
  107. duration: 0,
  108. steps: [],
  109. error: `Input validation failed: ${inputErrors.map(e => e.message).join(', ')}`,
  110. formatted: `Input validation failed:\n${inputErrors.map(e => `- ${e.message}`).join('\n')}`,
  111. }
  112. }
  113. const self = this
  114. const executorContext: ExecutorContext = {
  115. cwd: context?.cwd || process.cwd(),
  116. env: context?.env || {},
  117. agents: context?.agents,
  118. skills: context?.skills,
  119. approval: context?.approval,
  120. abilities: {
  121. get: (n: string) => self.abilities.get(n)?.ability,
  122. execute: async (a: Ability, i: InputValues) => {
  123. return executeAbility(a, i, executorContext)
  124. },
  125. },
  126. onStepStart: context?.onStepStart,
  127. onStepComplete: context?.onStepComplete,
  128. onStepFail: context?.onStepFail,
  129. }
  130. try {
  131. const execution = await this.executionManager.execute(ability, inputs, executorContext)
  132. return {
  133. id: execution.id,
  134. status: execution.status === 'completed' ? 'completed' : execution.status === 'cancelled' ? 'cancelled' : 'failed',
  135. ability: ability.name,
  136. duration: execution.completedAt ? execution.completedAt - execution.startedAt : 0,
  137. steps: execution.completedSteps.map(s => ({
  138. id: s.stepId,
  139. status: s.status,
  140. duration: s.duration,
  141. output: s.output,
  142. error: s.error,
  143. })),
  144. error: execution.error,
  145. formatted: formatExecutionResult(execution),
  146. }
  147. } catch (error) {
  148. return {
  149. id: '',
  150. status: 'failed',
  151. ability: name,
  152. duration: 0,
  153. steps: [],
  154. error: error instanceof Error ? error.message : String(error),
  155. formatted: `Execution error: ${error instanceof Error ? error.message : String(error)}`,
  156. }
  157. }
  158. }
  159. async status(executionId?: string): Promise<{
  160. active: boolean
  161. ability?: string
  162. currentStep?: string
  163. progress?: string
  164. status?: string
  165. }> {
  166. const execution = executionId
  167. ? this.executionManager.get(executionId)
  168. : this.executionManager.getActive()
  169. if (!execution) {
  170. return { active: false }
  171. }
  172. return {
  173. active: execution.status === 'running',
  174. ability: execution.ability.name,
  175. currentStep: execution.currentStep?.id,
  176. progress: `${execution.completedSteps.length}/${execution.ability.steps.length}`,
  177. status: execution.status,
  178. }
  179. }
  180. async cancel(executionId?: string): Promise<boolean> {
  181. if (executionId) {
  182. return this.executionManager.cancel(executionId)
  183. }
  184. return this.executionManager.cancelActive()
  185. }
  186. async waitFor(executionId: string, timeoutMs: number = 300000): Promise<ExecutionResult | null> {
  187. const startTime = Date.now()
  188. while (Date.now() - startTime < timeoutMs) {
  189. const execution = this.executionManager.get(executionId)
  190. if (!execution) {
  191. return null
  192. }
  193. if (execution.status !== 'running') {
  194. return {
  195. id: execution.id,
  196. status: execution.status === 'completed' ? 'completed' : execution.status === 'cancelled' ? 'cancelled' : 'failed',
  197. ability: execution.ability.name,
  198. duration: execution.completedAt ? execution.completedAt - execution.startedAt : 0,
  199. steps: execution.completedSteps.map(s => ({
  200. id: s.stepId,
  201. status: s.status,
  202. duration: s.duration,
  203. output: s.output,
  204. error: s.error,
  205. })),
  206. error: execution.error,
  207. formatted: formatExecutionResult(execution),
  208. }
  209. }
  210. await new Promise(resolve => setTimeout(resolve, 100))
  211. }
  212. return null
  213. }
  214. cleanup(): void {
  215. this.executionManager.cleanup()
  216. this.abilities.clear()
  217. this.initialized = false
  218. }
  219. }
  220. export function createAbilitiesSDK(options?: AbilitiesSDKOptions): AbilitiesSDK {
  221. return new AbilitiesSDK(options)
  222. }
  223. export { loadAbilities, listAbilities, validateAbility, validateInputs, executeAbility, formatExecutionResult }
  224. export type { Ability, AbilityExecution, ExecutorContext, LoadedAbility, InputValues }