@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.
- package/README.md +144 -31
- package/examples/12-skills.ts +88 -0
- package/examples/13-hooks.ts +88 -0
- package/examples/14-openai-compat.ts +71 -0
- package/package.json +1 -1
- package/src/agent.ts +106 -15
- package/src/engine.ts +169 -59
- package/src/index.ts +51 -1
- package/src/providers/anthropic.ts +60 -0
- package/src/providers/index.ts +34 -0
- package/src/providers/openai.ts +315 -0
- package/src/providers/types.ts +85 -0
- package/src/session.ts +5 -5
- package/src/skills/bundled/commit.ts +38 -0
- package/src/skills/bundled/debug.ts +48 -0
- package/src/skills/bundled/index.ts +28 -0
- package/src/skills/bundled/review.ts +41 -0
- package/src/skills/bundled/simplify.ts +51 -0
- package/src/skills/bundled/test.ts +43 -0
- package/src/skills/index.ts +25 -0
- package/src/skills/registry.ts +133 -0
- package/src/skills/types.ts +99 -0
- package/src/tools/agent-tool.ts +13 -2
- package/src/tools/index.ts +8 -0
- package/src/tools/skill-tool.ts +133 -0
- package/src/tools/types.ts +7 -3
- package/src/types.ts +35 -8
- package/src/utils/compact.ts +18 -17
- package/src/utils/messages.ts +12 -13
- package/src/utils/tokens.ts +29 -6
|
@@ -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
|
+
}
|
package/src/tools/agent-tool.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
117
|
+
model: subModel,
|
|
118
|
+
provider,
|
|
108
119
|
tools,
|
|
109
120
|
systemPrompt,
|
|
110
121
|
maxTurns: agentDef?.maxTurns || 10,
|
package/src/tools/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/tools/types.ts
CHANGED
|
@@ -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):
|
|
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
|
|
64
|
+
input_schema: tool.inputSchema,
|
|
61
65
|
}
|
|
62
66
|
}
|