@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,207 +0,0 @@
1
- /**
2
- * Context Compression / Auto-Compaction
3
- *
4
- * Summarizes long conversation histories when context window fills up.
5
- * Three-tier system:
6
- * 1. Auto-compact: triggered when tokens exceed threshold
7
- * 2. Micro-compact: cache-aware per-request optimization
8
- * 3. Session memory compaction: consolidates across sessions
9
- */
10
-
11
- import type { LLMProvider } from '../providers/types.js'
12
- import type { NormalizedMessageParam } from '../providers/types.js'
13
- import {
14
- estimateMessagesTokens,
15
- getAutoCompactThreshold,
16
- } from './tokens.js'
17
-
18
- /**
19
- * State for tracking auto-compaction across turns.
20
- */
21
- export interface AutoCompactState {
22
- compacted: boolean
23
- turnCounter: number
24
- consecutiveFailures: number
25
- }
26
-
27
- /**
28
- * Create initial auto-compact state.
29
- */
30
- export function createAutoCompactState(): AutoCompactState {
31
- return {
32
- compacted: false,
33
- turnCounter: 0,
34
- consecutiveFailures: 0,
35
- }
36
- }
37
-
38
- /**
39
- * Check if auto-compaction should trigger.
40
- */
41
- export function shouldAutoCompact(
42
- messages: any[],
43
- model: string,
44
- state: AutoCompactState,
45
- ): boolean {
46
- if (state.consecutiveFailures >= 3) return false
47
-
48
- const estimatedTokens = estimateMessagesTokens(messages)
49
- const threshold = getAutoCompactThreshold(model)
50
-
51
- return estimatedTokens >= threshold
52
- }
53
-
54
- /**
55
- * Compact conversation by summarizing with the LLM.
56
- *
57
- * Sends the entire conversation to the LLM for summarization,
58
- * then replaces the history with a compact summary.
59
- */
60
- export async function compactConversation(
61
- provider: LLMProvider,
62
- model: string,
63
- messages: any[],
64
- state: AutoCompactState,
65
- ): Promise<{
66
- compactedMessages: NormalizedMessageParam[]
67
- summary: string
68
- state: AutoCompactState
69
- }> {
70
- try {
71
- // Strip images before compacting to save tokens
72
- const strippedMessages = stripImagesFromMessages(messages)
73
-
74
- // Build compaction prompt
75
- const compactionPrompt = buildCompactionPrompt(strippedMessages)
76
-
77
- const response = await provider.createMessage({
78
- model,
79
- maxTokens: 8192,
80
- system: 'You are a conversation summarizer. Create a detailed summary of the conversation that preserves all important context, decisions made, files modified, tool outputs, and current state. The summary should allow the conversation to continue seamlessly.',
81
- messages: [
82
- {
83
- role: 'user',
84
- content: compactionPrompt,
85
- },
86
- ],
87
- })
88
-
89
- const summary = response.content
90
- .filter((b) => b.type === 'text')
91
- .map((b) => (b as { type: 'text'; text: string }).text)
92
- .join('\n')
93
-
94
- // Replace messages with summary
95
- const compactedMessages: NormalizedMessageParam[] = [
96
- {
97
- role: 'user',
98
- content: `[Previous conversation summary]\n\n${summary}\n\n[End of summary - conversation continues below]`,
99
- },
100
- {
101
- role: 'assistant',
102
- content: 'I understand the context from the previous conversation. I\'ll continue from where we left off.',
103
- },
104
- ]
105
-
106
- return {
107
- compactedMessages,
108
- summary,
109
- state: {
110
- compacted: true,
111
- turnCounter: state.turnCounter,
112
- consecutiveFailures: 0,
113
- },
114
- }
115
- } catch (err: any) {
116
- return {
117
- compactedMessages: messages,
118
- summary: '',
119
- state: {
120
- ...state,
121
- consecutiveFailures: state.consecutiveFailures + 1,
122
- },
123
- }
124
- }
125
- }
126
-
127
- /**
128
- * Strip images from messages for compaction safety.
129
- */
130
- function stripImagesFromMessages(
131
- messages: any[],
132
- ): any[] {
133
- return messages.map((msg: any) => {
134
- if (typeof msg.content === 'string') return msg
135
-
136
- const filtered = (msg.content as any[]).filter((block: any) => {
137
- return block.type !== 'image'
138
- })
139
-
140
- return { ...msg, content: filtered.length > 0 ? filtered : '[content removed for compaction]' }
141
- })
142
- }
143
-
144
- /**
145
- * Build compaction prompt from messages.
146
- */
147
- function buildCompactionPrompt(messages: any[]): string {
148
- const parts: string[] = ['Please summarize this conversation:\n']
149
-
150
- for (const msg of messages) {
151
- const role = msg.role === 'user' ? 'User' : 'Assistant'
152
-
153
- if (typeof msg.content === 'string') {
154
- parts.push(`${role}: ${msg.content.slice(0, 5000)}`)
155
- } else if (Array.isArray(msg.content)) {
156
- const texts: string[] = []
157
- for (const block of msg.content as any[]) {
158
- if (block.type === 'text') {
159
- texts.push(block.text.slice(0, 3000))
160
- } else if (block.type === 'tool_use') {
161
- texts.push(`[Tool: ${block.name}]`)
162
- } else if (block.type === 'tool_result') {
163
- const content = typeof block.content === 'string'
164
- ? block.content.slice(0, 1000)
165
- : '[tool result]'
166
- texts.push(`[Tool Result: ${content}]`)
167
- }
168
- }
169
- if (texts.length > 0) {
170
- parts.push(`${role}: ${texts.join('\n')}`)
171
- }
172
- }
173
- }
174
-
175
- return parts.join('\n\n')
176
- }
177
-
178
- /**
179
- * Micro-compact: optimize messages by truncating large tool results
180
- * to fit within token budgets.
181
- */
182
- export function microCompactMessages(
183
- messages: any[],
184
- maxToolResultChars: number = 50000,
185
- ): any[] {
186
- return messages.map((msg: any) => {
187
- if (typeof msg.content === 'string') return msg
188
- if (!Array.isArray(msg.content)) return msg
189
-
190
- const content = (msg.content as any[]).map((block: any) => {
191
- if (block.type === 'tool_result' && typeof block.content === 'string') {
192
- if (block.content.length > maxToolResultChars) {
193
- return {
194
- ...block,
195
- content:
196
- block.content.slice(0, maxToolResultChars / 2) +
197
- '\n...(truncated)...\n' +
198
- block.content.slice(-maxToolResultChars / 2),
199
- }
200
- }
201
- }
202
- return block
203
- })
204
-
205
- return { ...msg, content }
206
- })
207
- }
@@ -1,191 +0,0 @@
1
- /**
2
- * System & User Context
3
- *
4
- * Builds context for the system prompt:
5
- * - Git status injection (branch, commits, status)
6
- * - AGENT.md / project context discovery and injection
7
- * - Working directory info
8
- * - Date injection
9
- */
10
-
11
- import { execSync } from 'child_process'
12
- import { readFile, stat } from 'fs/promises'
13
- import { join, resolve } from 'path'
14
-
15
- // Memoization cache
16
- let cachedGitStatus: string | null = null
17
- let cachedGitStatusCwd: string | null = null
18
-
19
- /**
20
- * Get git status info for system prompt.
21
- * Memoized per cwd (cleared on new session).
22
- */
23
- export async function getGitStatus(cwd: string): Promise<string> {
24
- if (cachedGitStatus && cachedGitStatusCwd === cwd) {
25
- return cachedGitStatus
26
- }
27
-
28
- try {
29
- const parts: string[] = []
30
-
31
- const gitExec = (cmd: string, timeoutMs = 5000): string | null => {
32
- try {
33
- return execSync(cmd, {
34
- cwd, timeout: timeoutMs, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
35
- }).trim()
36
- } catch {
37
- return null
38
- }
39
- }
40
-
41
- // Check if this is a git repo at all
42
- if (!gitExec('git rev-parse --git-dir')) return ''
43
-
44
- // Current branch
45
- const branch = gitExec('git rev-parse --abbrev-ref HEAD')
46
- if (branch) parts.push(`Current branch: ${branch}`)
47
-
48
- // Main branch detection
49
- const mainBranch = detectMainBranch(cwd)
50
- if (mainBranch) parts.push(`Main branch: ${mainBranch}`)
51
-
52
- // Git user
53
- const user = gitExec('git config user.name', 3000)
54
- if (user) parts.push(`Git user: ${user}`)
55
-
56
- // Status (staged + unstaged)
57
- const status = gitExec('git status --short')
58
- if (status) {
59
- const truncated = status.length > 2000
60
- ? status.slice(0, 2000) + '\n...(truncated)'
61
- : status
62
- parts.push(`Status:\n${truncated}`)
63
- }
64
-
65
- // Recent commits (only if HEAD exists)
66
- const hasHead = gitExec('git rev-parse HEAD')
67
- if (hasHead) {
68
- const log = gitExec('git log --oneline -5 --no-decorate')
69
- if (log) parts.push(`Recent commits:\n${log}`)
70
- }
71
-
72
- cachedGitStatus = parts.join('\n\n')
73
- cachedGitStatusCwd = cwd
74
-
75
- return cachedGitStatus
76
- } catch {
77
- return ''
78
- }
79
- }
80
-
81
- /**
82
- * Detect the main branch name (main or master).
83
- */
84
- function detectMainBranch(cwd: string): string | null {
85
- try {
86
- const branches = execSync('git branch -l main master', {
87
- cwd, timeout: 3000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
88
- }).trim()
89
- if (branches.includes('main')) return 'main'
90
- if (branches.includes('master')) return 'master'
91
- return null
92
- } catch {
93
- return null
94
- }
95
- }
96
-
97
- /**
98
- * Discover project context files (AGENT.md, CLAUDE.md) in the project.
99
- */
100
- export async function discoverProjectContextFiles(cwd: string): Promise<string[]> {
101
- const candidates = [
102
- join(cwd, 'AGENT.md'),
103
- join(cwd, 'CLAUDE.md'),
104
- join(cwd, '.claude', 'CLAUDE.md'),
105
- join(cwd, 'claude.md'),
106
- ]
107
-
108
- // Also check home directory
109
- const home = process.env.HOME || process.env.USERPROFILE || ''
110
- if (home) {
111
- candidates.push(
112
- join(home, '.claude', 'CLAUDE.md'),
113
- )
114
- }
115
-
116
- const found: string[] = []
117
- for (const path of candidates) {
118
- try {
119
- const s = await stat(path)
120
- if (s.isFile()) {
121
- found.push(path)
122
- }
123
- } catch {
124
- // File doesn't exist
125
- }
126
- }
127
-
128
- return found
129
- }
130
-
131
- /**
132
- * Read project context file content from discovered files.
133
- */
134
- export async function readProjectContextContent(cwd: string): Promise<string> {
135
- const files = await discoverProjectContextFiles(cwd)
136
- if (files.length === 0) return ''
137
-
138
- const parts: string[] = []
139
- for (const file of files) {
140
- try {
141
- const content = await readFile(file, 'utf-8')
142
- if (content.trim()) {
143
- parts.push(`# From ${file}:\n${content.trim()}`)
144
- }
145
- } catch {
146
- // Skip unreadable files
147
- }
148
- }
149
-
150
- return parts.join('\n\n')
151
- }
152
-
153
- /**
154
- * Get system context for the system prompt.
155
- */
156
- export async function getSystemContext(cwd: string): Promise<string> {
157
- const parts: string[] = []
158
-
159
- const gitStatus = await getGitStatus(cwd)
160
- if (gitStatus) {
161
- parts.push(`gitStatus: ${gitStatus}`)
162
- }
163
-
164
- return parts.join('\n\n')
165
- }
166
-
167
- /**
168
- * Get user context (AGENT.md, date, etc).
169
- */
170
- export async function getUserContext(cwd: string): Promise<string> {
171
- const parts: string[] = []
172
-
173
- // Current date
174
- parts.push(`# currentDate\nToday's date is ${new Date().toISOString().split('T')[0]}.`)
175
-
176
- // Project context files
177
- const projectCtx = await readProjectContextContent(cwd)
178
- if (projectCtx) {
179
- parts.push(projectCtx)
180
- }
181
-
182
- return parts.join('\n\n')
183
- }
184
-
185
- /**
186
- * Clear memoized context (call between sessions).
187
- */
188
- export function clearContextCache(): void {
189
- cachedGitStatus = null
190
- cachedGitStatusCwd = null
191
- }
@@ -1,148 +0,0 @@
1
- /**
2
- * File State LRU Cache
3
- *
4
- * Bounded cache for file contents with path normalization.
5
- * Used to track file states for compaction diffs and
6
- * avoiding redundant reads.
7
- */
8
-
9
- import { normalize, resolve } from 'path'
10
-
11
- /**
12
- * Cached file state.
13
- */
14
- export interface FileState {
15
- content: string
16
- timestamp: number
17
- offset?: number
18
- limit?: number
19
- isPartialView?: boolean
20
- }
21
-
22
- /**
23
- * LRU file state cache with size limits.
24
- */
25
- export class FileStateCache {
26
- private cache = new Map<string, FileState>()
27
- private maxEntries: number
28
- private maxSizeBytes: number
29
- private currentSizeBytes = 0
30
-
31
- constructor(maxEntries: number = 100, maxSizeBytes: number = 25 * 1024 * 1024) {
32
- this.maxEntries = maxEntries
33
- this.maxSizeBytes = maxSizeBytes
34
- }
35
-
36
- /**
37
- * Normalize a file path for cache lookup.
38
- */
39
- private normalizePath(filePath: string): string {
40
- return normalize(resolve(filePath))
41
- }
42
-
43
- /**
44
- * Get a cached file state.
45
- */
46
- get(filePath: string): FileState | undefined {
47
- const key = this.normalizePath(filePath)
48
- const entry = this.cache.get(key)
49
- if (entry) {
50
- // Move to end (most recently used)
51
- this.cache.delete(key)
52
- this.cache.set(key, entry)
53
- }
54
- return entry
55
- }
56
-
57
- /**
58
- * Set a cached file state.
59
- */
60
- set(filePath: string, state: FileState): void {
61
- const key = this.normalizePath(filePath)
62
-
63
- // Remove old entry if exists
64
- const old = this.cache.get(key)
65
- if (old) {
66
- this.currentSizeBytes -= Buffer.byteLength(old.content, 'utf-8')
67
- this.cache.delete(key)
68
- }
69
-
70
- const newSize = Buffer.byteLength(state.content, 'utf-8')
71
-
72
- // Evict entries if necessary
73
- while (
74
- (this.cache.size >= this.maxEntries || this.currentSizeBytes + newSize > this.maxSizeBytes) &&
75
- this.cache.size > 0
76
- ) {
77
- const firstKey = this.cache.keys().next().value
78
- if (firstKey) {
79
- const entry = this.cache.get(firstKey)
80
- if (entry) {
81
- this.currentSizeBytes -= Buffer.byteLength(entry.content, 'utf-8')
82
- }
83
- this.cache.delete(firstKey)
84
- }
85
- }
86
-
87
- this.cache.set(key, state)
88
- this.currentSizeBytes += newSize
89
- }
90
-
91
- /**
92
- * Delete a cached entry.
93
- */
94
- delete(filePath: string): boolean {
95
- const key = this.normalizePath(filePath)
96
- const entry = this.cache.get(key)
97
- if (entry) {
98
- this.currentSizeBytes -= Buffer.byteLength(entry.content, 'utf-8')
99
- this.cache.delete(key)
100
- return true
101
- }
102
- return false
103
- }
104
-
105
- /**
106
- * Clear all cached entries.
107
- */
108
- clear(): void {
109
- this.cache.clear()
110
- this.currentSizeBytes = 0
111
- }
112
-
113
- /**
114
- * Get the number of cached entries.
115
- */
116
- get size(): number {
117
- return this.cache.size
118
- }
119
-
120
- /**
121
- * Get all cached file paths.
122
- */
123
- keys(): string[] {
124
- return Array.from(this.cache.keys())
125
- }
126
-
127
- /**
128
- * Clone the cache.
129
- */
130
- clone(): FileStateCache {
131
- const clone = new FileStateCache(this.maxEntries, this.maxSizeBytes)
132
- for (const [key, value] of this.cache) {
133
- clone.cache.set(key, { ...value })
134
- }
135
- clone.currentSizeBytes = this.currentSizeBytes
136
- return clone
137
- }
138
- }
139
-
140
- /**
141
- * Create a file state cache with default limits.
142
- */
143
- export function createFileStateCache(
144
- maxEntries: number = 100,
145
- maxSizeBytes: number = 25 * 1024 * 1024,
146
- ): FileStateCache {
147
- return new FileStateCache(maxEntries, maxSizeBytes)
148
- }