@codeany/open-agent-sdk 0.2.1 → 0.2.3

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 (57) hide show
  1. package/dist/engine.d.ts.map +1 -1
  2. package/dist/engine.js +2 -0
  3. package/dist/engine.js.map +1 -1
  4. package/package.json +5 -6
  5. package/src/agent.ts +0 -516
  6. package/src/engine.ts +0 -630
  7. package/src/hooks.ts +0 -261
  8. package/src/index.ts +0 -426
  9. package/src/mcp/client.ts +0 -150
  10. package/src/providers/anthropic.ts +0 -60
  11. package/src/providers/index.ts +0 -34
  12. package/src/providers/openai.ts +0 -315
  13. package/src/providers/types.ts +0 -85
  14. package/src/sdk-mcp-server.ts +0 -78
  15. package/src/session.ts +0 -227
  16. package/src/skills/bundled/commit.ts +0 -38
  17. package/src/skills/bundled/debug.ts +0 -48
  18. package/src/skills/bundled/index.ts +0 -28
  19. package/src/skills/bundled/review.ts +0 -41
  20. package/src/skills/bundled/simplify.ts +0 -51
  21. package/src/skills/bundled/test.ts +0 -43
  22. package/src/skills/index.ts +0 -25
  23. package/src/skills/registry.ts +0 -133
  24. package/src/skills/types.ts +0 -99
  25. package/src/tool-helper.ts +0 -127
  26. package/src/tools/agent-tool.ts +0 -164
  27. package/src/tools/ask-user.ts +0 -79
  28. package/src/tools/bash.ts +0 -75
  29. package/src/tools/config-tool.ts +0 -89
  30. package/src/tools/cron-tools.ts +0 -153
  31. package/src/tools/edit.ts +0 -74
  32. package/src/tools/glob.ts +0 -77
  33. package/src/tools/grep.ts +0 -168
  34. package/src/tools/index.ts +0 -240
  35. package/src/tools/lsp-tool.ts +0 -163
  36. package/src/tools/mcp-resource-tools.ts +0 -125
  37. package/src/tools/notebook-edit.ts +0 -93
  38. package/src/tools/plan-tools.ts +0 -88
  39. package/src/tools/read.ts +0 -73
  40. package/src/tools/send-message.ts +0 -96
  41. package/src/tools/skill-tool.ts +0 -133
  42. package/src/tools/task-tools.ts +0 -290
  43. package/src/tools/team-tools.ts +0 -128
  44. package/src/tools/todo-tool.ts +0 -112
  45. package/src/tools/tool-search.ts +0 -87
  46. package/src/tools/types.ts +0 -66
  47. package/src/tools/web-fetch.ts +0 -66
  48. package/src/tools/web-search.ts +0 -86
  49. package/src/tools/worktree-tools.ts +0 -140
  50. package/src/tools/write.ts +0 -42
  51. package/src/types.ts +0 -486
  52. package/src/utils/compact.ts +0 -207
  53. package/src/utils/context.ts +0 -191
  54. package/src/utils/fileCache.ts +0 -148
  55. package/src/utils/messages.ts +0 -195
  56. package/src/utils/retry.ts +0 -140
  57. package/src/utils/tokens.ts +0 -145
