@codeany/open-agent-sdk 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/README.md +144 -31
  2. package/dist/agent.d.ts +113 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +446 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/engine.d.ts +62 -0
  7. package/dist/engine.d.ts.map +1 -0
  8. package/dist/engine.js +494 -0
  9. package/dist/engine.js.map +1 -0
  10. package/dist/hooks.d.ts +111 -0
  11. package/dist/hooks.d.ts.map +1 -0
  12. package/dist/hooks.js +179 -0
  13. package/dist/hooks.js.map +1 -0
  14. package/dist/index.d.ts +66 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +133 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/mcp/client.d.ts +19 -0
  19. package/dist/mcp/client.d.ts.map +1 -0
  20. package/dist/mcp/client.js +126 -0
  21. package/dist/mcp/client.js.map +1 -0
  22. package/dist/providers/anthropic.d.ts +17 -0
  23. package/dist/providers/anthropic.d.ts.map +1 -0
  24. package/dist/providers/anthropic.js +47 -0
  25. package/dist/providers/anthropic.js.map +1 -0
  26. package/dist/providers/index.d.ts +20 -0
  27. package/dist/providers/index.d.ts.map +1 -0
  28. package/dist/providers/index.js +26 -0
  29. package/dist/providers/index.js.map +1 -0
  30. package/dist/providers/openai.d.ts +26 -0
  31. package/dist/providers/openai.d.ts.map +1 -0
  32. package/dist/providers/openai.js +212 -0
  33. package/dist/providers/openai.js.map +1 -0
  34. package/dist/providers/types.d.ts +84 -0
  35. package/dist/providers/types.d.ts.map +1 -0
  36. package/dist/providers/types.js +11 -0
  37. package/dist/providers/types.js.map +1 -0
  38. package/dist/sdk-mcp-server.d.ts +52 -0
  39. package/dist/sdk-mcp-server.d.ts.map +1 -0
  40. package/dist/sdk-mcp-server.js +57 -0
  41. package/dist/sdk-mcp-server.js.map +1 -0
  42. package/dist/session.d.ts +73 -0
  43. package/dist/session.d.ts.map +1 -0
  44. package/dist/session.js +159 -0
  45. package/dist/session.js.map +1 -0
  46. package/dist/skills/bundled/commit.d.ts +7 -0
  47. package/dist/skills/bundled/commit.d.ts.map +1 -0
  48. package/dist/skills/bundled/commit.js +35 -0
  49. package/dist/skills/bundled/commit.js.map +1 -0
  50. package/dist/skills/bundled/debug.d.ts +7 -0
  51. package/dist/skills/bundled/debug.d.ts.map +1 -0
  52. package/dist/skills/bundled/debug.js +46 -0
  53. package/dist/skills/bundled/debug.js.map +1 -0
  54. package/dist/skills/bundled/index.d.ts +11 -0
  55. package/dist/skills/bundled/index.d.ts.map +1 -0
  56. package/dist/skills/bundled/index.js +26 -0
  57. package/dist/skills/bundled/index.js.map +1 -0
  58. package/dist/skills/bundled/review.d.ts +7 -0
  59. package/dist/skills/bundled/review.d.ts.map +1 -0
  60. package/dist/skills/bundled/review.js +38 -0
  61. package/dist/skills/bundled/review.js.map +1 -0
  62. package/dist/skills/bundled/simplify.d.ts +8 -0
  63. package/dist/skills/bundled/simplify.d.ts.map +1 -0
  64. package/dist/skills/bundled/simplify.js +48 -0
  65. package/dist/skills/bundled/simplify.js.map +1 -0
  66. package/dist/skills/bundled/test.d.ts +7 -0
  67. package/dist/skills/bundled/test.d.ts.map +1 -0
  68. package/dist/skills/bundled/test.js +40 -0
  69. package/dist/skills/bundled/test.js.map +1 -0
  70. package/dist/skills/index.d.ts +7 -0
  71. package/dist/skills/index.d.ts.map +1 -0
  72. package/dist/skills/index.js +8 -0
  73. package/dist/skills/index.js.map +1 -0
  74. package/dist/skills/registry.d.ts +43 -0
  75. package/dist/skills/registry.d.ts.map +1 -0
  76. package/dist/skills/registry.js +111 -0
  77. package/dist/skills/registry.js.map +1 -0
  78. package/dist/skills/types.d.ts +83 -0
  79. package/dist/skills/types.d.ts.map +1 -0
  80. package/dist/skills/types.js +8 -0
  81. package/dist/skills/types.js.map +1 -0
  82. package/dist/tool-helper.d.ts +73 -0
  83. package/dist/tool-helper.d.ts.map +1 -0
  84. package/dist/tool-helper.js +86 -0
  85. package/dist/tool-helper.js.map +1 -0
  86. package/dist/tools/agent-tool.d.ts +17 -0
  87. package/dist/tools/agent-tool.d.ts.map +1 -0
  88. package/dist/tools/agent-tool.js +146 -0
  89. package/dist/tools/agent-tool.js.map +1 -0
  90. package/dist/tools/ask-user.d.ts +18 -0
  91. package/dist/tools/ask-user.d.ts.map +1 -0
  92. package/dist/tools/ask-user.js +72 -0
  93. package/dist/tools/ask-user.js.map +1 -0
  94. package/dist/tools/bash.d.ts +5 -0
  95. package/dist/tools/bash.d.ts.map +1 -0
  96. package/dist/tools/bash.js +67 -0
  97. package/dist/tools/bash.js.map +1 -0
  98. package/dist/tools/config-tool.d.ts +20 -0
  99. package/dist/tools/config-tool.d.ts.map +1 -0
  100. package/dist/tools/config-tool.js +83 -0
  101. package/dist/tools/config-tool.js.map +1 -0
  102. package/dist/tools/cron-tools.d.ts +33 -0
  103. package/dist/tools/cron-tools.d.ts.map +1 -0
  104. package/dist/tools/cron-tools.js +128 -0
  105. package/dist/tools/cron-tools.js.map +1 -0
  106. package/dist/tools/edit.d.ts +5 -0
  107. package/dist/tools/edit.d.ts.map +1 -0
  108. package/dist/tools/edit.js +70 -0
  109. package/dist/tools/edit.js.map +1 -0
  110. package/dist/tools/glob.d.ts +5 -0
  111. package/dist/tools/glob.d.ts.map +1 -0
  112. package/dist/tools/glob.js +75 -0
  113. package/dist/tools/glob.js.map +1 -0
  114. package/dist/tools/grep.d.ts +5 -0
  115. package/dist/tools/grep.d.ts.map +1 -0
  116. package/dist/tools/grep.js +168 -0
  117. package/dist/tools/grep.js.map +1 -0
  118. package/dist/tools/index.d.ts +45 -0
  119. package/dist/tools/index.d.ts.map +1 -0
  120. package/dist/tools/index.js +162 -0
  121. package/dist/tools/index.js.map +1 -0
  122. package/dist/tools/lsp-tool.d.ts +9 -0
  123. package/dist/tools/lsp-tool.d.ts.map +1 -0
  124. package/dist/tools/lsp-tool.js +137 -0
  125. package/dist/tools/lsp-tool.js.map +1 -0
  126. package/dist/tools/mcp-resource-tools.d.ts +14 -0
  127. package/dist/tools/mcp-resource-tools.d.ts.map +1 -0
  128. package/dist/tools/mcp-resource-tools.js +117 -0
  129. package/dist/tools/mcp-resource-tools.js.map +1 -0
  130. package/dist/tools/notebook-edit.d.ts +5 -0
  131. package/dist/tools/notebook-edit.d.ts.map +1 -0
  132. package/dist/tools/notebook-edit.js +85 -0
  133. package/dist/tools/notebook-edit.js.map +1 -0
  134. package/dist/tools/plan-tools.d.ts +12 -0
  135. package/dist/tools/plan-tools.d.ts.map +1 -0
  136. package/dist/tools/plan-tools.js +77 -0
  137. package/dist/tools/plan-tools.js.map +1 -0
  138. package/dist/tools/read.d.ts +5 -0
  139. package/dist/tools/read.d.ts.map +1 -0
  140. package/dist/tools/read.js +66 -0
  141. package/dist/tools/read.js.map +1 -0
  142. package/dist/tools/send-message.d.ts +31 -0
  143. package/dist/tools/send-message.d.ts.map +1 -0
  144. package/dist/tools/send-message.js +77 -0
  145. package/dist/tools/send-message.js.map +1 -0
  146. package/dist/tools/skill-tool.d.ts +9 -0
  147. package/dist/tools/skill-tool.d.ts.map +1 -0
  148. package/dist/tools/skill-tool.js +115 -0
  149. package/dist/tools/skill-tool.js.map +1 -0
  150. package/dist/tools/task-tools.d.ts +48 -0
  151. package/dist/tools/task-tools.d.ts.map +1 -0
  152. package/dist/tools/task-tools.js +242 -0
  153. package/dist/tools/task-tools.js.map +1 -0
  154. package/dist/tools/team-tools.d.ts +34 -0
  155. package/dist/tools/team-tools.d.ts.map +1 -0
  156. package/dist/tools/team-tools.js +103 -0
  157. package/dist/tools/team-tools.js.map +1 -0
  158. package/dist/tools/todo-tool.d.ts +22 -0
  159. package/dist/tools/todo-tool.d.ts.map +1 -0
  160. package/dist/tools/todo-tool.js +93 -0
  161. package/dist/tools/todo-tool.js.map +1 -0
  162. package/dist/tools/tool-search.d.ts +13 -0
  163. package/dist/tools/tool-search.d.ts.map +1 -0
  164. package/dist/tools/tool-search.js +76 -0
  165. package/dist/tools/tool-search.js.map +1 -0
  166. package/dist/tools/types.d.ts +29 -0
  167. package/dist/tools/types.d.ts.map +1 -0
  168. package/dist/tools/types.js +52 -0
  169. package/dist/tools/types.js.map +1 -0
  170. package/dist/tools/web-fetch.d.ts +5 -0
  171. package/dist/tools/web-fetch.d.ts.map +1 -0
  172. package/dist/tools/web-fetch.js +60 -0
  173. package/dist/tools/web-fetch.js.map +1 -0
  174. package/dist/tools/web-search.d.ts +5 -0
  175. package/dist/tools/web-search.d.ts.map +1 -0
  176. package/dist/tools/web-search.js +77 -0
  177. package/dist/tools/web-search.js.map +1 -0
  178. package/dist/tools/worktree-tools.d.ts +10 -0
  179. package/dist/tools/worktree-tools.d.ts.map +1 -0
  180. package/dist/tools/worktree-tools.js +130 -0
  181. package/dist/tools/worktree-tools.js.map +1 -0
  182. package/dist/tools/write.d.ts +5 -0
  183. package/dist/tools/write.d.ts.map +1 -0
  184. package/dist/tools/write.js +40 -0
  185. package/dist/tools/write.js.map +1 -0
  186. package/dist/types.d.ts +423 -0
  187. package/dist/types.d.ts.map +1 -0
  188. package/dist/types.js +5 -0
  189. package/dist/types.js.map +1 -0
  190. package/dist/utils/compact.d.ts +44 -0
  191. package/dist/utils/compact.d.ts.map +1 -0
  192. package/dist/utils/compact.js +162 -0
  193. package/dist/utils/compact.js.map +1 -0
  194. package/dist/utils/context.d.ts +35 -0
  195. package/dist/utils/context.d.ts.map +1 -0
  196. package/dist/utils/context.js +174 -0
  197. package/dist/utils/context.js.map +1 -0
  198. package/dist/utils/fileCache.d.ts +64 -0
  199. package/dist/utils/fileCache.d.ts.map +1 -0
  200. package/dist/utils/fileCache.js +117 -0
  201. package/dist/utils/fileCache.js.map +1 -0
  202. package/dist/utils/messages.d.ts +57 -0
  203. package/dist/utils/messages.d.ts.map +1 -0
  204. package/dist/utils/messages.js +152 -0
  205. package/dist/utils/messages.js.map +1 -0
  206. package/dist/utils/retry.d.ts +48 -0
  207. package/dist/utils/retry.d.ts.map +1 -0
  208. package/dist/utils/retry.js +111 -0
  209. package/dist/utils/retry.js.map +1 -0
  210. package/dist/utils/tokens.d.ts +57 -0
  211. package/dist/utils/tokens.d.ts.map +1 -0
  212. package/dist/utils/tokens.js +134 -0
  213. package/dist/utils/tokens.js.map +1 -0
  214. package/package.json +7 -1
  215. package/src/agent.ts +106 -15
  216. package/src/engine.ts +169 -59
  217. package/src/index.ts +51 -1
  218. package/src/providers/anthropic.ts +60 -0
  219. package/src/providers/index.ts +34 -0
  220. package/src/providers/openai.ts +315 -0
  221. package/src/providers/types.ts +85 -0
  222. package/src/session.ts +5 -5
  223. package/src/skills/bundled/commit.ts +38 -0
  224. package/src/skills/bundled/debug.ts +48 -0
  225. package/src/skills/bundled/index.ts +28 -0
  226. package/src/skills/bundled/review.ts +41 -0
  227. package/src/skills/bundled/simplify.ts +51 -0
  228. package/src/skills/bundled/test.ts +43 -0
  229. package/src/skills/index.ts +25 -0
  230. package/src/skills/registry.ts +133 -0
  231. package/src/skills/types.ts +99 -0
  232. package/src/tools/agent-tool.ts +13 -2
  233. package/src/tools/index.ts +8 -0
  234. package/src/tools/skill-tool.ts +133 -0
  235. package/src/tools/types.ts +7 -3
  236. package/src/types.ts +35 -8
  237. package/src/utils/compact.ts +18 -17
  238. package/src/utils/messages.ts +12 -13
  239. package/src/utils/tokens.ts +29 -6
  240. package/.env.example +0 -8
  241. package/examples/01-simple-query.ts +0 -43
  242. package/examples/02-multi-tool.ts +0 -44
  243. package/examples/03-multi-turn.ts +0 -39
  244. package/examples/04-prompt-api.ts +0 -29
  245. package/examples/05-custom-system-prompt.ts +0 -26
  246. package/examples/06-mcp-server.ts +0 -49
  247. package/examples/07-custom-tools.ts +0 -87
  248. package/examples/08-official-api-compat.ts +0 -38
  249. package/examples/09-subagents.ts +0 -48
  250. package/examples/10-permissions.ts +0 -40
  251. package/examples/11-custom-mcp-tools.ts +0 -101
  252. package/examples/web/index.html +0 -365
  253. package/examples/web/server.ts +0 -157
  254. package/tsconfig.json +0 -19
