@codeany/open-agent-sdk 0.1.0 → 0.2.0

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.
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Bundled Skill: debug
3
+ *
4
+ * Systematic debugging of an issue using structured investigation.
5
+ */
6
+
7
+ import { registerSkill } from '../registry.js'
8
+ import type { SkillContentBlock } from '../types.js'
9
+
10
+ const DEBUG_PROMPT = `Debug the described issue using a systematic approach:
11
+
12
+ 1. **Reproduce**: Understand and reproduce the issue
13
+ - Read relevant error messages or logs
14
+ - Identify the failing component
15
+
16
+ 2. **Investigate**: Trace the root cause
17
+ - Read the relevant source code
18
+ - Add logging or use debugging tools if needed
19
+ - Check recent changes that might have introduced the issue (\`git log --oneline -20\`)
20
+
21
+ 3. **Hypothesize**: Form a theory about the cause
22
+ - State your hypothesis clearly before attempting a fix
23
+
24
+ 4. **Fix**: Implement the minimal fix
25
+ - Make the smallest change that resolves the issue
26
+ - Don't refactor unrelated code
27
+
28
+ 5. **Verify**: Confirm the fix works
29
+ - Run relevant tests
30
+ - Check for regressions`
31
+
32
+ export function registerDebugSkill(): void {
33
+ registerSkill({
34
+ name: 'debug',
35
+ description: 'Systematic debugging of an issue using structured investigation.',
36
+ aliases: ['investigate', 'diagnose'],
37
+ userInvocable: true,
38
+ async getPrompt(args): Promise<SkillContentBlock[]> {
39
+ let prompt = DEBUG_PROMPT
40
+ if (args.trim()) {
41
+ prompt += `\n\n## Issue Description\n${args}`
42
+ } else {
43
+ prompt += `\n\nAsk the user to describe the issue they're experiencing.`
44
+ }
45
+ return [{ type: 'text', text: prompt }]
46
+ },
47
+ })
48
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Bundled Skills Initialization
3
+ *
4
+ * Registers all built-in skills at SDK startup.
5
+ */
6
+
7
+ import { registerSimplifySkill } from './simplify.js'
8
+ import { registerCommitSkill } from './commit.js'
9
+ import { registerReviewSkill } from './review.js'
10
+ import { registerDebugSkill } from './debug.js'
11
+ import { registerTestSkill } from './test.js'
12
+
13
+ let initialized = false
14
+
15
+ /**
16
+ * Initialize all bundled skills.
17
+ * Safe to call multiple times (idempotent).
18
+ */
19
+ export function initBundledSkills(): void {
20
+ if (initialized) return
21
+ initialized = true
22
+
23
+ registerSimplifySkill()
24
+ registerCommitSkill()
25
+ registerReviewSkill()
26
+ registerDebugSkill()
27
+ registerTestSkill()
28
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Bundled Skill: review
3
+ *
4
+ * Review code changes (PR or local diff) for issues and improvements.
5
+ */
6
+
7
+ import { registerSkill } from '../registry.js'
8
+ import type { SkillContentBlock } from '../types.js'
9
+
10
+ const REVIEW_PROMPT = `Review the current code changes for potential issues. Follow these steps:
11
+
12
+ 1. Run \`git diff\` to see uncommitted changes, or \`git diff main...HEAD\` for branch changes
13
+ 2. For each changed file, analyze:
14
+ - **Correctness**: Logic errors, edge cases, off-by-one errors
15
+ - **Security**: Injection vulnerabilities, auth issues, data exposure
16
+ - **Performance**: N+1 queries, unnecessary allocations, blocking I/O
17
+ - **Style**: Naming, consistency with surrounding code, readability
18
+ - **Testing**: Are the changes adequately tested?
19
+ 3. Provide a summary with:
20
+ - Critical issues (must fix)
21
+ - Suggestions (nice to have)
22
+ - Questions (need clarification)
23
+
24
+ Be specific: reference file names, line numbers, and suggest fixes.`
25
+
26
+ export function registerReviewSkill(): void {
27
+ registerSkill({
28
+ name: 'review',
29
+ description: 'Review code changes for correctness, security, performance, and style issues.',
30
+ aliases: ['review-pr', 'cr'],
31
+ allowedTools: ['Bash', 'Read', 'Glob', 'Grep'],
32
+ userInvocable: true,
33
+ async getPrompt(args): Promise<SkillContentBlock[]> {
34
+ let prompt = REVIEW_PROMPT
35
+ if (args.trim()) {
36
+ prompt += `\n\nFocus area: ${args}`
37
+ }
38
+ return [{ type: 'text', text: prompt }]
39
+ },
40
+ })
41
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Bundled Skill: simplify
3
+ *
4
+ * Reviews changed code for reuse opportunities, code quality issues,
5
+ * and efficiency improvements, then fixes any issues found.
6
+ */
7
+
8
+ import { registerSkill } from '../registry.js'
9
+ import type { SkillContentBlock } from '../types.js'
10
+
11
+ const SIMPLIFY_PROMPT = `Review the recently changed code for three categories of improvements. Launch 3 parallel Agent sub-tasks:
12
+
13
+ ## Task 1: Reuse Analysis
14
+ Look for:
15
+ - Duplicated code that could be consolidated
16
+ - Existing utilities or helpers that could replace new code
17
+ - Patterns that should be extracted into shared functions
18
+ - Re-implementations of functionality that already exists elsewhere
19
+
20
+ ## Task 2: Code Quality
21
+ Look for:
22
+ - Overly complex logic that could be simplified
23
+ - Poor naming or unclear intent
24
+ - Missing edge case handling
25
+ - Unnecessary abstractions or over-engineering
26
+ - Dead code or unused imports
27
+
28
+ ## Task 3: Efficiency
29
+ Look for:
30
+ - Unnecessary allocations or copies
31
+ - N+1 query patterns or redundant I/O
32
+ - Blocking operations that could be async
33
+ - Inefficient data structures for the access pattern
34
+ - Unnecessary re-computation
35
+
36
+ After all three analyses complete, fix any issues found. Prioritize by impact.`
37
+
38
+ export function registerSimplifySkill(): void {
39
+ registerSkill({
40
+ name: 'simplify',
41
+ description: 'Review changed code for reuse, quality, and efficiency, then fix any issues found.',
42
+ userInvocable: true,
43
+ async getPrompt(args): Promise<SkillContentBlock[]> {
44
+ let prompt = SIMPLIFY_PROMPT
45
+ if (args.trim()) {
46
+ prompt += `\n\n## Additional Focus\n${args}`
47
+ }
48
+ return [{ type: 'text', text: prompt }]
49
+ },
50
+ })
51
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Bundled Skill: test
3
+ *
4
+ * Run tests and analyze failures.
5
+ */
6
+
7
+ import { registerSkill } from '../registry.js'
8
+ import type { SkillContentBlock } from '../types.js'
9
+
10
+ const TEST_PROMPT = `Run the project's test suite and analyze the results:
11
+
12
+ 1. **Discover**: Find the test runner configuration
13
+ - Look for package.json scripts, jest.config, vitest.config, pytest.ini, etc.
14
+ - Identify the appropriate test command
15
+
16
+ 2. **Execute**: Run the tests
17
+ - Run the full test suite or specific tests if specified
18
+ - Capture output including failures and errors
19
+
20
+ 3. **Analyze**: If tests fail:
21
+ - Read the failing test to understand what it expects
22
+ - Read the source code being tested
23
+ - Identify why the test is failing
24
+ - Fix the issue (in tests or source as appropriate)
25
+
26
+ 4. **Re-verify**: Run the failing tests again to confirm the fix`
27
+
28
+ export function registerTestSkill(): void {
29
+ registerSkill({
30
+ name: 'test',
31
+ description: 'Run tests and analyze failures, fixing any issues found.',
32
+ aliases: ['run-tests'],
33
+ allowedTools: ['Bash', 'Read', 'Write', 'Edit', 'Glob', 'Grep'],
34
+ userInvocable: true,
35
+ async getPrompt(args): Promise<SkillContentBlock[]> {
36
+ let prompt = TEST_PROMPT
37
+ if (args.trim()) {
38
+ prompt += `\n\nSpecific test target: ${args}`
39
+ }
40
+ return [{ type: 'text', text: prompt }]
41
+ },
42
+ })
43
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Skills Module - Public API
3
+ */
4
+
5
+ // Types
6
+ export type {
7
+ SkillDefinition,
8
+ SkillContentBlock,
9
+ SkillResult,
10
+ } from './types.js'
11
+
12
+ // Registry
13
+ export {
14
+ registerSkill,
15
+ getSkill,
16
+ getAllSkills,
17
+ getUserInvocableSkills,
18
+ hasSkill,
19
+ unregisterSkill,
20
+ clearSkills,
21
+ formatSkillsForPrompt,
22
+ } from './registry.js'
23
+
24
+ // Bundled skills
25
+ export { initBundledSkills } from './bundled/index.js'
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Skill Registry
3
+ *
4
+ * Central registry for managing skill definitions.
5
+ * Skills can be registered programmatically or loaded from bundled definitions.
6
+ */
7
+
8
+ import type { SkillDefinition } from './types.js'
9
+
10
+ /** Internal skill store */
11
+ const skills: Map<string, SkillDefinition> = new Map()
12
+
13
+ /** Alias -> skill name mapping */
14
+ const aliases: Map<string, string> = new Map()
15
+
16
+ /**
17
+ * Register a skill definition.
18
+ */
19
+ export function registerSkill(definition: SkillDefinition): void {
20
+ skills.set(definition.name, definition)
21
+
22
+ // Register aliases
23
+ if (definition.aliases) {
24
+ for (const alias of definition.aliases) {
25
+ aliases.set(alias, definition.name)
26
+ }
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Get a skill by name or alias.
32
+ */
33
+ export function getSkill(name: string): SkillDefinition | undefined {
34
+ // Direct lookup
35
+ const direct = skills.get(name)
36
+ if (direct) return direct
37
+
38
+ // Alias lookup
39
+ const resolved = aliases.get(name)
40
+ if (resolved) return skills.get(resolved)
41
+
42
+ return undefined
43
+ }
44
+
45
+ /**
46
+ * Get all registered skills.
47
+ */
48
+ export function getAllSkills(): SkillDefinition[] {
49
+ return Array.from(skills.values())
50
+ }
51
+
52
+ /**
53
+ * Get all user-invocable skills (for /command listing).
54
+ */
55
+ export function getUserInvocableSkills(): SkillDefinition[] {
56
+ return getAllSkills().filter(
57
+ (s) => s.userInvocable !== false && (!s.isEnabled || s.isEnabled()),
58
+ )
59
+ }
60
+
61
+ /**
62
+ * Check if a skill exists.
63
+ */
64
+ export function hasSkill(name: string): boolean {
65
+ return skills.has(name) || aliases.has(name)
66
+ }
67
+
68
+ /**
69
+ * Remove a skill.
70
+ */
71
+ export function unregisterSkill(name: string): boolean {
72
+ const skill = skills.get(name)
73
+ if (!skill) return false
74
+
75
+ // Remove aliases
76
+ if (skill.aliases) {
77
+ for (const alias of skill.aliases) {
78
+ aliases.delete(alias)
79
+ }
80
+ }
81
+
82
+ return skills.delete(name)
83
+ }
84
+
85
+ /**
86
+ * Clear all skills (for testing).
87
+ */
88
+ export function clearSkills(): void {
89
+ skills.clear()
90
+ aliases.clear()
91
+ }
92
+
93
+ /**
94
+ * Format skills listing for system prompt injection.
95
+ *
96
+ * Uses a budget system: skills listing gets a limited character budget
97
+ * to avoid bloating the context window.
98
+ */
99
+ export function formatSkillsForPrompt(
100
+ contextWindowTokens?: number,
101
+ ): string {
102
+ const invocable = getUserInvocableSkills()
103
+ if (invocable.length === 0) return ''
104
+
105
+ // Budget: 1% of context window in characters (4 chars per token)
106
+ const CHARS_PER_TOKEN = 4
107
+ const DEFAULT_BUDGET = 8000
108
+ const MAX_DESC_CHARS = 250
109
+ const budget = contextWindowTokens
110
+ ? Math.floor(contextWindowTokens * 0.01 * CHARS_PER_TOKEN)
111
+ : DEFAULT_BUDGET
112
+
113
+ const lines: string[] = []
114
+ let used = 0
115
+
116
+ for (const skill of invocable) {
117
+ const desc = skill.description.length > MAX_DESC_CHARS
118
+ ? skill.description.slice(0, MAX_DESC_CHARS) + '...'
119
+ : skill.description
120
+
121
+ const trigger = skill.whenToUse
122
+ ? ` TRIGGER when: ${skill.whenToUse}`
123
+ : ''
124
+
125
+ const line = `- ${skill.name}: ${desc}${trigger}`
126
+
127
+ if (used + line.length > budget) break
128
+ lines.push(line)
129
+ used += line.length
130
+ }
131
+
132
+ return lines.join('\n')
133
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Skill System Types
3
+ *
4
+ * Skills are reusable prompt templates that extend agent capabilities.
5
+ * They can be invoked by the model via the Skill tool or by users via /skillname.
6
+ */
7
+
8
+ import type { ToolContext } from '../types.js'
9
+ import type { HookConfig } from '../hooks.js'
10
+
11
+ /**
12
+ * Content block for skill prompts (compatible with Anthropic API).
13
+ */
14
+ export type SkillContentBlock =
15
+ | { type: 'text'; text: string }
16
+ | { type: 'image'; source: { type: 'base64'; media_type: string; data: string } }
17
+
18
+ /**
19
+ * Bundled skill definition.
20
+ *
21
+ * Inspired by Claude Code's skill system. Skills provide specialized
22
+ * capabilities by injecting context-specific prompts with optional
23
+ * tool restrictions and model overrides.
24
+ */
25
+ export interface SkillDefinition {
26
+ /** Unique skill name (e.g., 'simplify', 'commit') */
27
+ name: string
28
+
29
+ /** Human-readable description */
30
+ description: string
31
+
32
+ /** Alternative names for the skill */
33
+ aliases?: string[]
34
+
35
+ /** When the model should invoke this skill (used in system prompt) */
36
+ whenToUse?: string
37
+
38
+ /** Hint for expected arguments */
39
+ argumentHint?: string
40
+
41
+ /** Tools the skill is allowed to use (empty = all tools) */
42
+ allowedTools?: string[]
43
+
44
+ /** Model override for this skill */
45
+ model?: string
46
+
47
+ /** Whether the skill can be invoked by users via /command */
48
+ userInvocable?: boolean
49
+
50
+ /** Runtime check for availability */
51
+ isEnabled?: () => boolean
52
+
53
+ /** Hook overrides while skill is active */
54
+ hooks?: HookConfig
55
+
56
+ /** Execution context: 'inline' runs in current context, 'fork' spawns a subagent */
57
+ context?: 'inline' | 'fork'
58
+
59
+ /** Subagent type for forked execution */
60
+ agent?: string
61
+
62
+ /**
63
+ * Generate the prompt content blocks for this skill.
64
+ *
65
+ * @param args - User-provided arguments (e.g., from "/simplify focus on error handling")
66
+ * @param context - Tool execution context (cwd, etc.)
67
+ * @returns Content blocks to inject into the conversation
68
+ */
69
+ getPrompt: (
70
+ args: string,
71
+ context: ToolContext,
72
+ ) => Promise<SkillContentBlock[]>
73
+ }
74
+
75
+ /**
76
+ * Result of executing a skill.
77
+ */
78
+ export interface SkillResult {
79
+ /** Whether execution succeeded */
80
+ success: boolean
81
+
82
+ /** Skill name that was executed */
83
+ skillName: string
84
+
85
+ /** Execution status */
86
+ status: 'inline' | 'forked'
87
+
88
+ /** Allowed tools override (for inline execution) */
89
+ allowedTools?: string[]
90
+
91
+ /** Model override */
92
+ model?: string
93
+
94
+ /** Result text (for forked execution) */
95
+ result?: string
96
+
97
+ /** Error message */
98
+ error?: string
99
+ }
@@ -8,7 +8,7 @@
8
8
  import type { ToolDefinition, ToolContext, ToolResult, AgentDefinition } from '../types.js'
9
9
  import { QueryEngine } from '../engine.js'
10
10
  import { getAllBaseTools, filterTools } from './index.js'
11
- import { toApiTool } from './types.js'
11
+ import { createProvider, type ApiType } from '../providers/index.js'
12
12
 
13
13
  // Store for registered agent definitions
14
14
  let registeredAgents: Record<string, AgentDefinition> = {}
@@ -101,10 +101,21 @@ export const AgentTool: ToolDefinition = {
101
101
  const systemPrompt = agentDef?.prompt ||
102
102
  'You are a helpful assistant. Complete the given task using the available tools.'
103
103
 
104
+ // Inherit provider and model from parent agent context, fall back to env vars
105
+ const subModel = input.model || context.model || process.env.CODEANY_MODEL || 'claude-sonnet-4-6'
106
+ const provider = context.provider ?? createProvider(
107
+ (context.apiType || process.env.CODEANY_API_TYPE as ApiType) || 'anthropic-messages',
108
+ {
109
+ apiKey: process.env.CODEANY_API_KEY,
110
+ baseURL: process.env.CODEANY_BASE_URL,
111
+ },
112
+ )
113
+
104
114
  // Create subagent engine
105
115
  const engine = new QueryEngine({
106
116
  cwd: context.cwd,
107
- model: input.model || process.env.CODEANY_MODEL || 'claude-sonnet-4-6',
117
+ model: subModel,
118
+ provider,
108
119
  tools,
109
120
  systemPrompt,
110
121
  maxTurns: agentDef?.maxTurns || 10,
@@ -62,6 +62,9 @@ import { ConfigTool } from './config-tool.js'
62
62
  // Todo
63
63
  import { TodoWriteTool } from './todo-tool.js'
64
64
 
65
+ // Skill
66
+ import { SkillTool } from './skill-tool.js'
67
+
65
68
  /**
66
69
  * All built-in tools (30+).
67
70
  */
@@ -125,6 +128,9 @@ const ALL_TOOLS: ToolDefinition[] = [
125
128
 
126
129
  // Todo
127
130
  TodoWriteTool,
131
+
132
+ // Skill
133
+ SkillTool,
128
134
  ]
129
135
 
130
136
  /**
@@ -226,6 +232,8 @@ export {
226
232
  ConfigTool,
227
233
  // Todo
228
234
  TodoWriteTool,
235
+ // Skill
236
+ SkillTool,
229
237
  }
230
238
 
231
239
  // Re-export helpers
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Skill Tool
3
+ *
4
+ * Allows the model to invoke registered skills by name.
5
+ * Skills are prompt templates that provide specialized capabilities.
6
+ */
7
+
8
+ import type { ToolDefinition, ToolResult, ToolContext } from '../types.js'
9
+ import { getSkill, getUserInvocableSkills } from '../skills/registry.js'
10
+
11
+ export const SkillTool: ToolDefinition = {
12
+ name: 'Skill',
13
+ description:
14
+ 'Execute a skill within the current conversation. ' +
15
+ 'Skills provide specialized capabilities and domain knowledge. ' +
16
+ 'Use this tool with the skill name and optional arguments. ' +
17
+ 'Available skills are listed in system-reminder messages.',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ skill: {
22
+ type: 'string',
23
+ description: 'The skill name to execute (e.g., "commit", "review", "simplify")',
24
+ },
25
+ args: {
26
+ type: 'string',
27
+ description: 'Optional arguments for the skill',
28
+ },
29
+ },
30
+ required: ['skill'],
31
+ },
32
+
33
+ isReadOnly: () => false,
34
+ isConcurrencySafe: () => false,
35
+ isEnabled: () => getUserInvocableSkills().length > 0,
36
+
37
+ async prompt(): Promise<string> {
38
+ const skills = getUserInvocableSkills()
39
+ if (skills.length === 0) return ''
40
+
41
+ const lines = skills.map((s) => {
42
+ const desc =
43
+ s.description.length > 200
44
+ ? s.description.slice(0, 200) + '...'
45
+ : s.description
46
+ return `- ${s.name}: ${desc}`
47
+ })
48
+
49
+ return (
50
+ 'Execute a skill within the main conversation.\n\n' +
51
+ 'Available skills:\n' +
52
+ lines.join('\n') +
53
+ '\n\nWhen a skill matches the user\'s request, invoke it using the Skill tool.'
54
+ )
55
+ },
56
+
57
+ async call(input: any, context: ToolContext): Promise<ToolResult> {
58
+ const skillName: string = input.skill
59
+ const args: string = input.args || ''
60
+
61
+ if (!skillName) {
62
+ return {
63
+ type: 'tool_result',
64
+ tool_use_id: '',
65
+ content: 'Error: skill name is required',
66
+ is_error: true,
67
+ }
68
+ }
69
+
70
+ const skill = getSkill(skillName)
71
+ if (!skill) {
72
+ const available = getUserInvocableSkills()
73
+ .map((s) => s.name)
74
+ .join(', ')
75
+ return {
76
+ type: 'tool_result',
77
+ tool_use_id: '',
78
+ content: `Error: Unknown skill "${skillName}". Available skills: ${available || 'none'}`,
79
+ is_error: true,
80
+ }
81
+ }
82
+
83
+ // Check if skill is enabled
84
+ if (skill.isEnabled && !skill.isEnabled()) {
85
+ return {
86
+ type: 'tool_result',
87
+ tool_use_id: '',
88
+ content: `Error: Skill "${skillName}" is currently disabled`,
89
+ is_error: true,
90
+ }
91
+ }
92
+
93
+ try {
94
+ // Get skill prompt
95
+ const contentBlocks = await skill.getPrompt(args, context)
96
+
97
+ // Convert content blocks to text
98
+ const promptText = contentBlocks
99
+ .filter((b): b is { type: 'text'; text: string } => b.type === 'text')
100
+ .map((b) => b.text)
101
+ .join('\n\n')
102
+
103
+ // Build result with metadata
104
+ const result: Record<string, unknown> = {
105
+ success: true,
106
+ commandName: skill.name,
107
+ status: skill.context === 'fork' ? 'forked' : 'inline',
108
+ prompt: promptText,
109
+ }
110
+
111
+ if (skill.allowedTools) {
112
+ result.allowedTools = skill.allowedTools
113
+ }
114
+
115
+ if (skill.model) {
116
+ result.model = skill.model
117
+ }
118
+
119
+ return {
120
+ type: 'tool_result',
121
+ tool_use_id: '',
122
+ content: JSON.stringify(result),
123
+ }
124
+ } catch (err: any) {
125
+ return {
126
+ type: 'tool_result',
127
+ tool_use_id: '',
128
+ content: `Error executing skill "${skillName}": ${err.message}`,
129
+ is_error: true,
130
+ }
131
+ }
132
+ },
133
+ }
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import type { ToolDefinition, ToolInputSchema, ToolContext, ToolResult } from '../types.js'
6
- import type Anthropic from '@anthropic-ai/sdk'
7
6
 
8
7
  /**
9
8
  * Helper to create a tool definition with sensible defaults.
@@ -52,11 +51,16 @@ export function defineTool(config: {
52
51
 
53
52
  /**
54
53
  * Convert a ToolDefinition to API-compatible tool format.
54
+ * Returns the normalized tool format used by providers.
55
55
  */
56
- export function toApiTool(tool: ToolDefinition): Anthropic.Tool {
56
+ export function toApiTool(tool: ToolDefinition): {
57
+ name: string
58
+ description: string
59
+ input_schema: ToolInputSchema
60
+ } {
57
61
  return {
58
62
  name: tool.name,
59
63
  description: tool.description,
60
- input_schema: tool.inputSchema as Anthropic.Tool.InputSchema,
64
+ input_schema: tool.inputSchema,
61
65
  }
62
66
  }