@@ -1,195 +0,0 @@
1
- /**
2
- * Message Utilities
3
- *
4
- * Message creation factories, normalization for API,
5
- * synthetic placeholders, and content processing.
6
- */
7
-
8
- import type { Message, UserMessage, AssistantMessage, TokenUsage } from '../types.js'
9
-
10
- /**
11
- * Create a user message.
12
- */
13
- export function createUserMessage(
14
- content: string | any[],
15
- options?: {
16
- uuid?: string
17
- isMeta?: boolean
18
- toolUseResult?: unknown
19
- },
20
- ): UserMessage {
21
- return {
22
- type: 'user',
23
- message: {
24
- role: 'user',
25
- content,
26
- },
27
- uuid: options?.uuid || crypto.randomUUID(),
28
- timestamp: new Date().toISOString(),
29
- }
30
- }
31
-
32
- /**
33
- * Create an assistant message.
34
- */
35
- export function createAssistantMessage(
36
- content: any[],
37
- usage?: TokenUsage,
38
- ): AssistantMessage {
39
- return {
40
- type: 'assistant',
41
- message: {
42
- role: 'assistant',
43
- content,
44
- },
45
- uuid: crypto.randomUUID(),
46
- timestamp: new Date().toISOString(),
47
- usage,
48
- }
49
- }
50
-
51
- /**
52
- * Normalize messages for the LLM API.
53
- * Ensures proper message format, strips internal metadata,
54
- * and fixes tool result pairing.
55
- */
56
- export function normalizeMessagesForAPI(
57
- messages: Array<{ role: string; content: any }>,
58
- ): Array<{ role: string; content: any }> {
59
- const normalized: Array<{ role: string; content: any }> = []
60
-
61
- for (let i = 0; i < messages.length; i++) {
62
- const msg = messages[i]
63
-
64
- // Ensure alternating user/assistant messages
65
- if (normalized.length > 0) {
66
- const last = normalized[normalized.length - 1]
67
- if (last.role === msg.role) {
68
- // Merge same-role messages
69
- if (msg.role === 'user') {
70
- // Combine content
71
- const lastContent = typeof last.content === 'string'
72
- ? [{ type: 'text' as const, text: last.content }]
73
- : last.content as any[]
74
- const newContent = typeof msg.content === 'string'
75
- ? [{ type: 'text' as const, text: msg.content }]
76
- : msg.content as any[]
77
- normalized[normalized.length - 1] = {
78
- role: 'user',
79
- content: [...lastContent, ...newContent],
80
- }
81
- continue
82
- }
83
- }
84
- }
85
-
86
- normalized.push({ ...msg })
87
- }
88
-
89
- // Ensure tool results are properly paired with tool_use
90
- return fixToolResultPairing(normalized)
91
- }
92
-
93
- /**
94
- * Fix tool result pairing: ensure every tool_result has a
95
- * matching tool_use in the previous assistant message.
96
- */
97
- function fixToolResultPairing(
98
- messages: Array<{ role: string; content: any }>,
99
- ): Array<{ role: string; content: any }> {
100
- const result: Array<{ role: string; content: any }> = []
101
-
102
- for (let i = 0; i < messages.length; i++) {
103
- const msg = messages[i]
104
-
105
- if (msg.role === 'user' && Array.isArray(msg.content)) {
106
- // Check for tool_result blocks
107
- const toolResults = (msg.content as any[]).filter(
108
- (block: any) => block.type === 'tool_result',
109
- )
110
-
111
- if (toolResults.length > 0 && result.length > 0) {
112
- // Find the previous assistant message
113
- const prevAssistant = result[result.length - 1]
114
- if (prevAssistant.role === 'assistant' && Array.isArray(prevAssistant.content)) {
115
- const toolUseIds = new Set(
116
- (prevAssistant.content as any[])
117
- .filter((b: any) => b.type === 'tool_use')
118
- .map((b: any) => b.id),
119
- )
120
-
121
- // Filter out orphaned tool results
122
- const validContent = (msg.content as any[]).filter((block: any) => {
123
- if (block.type === 'tool_result') {
124
- return toolUseIds.has(block.tool_use_id)
125
- }
126
- return true
127
- })
128
-
129
- if (validContent.length > 0) {
130
- result.push({ ...msg, content: validContent })
131
- }
132
- continue
133
- }
134
- }
135
- }
136
-
137
- result.push(msg)
138
- }
139
-
140
- return result
141
- }
142
-
143
- /**
144
- * Strip images from messages (for compaction).
145
- */
146
- export function stripImagesFromMessages(
147
- messages: Array<{ role: string; content: any }>,
148
- ): Array<{ role: string; content: any }> {
149
- return messages.map((msg) => {
150
- if (typeof msg.content === 'string') return msg
151
- if (!Array.isArray(msg.content)) return msg
152
-
153
- const filtered = (msg.content as any[]).filter(
154
- (block: any) => block.type !== 'image',
155
- )
156
-
157
- return {
158
- ...msg,
159
- content: filtered.length > 0 ? filtered : '[content removed]',
160
- }
161
- })
162
- }
163
-
164
- /**
165
- * Extract text from message content blocks.
166
- */
167
- export function extractTextFromContent(
168
- content: any[] | string,
169
- ): string {
170
- if (typeof content === 'string') return content
171
-
172
- return content
173
- .filter((b: any) => b.type === 'text')
174
- .map((b: any) => b.text)
175
- .join('')
176
- }
177
-
178
- /**
179
- * Create a system message for compact boundary.
180
- */
181
- export function createCompactBoundaryMessage(): { role: string; content: string } {
182
- return {
183
- role: 'user',
184
- content: '[Previous context has been summarized above. Continuing conversation.]',
185
- }
186
- }
187
-
188
- /**
189
- * Truncate text to max length with ellipsis.
190
- */
191
- export function truncateText(text: string, maxLength: number): string {
192
- if (text.length <= maxLength) return text
193
- const half = Math.floor(maxLength / 2)
194
- return text.slice(0, half) + '\n...(truncated)...\n' + text.slice(-half)
195
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * Retry Logic with Exponential Backoff
3
- *
4
- * Handles API retries for rate limits, overloaded servers,
5
- * and transient failures.
6
- */
7
-
8
- /**
9
- * Retry configuration.
10
- */
11
- export interface RetryConfig {
12
- maxRetries: number
13
- baseDelayMs: number
14
- maxDelayMs: number
15
- retryableStatusCodes: number[]
16
- }
17
-
18
- /**
19
- * Default retry configuration.
20
- */
21
- export const DEFAULT_RETRY_CONFIG: RetryConfig = {
22
- maxRetries: 3,
23
- baseDelayMs: 2000,
24
- maxDelayMs: 30000,
25
- retryableStatusCodes: [429, 500, 502, 503, 529],
26
- }
27
-
28
- /**
29
- * Check if an error is retryable.
30
- */
31
- export function isRetryableError(err: any, config: RetryConfig = DEFAULT_RETRY_CONFIG): boolean {
32
- if (err?.status && config.retryableStatusCodes.includes(err.status)) {
33
- return true
34
- }
35
-
36
- // Network errors
37
- if (err?.code === 'ECONNRESET' || err?.code === 'ETIMEDOUT' || err?.code === 'ECONNREFUSED') {
38
- return true
39
- }
40
-
41
- // API overloaded
42
- if (err?.error?.type === 'overloaded_error') {
43
- return true
44
- }
45
-
46
- return false
47
- }
48
-
49
- /**
50
- * Calculate delay for exponential backoff.
51
- */
52
- export function getRetryDelay(attempt: number, config: RetryConfig = DEFAULT_RETRY_CONFIG): number {
53
- const delay = config.baseDelayMs * Math.pow(2, attempt)
54
- // Add jitter (±25%)
55
- const jitter = delay * 0.25 * (Math.random() * 2 - 1)
56
- return Math.min(delay + jitter, config.maxDelayMs)
57
- }
58
-
59
- /**
60
- * Execute a function with retries.
61
- */
62
- export async function withRetry<T>(
63
- fn: () => Promise<T>,
64
- config: RetryConfig = DEFAULT_RETRY_CONFIG,
65
- abortSignal?: AbortSignal,
66
- ): Promise<T> {
67
- let lastError: any
68
-
69
- for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
70
- if (abortSignal?.aborted) {
71
- throw new Error('Aborted')
72
- }
73
-
74
- try {
75
- return await fn()
76
- } catch (err: any) {
77
- lastError = err
78
-
79
- if (!isRetryableError(err, config)) {
80
- throw err
81
- }
82
-
83
- if (attempt === config.maxRetries) {
84
- throw err
85
- }
86
-
87
- // Wait before retry
88
- const delay = getRetryDelay(attempt, config)
89
- await new Promise((resolve) => setTimeout(resolve, delay))
90
- }
91
- }
92
-
93
- throw lastError
94
- }
95
-
96
- /**
97
- * Check if an error is a "prompt too long" error.
98
- */
99
- export function isPromptTooLongError(err: any): boolean {
100
- if (err?.status === 400) {
101
- const message = err?.error?.error?.message || err?.message || ''
102
- return message.includes('prompt is too long') ||
103
- message.includes('max_tokens') ||
104
- message.includes('context length')
105
- }
106
- return false
107
- }
108
-
109
- /**
110
- * Check if error is an auth error.
111
- */
112
- export function isAuthError(err: any): boolean {
113
- return err?.status === 401 || err?.status === 403
114
- }
115
-
116
- /**
117
- * Check if error is a rate limit error.
118
- */
119
- export function isRateLimitError(err: any): boolean {
120
- return err?.status === 429
121
- }
122
-
123
- /**
124
- * Format an API error for display.
125
- */
126
- export function formatApiError(err: any): string {
127
- if (isAuthError(err)) {
128
- return 'Authentication failed. Check your CODEANY_API_KEY.'
129
- }
130
- if (isRateLimitError(err)) {
131
- return 'Rate limit exceeded. Please retry after a short wait.'
132
- }
133
- if (err?.status === 529) {
134
- return 'API overloaded. Please retry later.'
135
- }
136
- if (isPromptTooLongError(err)) {
137
- return 'Prompt too long. Auto-compacting conversation...'
138
- }
139
- return `API error: ${err.message || err}`
140
- }
@@ -1,145 +0,0 @@
1
- /**
2
- * Token Estimation & Counting
3
- *
4
- * Provides rough token estimation (character-based) and
5
- * API-based exact counting when available.
6
- */
7
-
8
- /**
9
- * Rough token estimation: ~4 chars per token (conservative).
10
- */
11
- export function estimateTokens(text: string): number {
12
- return Math.ceil(text.length / 4)
13
- }
14
-
15
- /**
16
- * Estimate tokens for a message array.
17
- */
18
- export function estimateMessagesTokens(
19
- messages: Array<{ role: string; content: any }>,
20
- ): number {
21
- let total = 0
22
- for (const msg of messages) {
23
- if (typeof msg.content === 'string') {
24
- total += estimateTokens(msg.content)
25
- } else if (Array.isArray(msg.content)) {
26
- for (const block of msg.content) {
27
- if ('text' in block && typeof block.text === 'string') {
28
- total += estimateTokens(block.text)
29
- } else if ('content' in block && typeof block.content === 'string') {
30
- total += estimateTokens(block.content)
31
- } else {
32
- // tool_use, image, etc - rough estimate
33
- total += estimateTokens(JSON.stringify(block))
34
- }
35
- }
36
- }
37
- }
38
- return total
39
- }
40
-
41
- /**
42
- * Estimate tokens for a system prompt.
43
- */
44
- export function estimateSystemPromptTokens(systemPrompt: string): number {
45
- return estimateTokens(systemPrompt)
46
- }
47
-
48
- /**
49
- * Count tokens from API usage response.
50
- */
51
- export function getTokenCountFromUsage(usage: {
52
- input_tokens: number
53
- output_tokens: number
54
- cache_creation_input_tokens?: number
55
- cache_read_input_tokens?: number
56
- }): number {
57
- return (
58
- usage.input_tokens +
59
- usage.output_tokens +
60
- (usage.cache_creation_input_tokens || 0) +
61
- (usage.cache_read_input_tokens || 0)
62
- )
63
- }
64
-
65
- /**
66
- * Get the context window size for a model.
67
- */
68
- export function getContextWindowSize(model: string): number {
69
- // Anthropic model context windows
70
- if (model.includes('opus-4') && model.includes('1m')) return 1_000_000
71
- if (model.includes('opus-4')) return 200_000
72
- if (model.includes('sonnet-4')) return 200_000
73
- if (model.includes('haiku-4')) return 200_000
74
- if (model.includes('claude-3')) return 200_000
75
-
76
- // OpenAI model context windows
77
- if (model.includes('gpt-4o')) return 128_000
78
- if (model.includes('gpt-4-turbo')) return 128_000
79
- if (model.includes('gpt-4-1')) return 1_000_000
80
- if (model.includes('gpt-4')) return 128_000
81
- if (model.includes('gpt-3.5')) return 16_385
82
- if (model.includes('o1')) return 200_000
83
- if (model.includes('o3')) return 200_000
84
- if (model.includes('o4')) return 200_000
85
-
86
- // DeepSeek models
87
- if (model.includes('deepseek')) return 128_000
88
-
89
- // Default
90
- return 200_000
91
- }
92
-
93
- /**
94
- * Auto-compact buffer: trigger compaction when within this many tokens of the limit.
95
- */
96
- export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
97
-
98
- /**
99
- * Get the auto-compact threshold for a model.
100
- */
101
- export function getAutoCompactThreshold(model: string): number {
102
- return getContextWindowSize(model) - AUTOCOMPACT_BUFFER_TOKENS
103
- }
104
-
105
- /**
106
- * Model pricing (USD per token).
107
- */
108
- export const MODEL_PRICING: Record<string, { input: number; output: number }> = {
109
- // Anthropic models
110
- 'claude-opus-4-6': { input: 15 / 1_000_000, output: 75 / 1_000_000 },
111
- 'claude-opus-4-5': { input: 15 / 1_000_000, output: 75 / 1_000_000 },
112
- 'claude-sonnet-4-6': { input: 3 / 1_000_000, output: 15 / 1_000_000 },
113
- 'claude-sonnet-4-5': { input: 3 / 1_000_000, output: 15 / 1_000_000 },
114
- 'claude-haiku-4-5': { input: 0.8 / 1_000_000, output: 4 / 1_000_000 },
115
- 'claude-3-5-sonnet': { input: 3 / 1_000_000, output: 15 / 1_000_000 },
116
- 'claude-3-5-haiku': { input: 0.8 / 1_000_000, output: 4 / 1_000_000 },
117
- 'claude-3-opus': { input: 15 / 1_000_000, output: 75 / 1_000_000 },
118
-
119
- // OpenAI models
120
- 'gpt-4o': { input: 2.5 / 1_000_000, output: 10 / 1_000_000 },
121
- 'gpt-4o-mini': { input: 0.15 / 1_000_000, output: 0.6 / 1_000_000 },
122
- 'gpt-4-turbo': { input: 10 / 1_000_000, output: 30 / 1_000_000 },
123
- 'gpt-4-1': { input: 2 / 1_000_000, output: 8 / 1_000_000 },
124
- 'o1': { input: 15 / 1_000_000, output: 60 / 1_000_000 },
125
- 'o3': { input: 10 / 1_000_000, output: 40 / 1_000_000 },
126
- 'o4-mini': { input: 1.1 / 1_000_000, output: 4.4 / 1_000_000 },
127
-
128
- // DeepSeek models
129
- 'deepseek-chat': { input: 0.27 / 1_000_000, output: 1.1 / 1_000_000 },
130
- 'deepseek-reasoner': { input: 0.55 / 1_000_000, output: 2.19 / 1_000_000 },
131
- }
132
-
133
- /**
134
- * Estimate cost from usage and model.
135
- */
136
- export function estimateCost(
137
- model: string,
138
- usage: { input_tokens: number; output_tokens: number },
139
- ): number {
140
- const pricing = Object.entries(MODEL_PRICING).find(([key]) =>
141
- model.includes(key),
142
- )?.[1] ?? { input: 3 / 1_000_000, output: 15 / 1_000_000 }
143
-
144
- return usage.input_tokens * pricing.input + usage.output_tokens * pricing.output
145
- }