package/src/engine.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Manages the full conversation lifecycle:
5
5
  * 1. Take user prompt
6
6
  * 2. Build system prompt with context (git status, project context, tools)
7
- * 3. Call LLM API with tools
7
+ * 3. Call LLM API with tools (via provider abstraction)
8
8
  * 4. Stream response
9
9
  * 5. Execute tool calls (concurrent for read-only, serial for mutations)
10
10
  * 6. Send results back, repeat until done
@@ -12,7 +12,6 @@
12
12
  * 8. Retry with exponential backoff on transient errors
13
13
  */
14
14
 
15
- import Anthropic from '@anthropic-ai/sdk'
16
15
  import type {
17
16
  SDKMessage,
18
17
  QueryEngineConfig,
@@ -21,7 +20,12 @@ import type {
21
20
  ToolContext,
22
21
  TokenUsage,
23
22
  } from './types.js'
24
- import { toApiTool } from './tools/types.js'
23
+ import type {
24
+ LLMProvider,
25
+ CreateMessageResponse,
26
+ NormalizedMessageParam,
27
+ NormalizedTool,
28
+ } from './providers/types.js'
25
29
  import {
26
30
  estimateMessagesTokens,
27
31
  estimateCost,
@@ -37,10 +41,34 @@ import {
37
41
  import {
38
42
  withRetry,
39
43
  isPromptTooLongError,
40
- formatApiError,
41
44
  } from './utils/retry.js'
42
45
  import { getSystemContext, getUserContext } from './utils/context.js'
43
46
  import { normalizeMessagesForAPI } from './utils/messages.js'
47
+ import type { HookRegistry, HookInput, HookOutput } from './hooks.js'
48
+
49
+ // ============================================================================
50
+ // Tool format conversion
51
+ // ============================================================================
52
+
53
+ /** Convert a ToolDefinition to the normalized provider tool format. */
54
+ function toProviderTool(tool: ToolDefinition): NormalizedTool {
55
+ return {
56
+ name: tool.name,
57
+ description: tool.description,
58
+ input_schema: tool.inputSchema,
59
+ }
60
+ }
61
+
62
+ // ============================================================================
63
+ // ToolUseBlock (internal type for extracted tool_use blocks)
64
+ // ============================================================================
65
+
66
+ interface ToolUseBlock {
67
+ type: 'tool_use'
68
+ id: string
69
+ name: string
70
+ input: any
71
+ }
44
72
 
45
73
  // ============================================================================
46
74
  // System Prompt Builder
@@ -113,23 +141,43 @@ async function buildSystemPrompt(config: QueryEngineConfig): Promise<string> {
113
141
 
114
142
  export class QueryEngine {
115
143
  private config: QueryEngineConfig
116
- private client: Anthropic
117
- public messages: Anthropic.MessageParam[] = []
144
+ private provider: LLMProvider
145
+ public messages: NormalizedMessageParam[] = []
118
146
  private totalUsage: TokenUsage = { input_tokens: 0, output_tokens: 0 }
119
147
  private totalCost = 0
120
148
  private turnCount = 0
121
149
  private compactState: AutoCompactState
122
150
  private sessionId: string
123
151
  private apiTimeMs = 0
152
+ private hookRegistry?: HookRegistry
124
153
 
125
154
  constructor(config: QueryEngineConfig) {
126
155
  this.config = config
127
- this.client = new Anthropic({
128
- apiKey: config.apiKey,
129
- baseURL: config.baseURL,
130
- })
156
+ this.provider = config.provider
131
157
  this.compactState = createAutoCompactState()
132
- this.sessionId = crypto.randomUUID()
158
+ this.sessionId = config.sessionId || crypto.randomUUID()
159
+ this.hookRegistry = config.hookRegistry
160
+ }
161
+
162
+ /**
163
+ * Execute hooks for a lifecycle event.
164
+ * Returns hook outputs; never throws.
165
+ */
166
+ private async executeHooks(
167
+ event: import('./hooks.js').HookEvent,
168
+ extra?: Partial<HookInput>,
169
+ ): Promise<HookOutput[]> {
170
+ if (!this.hookRegistry?.hasHooks(event)) return []
171
+ try {
172
+ return await this.hookRegistry.execute(event, {
173
+ event,
174
+ sessionId: this.sessionId,
175
+ cwd: this.config.cwd,
176
+ ...extra,
177
+ })
178
+ } catch {
179
+ return []
180
+ }
133
181
  }
134
182
 
135
183
  /**
@@ -137,13 +185,34 @@ export class QueryEngine {
137
185
  * Yields SDKMessage events as the agent works.
138
186
  */
139
187
  async *submitMessage(
140
- prompt: string | Anthropic.ContentBlockParam[],
188
+ prompt: string | any[],
141
189
  ): AsyncGenerator<SDKMessage> {
190
+ // Hook: SessionStart
191
+ await this.executeHooks('SessionStart')
192
+
193
+ // Hook: UserPromptSubmit
194
+ const userHookResults = await this.executeHooks('UserPromptSubmit', {
195
+ toolInput: prompt,
196
+ })
197
+ // Check if any hook blocks the submission
198
+ if (userHookResults.some((r) => r.block)) {
199
+ yield {
200
+ type: 'result',
201
+ subtype: 'error_during_execution',
202
+ is_error: true,
203
+ usage: this.totalUsage,
204
+ num_turns: 0,
205
+ cost: 0,
206
+ errors: ['Blocked by UserPromptSubmit hook'],
207
+ }
208
+ return
209
+ }
210
+
142
211
  // Add user message
143
- this.messages.push({ role: 'user', content: prompt })
212
+ this.messages.push({ role: 'user', content: prompt as any })
144
213
 
145
- // Build tool definitions for API
146
- const tools = this.config.tools.map(toApiTool)
214
+ // Build tool definitions for provider
215
+ const tools = this.config.tools.map(toProviderTool)
147
216
 
148
217
  // Build system prompt
149
218
  const systemPrompt = await buildSystemPrompt(this.config)
@@ -176,16 +245,18 @@ export class QueryEngine {
176
245
  }
177
246
 
178
247
  // Auto-compact if context is too large
179
- if (shouldAutoCompact(this.messages, this.config.model, this.compactState)) {
248
+ if (shouldAutoCompact(this.messages as any[], this.config.model, this.compactState)) {
249
+ await this.executeHooks('PreCompact')
180
250
  try {
181
251
  const result = await compactConversation(
182
- this.client,
252
+ this.provider,
183
253
  this.config.model,
184
- this.messages,
254
+ this.messages as any[],
185
255
  this.compactState,
186
256
  )
187
- this.messages = result.compactedMessages
257
+ this.messages = result.compactedMessages as NormalizedMessageParam[]
188
258
  this.compactState = result.state
259
+ await this.executeHooks('PostCompact')
189
260
  } catch {
190
261
  // Continue with uncompacted messages
191
262
  }
@@ -193,38 +264,33 @@ export class QueryEngine {
193
264
 
194
265
  // Micro-compact: truncate large tool results
195
266
  const apiMessages = microCompactMessages(
196
- normalizeMessagesForAPI(this.messages),
197
- )
267
+ normalizeMessagesForAPI(this.messages as any[]),
268
+ ) as NormalizedMessageParam[]
198
269
 
199
270
  this.turnCount++
200
271
  turnsRemaining--
201
272
 
202
- // Make API call with retry
203
- let response: Anthropic.Message
273
+ // Make API call with retry via provider
274
+ let response: CreateMessageResponse
204
275
  const apiStart = performance.now()
205
276
  try {
206
277
  response = await withRetry(
207
278
  async () => {
208
- const requestParams: Anthropic.MessageCreateParamsNonStreaming = {
279
+ return this.provider.createMessage({
209
280
  model: this.config.model,
210
- max_tokens: this.config.maxTokens,
281
+ maxTokens: this.config.maxTokens,
211
282
  system: systemPrompt,
212
283
  messages: apiMessages,
213
284
  tools: tools.length > 0 ? tools : undefined,
214
- }
215
-
216
- // Add thinking if configured
217
- if (
218
- this.config.thinking?.type === 'enabled' &&
219
- this.config.thinking.budgetTokens
220
- ) {
221
- (requestParams as any).thinking = {
222
- type: 'enabled',
223
- budget_tokens: this.config.thinking.budgetTokens,
224
- }
225
- }
226
-
227
- return this.client.messages.create(requestParams)
285
+ thinking:
286
+ this.config.thinking?.type === 'enabled' &&
287
+ this.config.thinking.budgetTokens
288
+ ? {
289
+ type: 'enabled',
290
+ budget_tokens: this.config.thinking.budgetTokens,
291
+ }
292
+ : undefined,
293
+ })
228
294
  },
229
295
  undefined,
230
296
  this.config.abortSignal,
@@ -234,12 +300,12 @@ export class QueryEngine {
234
300
  if (isPromptTooLongError(err) && !this.compactState.compacted) {
235
301
  try {
236
302
  const result = await compactConversation(
237
- this.client,
303
+ this.provider,
238
304
  this.config.model,
239
- this.messages,
305
+ this.messages as any[],
240
306
  this.compactState,
241
307
  )
242
- this.messages = result.compactedMessages
308
+ this.messages = result.compactedMessages as NormalizedMessageParam[]
243
309
  this.compactState = result.state
244
310
  turnsRemaining++ // Retry this turn
245
311
  this.turnCount--
@@ -262,38 +328,38 @@ export class QueryEngine {
262
328
  // Track API timing
263
329
  this.apiTimeMs += performance.now() - apiStart
264
330
 
265
- // Track usage
331
+ // Track usage (normalized by provider)
266
332
  if (response.usage) {
267
333
  this.totalUsage.input_tokens += response.usage.input_tokens
268
334
  this.totalUsage.output_tokens += response.usage.output_tokens
269
- if ('cache_creation_input_tokens' in response.usage) {
335
+ if (response.usage.cache_creation_input_tokens) {
270
336
  this.totalUsage.cache_creation_input_tokens =
271
337
  (this.totalUsage.cache_creation_input_tokens || 0) +
272
- ((response.usage as any).cache_creation_input_tokens || 0)
338
+ response.usage.cache_creation_input_tokens
273
339
  }
274
- if ('cache_read_input_tokens' in response.usage) {
340
+ if (response.usage.cache_read_input_tokens) {
275
341
  this.totalUsage.cache_read_input_tokens =
276
342
  (this.totalUsage.cache_read_input_tokens || 0) +
277
- ((response.usage as any).cache_read_input_tokens || 0)
343
+ response.usage.cache_read_input_tokens
278
344
  }
279
- this.totalCost += estimateCost(this.config.model, response.usage as TokenUsage)
345
+ this.totalCost += estimateCost(this.config.model, response.usage)
280
346
  }
281
347
 
282
348
  // Add assistant message to conversation
283
- this.messages.push({ role: 'assistant', content: response.content })
349
+ this.messages.push({ role: 'assistant', content: response.content as any })
284
350
 
285
351
  // Yield assistant message
286
352
  yield {
287
353
  type: 'assistant',
288
354
  message: {
289
355
  role: 'assistant',
290
- content: response.content,
356
+ content: response.content as any,
291
357
  },
292
358
  }
293
359
 
294
360
  // Handle max_output_tokens recovery
295
361
  if (
296
- response.stop_reason === 'max_tokens' &&
362
+ response.stopReason === 'max_tokens' &&
297
363
  maxOutputRecoveryAttempts < MAX_OUTPUT_RECOVERY
298
364
  ) {
299
365
  maxOutputRecoveryAttempts++
@@ -307,7 +373,7 @@ export class QueryEngine {
307
373
 
308
374
  // Check for tool use
309
375
  const toolUseBlocks = response.content.filter(
310
- (block): block is Anthropic.ToolUseBlock => block.type === 'tool_use',
376
+ (block): block is ToolUseBlock => block.type === 'tool_use',
311
377
  )
312
378
 
313
379
  if (toolUseBlocks.length === 0) {
@@ -349,9 +415,15 @@ export class QueryEngine {
349
415
  })),
350
416
  })
351
417
 
352
- if (response.stop_reason === 'end_turn') break
418
+ if (response.stopReason === 'end_turn') break
353
419
  }
354
420
 
421
+ // Hook: Stop (end of agentic loop)
422
+ await this.executeHooks('Stop')
423
+
424
+ // Hook: SessionEnd
425
+ await this.executeHooks('SessionEnd')
426
+
355
427
  // Yield enriched final result
356
428
  const endSubtype = budgetExceeded
357
429
  ? 'error_max_budget_usd'
@@ -380,11 +452,14 @@ export class QueryEngine {
380
452
  * Mutation tools run sequentially.
381
453
  */
382
454
  private async executeTools(
383
- toolUseBlocks: Anthropic.ToolUseBlock[],
455
+ toolUseBlocks: ToolUseBlock[],
384
456
  ): Promise<(ToolResult & { tool_name?: string })[]> {
385
457
  const context: ToolContext = {
386
458
  cwd: this.config.cwd,
387
459
  abortSignal: this.config.abortSignal,
460
+ provider: this.provider,
461
+ model: this.config.model,
462
+ apiType: this.provider.apiType,
388
463
  }
389
464
 
390
465
  const MAX_CONCURRENCY = parseInt(
@@ -392,8 +467,8 @@ export class QueryEngine {
392
467
  )
393
468
 
394
469
  // Partition into read-only (concurrent) and mutation (serial)
395
- const readOnly: Array<{ block: Anthropic.ToolUseBlock; tool?: ToolDefinition }> = []
396
- const mutations: Array<{ block: Anthropic.ToolUseBlock; tool?: ToolDefinition }> = []
470
+ const readOnly: Array<{ block: ToolUseBlock; tool?: ToolDefinition }> = []
471
+ const mutations: Array<{ block: ToolUseBlock; tool?: ToolDefinition }> = []
397
472
 
398
473
  for (const block of toolUseBlocks) {
399
474
  const tool = this.config.tools.find((t) => t.name === block.name)
@@ -430,7 +505,7 @@ export class QueryEngine {
430
505
  * Execute a single tool with permission checking.
431
506
  */
432
507
  private async executeSingleTool(
433
- block: Anthropic.ToolUseBlock,
508
+ block: ToolUseBlock,
434
509
  tool: ToolDefinition | undefined,
435
510
  context: ToolContext,
436
511
  ): Promise<ToolResult & { tool_name?: string }> {
@@ -469,7 +544,7 @@ export class QueryEngine {
469
544
  }
470
545
  }
471
546
  if (permission.updatedInput !== undefined) {
472
- block = { ...block, input: permission.updatedInput as Record<string, unknown> }
547
+ block = { ...block, input: permission.updatedInput }
473
548
  }
474
549
  } catch (err: any) {
475
550
  return {
@@ -482,11 +557,46 @@ export class QueryEngine {
482
557
  }
483
558
  }
484
559
 
560
+ // Hook: PreToolUse
561
+ const preHookResults = await this.executeHooks('PreToolUse', {
562
+ toolName: block.name,
563
+ toolInput: block.input,
564
+ toolUseId: block.id,
565
+ })
566
+ // Check if any hook blocks this tool
567
+ if (preHookResults.some((r) => r.block)) {
568
+ const msg = preHookResults.find((r) => r.message)?.message || 'Blocked by PreToolUse hook'
569
+ return {
570
+ type: 'tool_result',
571
+ tool_use_id: block.id,
572
+ content: msg,
573
+ is_error: true,
574
+ tool_name: block.name,
575
+ }
576
+ }
577
+
485
578
  // Execute the tool
486
579
  try {
487
580
  const result = await tool.call(block.input, context)
581
+
582
+ // Hook: PostToolUse
583
+ await this.executeHooks('PostToolUse', {
584
+ toolName: block.name,
585
+ toolInput: block.input,
586
+ toolOutput: typeof result.content === 'string' ? result.content : JSON.stringify(result.content),
587
+ toolUseId: block.id,
588
+ })
589
+
488
590
  return { ...result, tool_use_id: block.id, tool_name: block.name }
489
591
  } catch (err: any) {
592
+ // Hook: PostToolUseFailure
593
+ await this.executeHooks('PostToolUseFailure', {
594
+ toolName: block.name,
595
+ toolInput: block.input,
596
+ toolUseId: block.id,
597
+ error: err.message,
598
+ })
599
+
490
600
  return {
491
601
  type: 'tool_result',
492
602
  tool_use_id: block.id,
@@ -500,7 +610,7 @@ export class QueryEngine {
500
610
  /**
501
611
  * Get current messages for session persistence.
502
612
  */
503
- getMessages(): Anthropic.MessageParam[] {
613
+ getMessages(): NormalizedMessageParam[] {
504
614
  return [...this.messages]
505
615
  }
506
616
 
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * Features:
8
8
  * - 30+ built-in tools (file I/O, shell, web, agents, tasks, teams, etc.)
9
+ * - Skill system (reusable prompt templates with bundled skills)
9
10
  * - MCP server integration (stdio, SSE, HTTP)
10
11
  * - Context compression (auto-compact, micro-compact)
11
12
  * - Retry with exponential backoff
@@ -14,7 +15,7 @@
14
15
  * - Permission system (allow/deny/bypass modes)
15
16
  * - Subagent spawning & team coordination
16
17
  * - Task management & scheduling
17
- * - Hook system (pre/post tool use, lifecycle events)
18
+ * - Hook system with lifecycle integration (pre/post tool use, session, compact)
18
19
  * - Token estimation & cost tracking
19
20
  * - File state LRU caching
20
21
  * - Plan mode for structured workflows
@@ -50,6 +51,26 @@ export type { McpSdkServerConfig } from './sdk-mcp-server.js'
50
51
 
51
52
  export { QueryEngine } from './engine.js'
52
53
 
54
+ // --------------------------------------------------------------------------
55
+ // LLM Providers (Anthropic + OpenAI)
56
+ // --------------------------------------------------------------------------
57
+
58
+ export {
59
+ createProvider,
60
+ AnthropicProvider,
61
+ OpenAIProvider,
62
+ } from './providers/index.js'
63
+ export type {
64
+ ApiType,
65
+ LLMProvider,
66
+ CreateMessageParams,
67
+ CreateMessageResponse,
68
+ NormalizedMessageParam,
69
+ NormalizedContentBlock,
70
+ NormalizedTool,
71
+ NormalizedResponseBlock,
72
+ } from './providers/index.js'
73
+
53
74
  // --------------------------------------------------------------------------
54
75
  // Tool System (30+ tools)
55
76
  // --------------------------------------------------------------------------
@@ -123,6 +144,9 @@ export {
123
144
 
124
145
  // Todo
125
146
  TodoWriteTool,
147
+
148
+ // Skill
149
+ SkillTool,
126
150
  } from './tools/index.js'
127
151
 
128
152
  // --------------------------------------------------------------------------
@@ -132,6 +156,27 @@ export {
132
156
  export { connectMCPServer, closeAllConnections } from './mcp/client.js'
133
157
  export type { MCPConnection } from './mcp/client.js'
134
158
 
159
+ // --------------------------------------------------------------------------
160
+ // Skill System
161
+ // --------------------------------------------------------------------------
162
+
163
+ export {
164
+ registerSkill,
165
+ getSkill,
166
+ getAllSkills,
167
+ getUserInvocableSkills,
168
+ hasSkill,
169
+ unregisterSkill,
170
+ clearSkills,
171
+ formatSkillsForPrompt,
172
+ initBundledSkills,
173
+ } from './skills/index.js'
174
+ export type {
175
+ SkillDefinition,
176
+ SkillContentBlock,
177
+ SkillResult,
178
+ } from './skills/index.js'
179
+
135
180
  // --------------------------------------------------------------------------
136
181
  // Hook System
137
182
  // --------------------------------------------------------------------------
@@ -341,6 +386,7 @@ export type {
341
386
 
342
387
  // Permission types
343
388
  PermissionMode,
389
+ PermissionBehavior,
344
390
  CanUseToolFn,
345
391
  CanUseToolResult,
346
392
 
@@ -360,6 +406,10 @@ export type {
360
406
  // Engine types
361
407
  QueryEngineConfig,
362
408
 
409
+ // Content block types
410
+ ContentBlockParam,
411
+ ContentBlock,
412
+
363
413
  // Sandbox types
364
414
  SandboxSettings,
365
415
  SandboxNetworkConfig,
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Anthropic Messages API Provider
3
+ *
4
+ * Wraps the @anthropic-ai/sdk client. Since our internal format is
5
+ * Anthropic-like, this is mostly a thin pass-through.
6
+ */
7
+
8
+ import Anthropic from '@anthropic-ai/sdk'
9
+ import type {
10
+ LLMProvider,
11
+ CreateMessageParams,
12
+ CreateMessageResponse,
13
+ } from './types.js'
14
+
15
+ export class AnthropicProvider implements LLMProvider {
16
+ readonly apiType = 'anthropic-messages' as const
17
+ private client: Anthropic
18
+
19
+ constructor(opts: { apiKey?: string; baseURL?: string }) {
20
+ this.client = new Anthropic({
21
+ apiKey: opts.apiKey,
22
+ baseURL: opts.baseURL,
23
+ })
24
+ }
25
+
26
+ async createMessage(params: CreateMessageParams): Promise<CreateMessageResponse> {
27
+ const requestParams: Anthropic.MessageCreateParamsNonStreaming = {
28
+ model: params.model,
29
+ max_tokens: params.maxTokens,
30
+ system: params.system,
31
+ messages: params.messages as Anthropic.MessageParam[],
32
+ tools: params.tools
33
+ ? (params.tools as Anthropic.Tool[])
34
+ : undefined,
35
+ }
36
+
37
+ // Add extended thinking if configured
38
+ if (params.thinking?.type === 'enabled' && params.thinking.budget_tokens) {
39
+ (requestParams as any).thinking = {
40
+ type: 'enabled',
41
+ budget_tokens: params.thinking.budget_tokens,
42
+ }
43
+ }
44
+
45
+ const response = await this.client.messages.create(requestParams)
46
+
47
+ return {
48
+ content: response.content as CreateMessageResponse['content'],
49
+ stopReason: response.stop_reason || 'end_turn',
50
+ usage: {
51
+ input_tokens: response.usage.input_tokens,
52
+ output_tokens: response.usage.output_tokens,
53
+ cache_creation_input_tokens:
54
+ (response.usage as any).cache_creation_input_tokens,
55
+ cache_read_input_tokens:
56
+ (response.usage as any).cache_read_input_tokens,
57
+ },
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * LLM Provider Factory
3
+ *
4
+ * Creates the appropriate provider based on API type configuration.
5
+ */
6
+
7
+ export type { ApiType, LLMProvider, CreateMessageParams, CreateMessageResponse, NormalizedMessageParam, NormalizedContentBlock, NormalizedTool, NormalizedResponseBlock } from './types.js'
8
+
9
+ export { AnthropicProvider } from './anthropic.js'
10
+ export { OpenAIProvider } from './openai.js'
11
+
12
+ import type { ApiType, LLMProvider } from './types.js'
13
+ import { AnthropicProvider } from './anthropic.js'
14
+ import { OpenAIProvider } from './openai.js'
15
+
16
+ /**
17
+ * Create an LLM provider based on the API type.
18
+ *
19
+ * @param apiType - 'anthropic-messages' or 'openai-completions'
20
+ * @param opts - API credentials
21
+ */
22
+ export function createProvider(
23
+ apiType: ApiType,
24
+ opts: { apiKey?: string; baseURL?: string },
25
+ ): LLMProvider {
26
+ switch (apiType) {
27
+ case 'anthropic-messages':
28
+ return new AnthropicProvider(opts)
29
+ case 'openai-completions':
30
+ return new OpenAIProvider(opts)
31
+ default:
32
+ throw new Error(`Unsupported API type: ${apiType}. Use 'anthropic-messages' or 'openai-completions'.`)
33
+ }
34
+ }