@developerz.ai/ai-claude-compat 0.0.3 → 0.0.5
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 +66 -8
- package/dist/background-process.d.ts +62 -0
- package/dist/background-process.js +146 -0
- package/dist/bash-tool.d.ts +13 -0
- package/dist/bash-tool.js +49 -24
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/subagent.d.ts +16 -4
- package/dist/subagent.js +12 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,24 +29,56 @@ can't read or write outside the root you give it.
|
|
|
29
29
|
import {
|
|
30
30
|
readFileTool, writeFileTool, // Read (offset/limit window) + Write
|
|
31
31
|
editFileTool, multiEditTool, // exact-string Edit + batched MultiEdit
|
|
32
|
-
bashTool,
|
|
32
|
+
bashTool, multiBashTool, // one shell command / an ordered sequence, scoped to cwd
|
|
33
33
|
globTool, grepTool, // file glob + content search
|
|
34
34
|
} from '@developerz.ai/ai-claude-compat';
|
|
35
35
|
|
|
36
36
|
const cwd = process.cwd();
|
|
37
37
|
const tools = {
|
|
38
|
-
read:
|
|
39
|
-
write:
|
|
40
|
-
edit:
|
|
41
|
-
bash:
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
read: readFileTool({ cwd }),
|
|
39
|
+
write: writeFileTool({ cwd }),
|
|
40
|
+
edit: editFileTool({ cwd }),
|
|
41
|
+
bash: bashTool({ cwd }),
|
|
42
|
+
multiBash: multiBashTool({ cwd }),
|
|
43
|
+
grep: grepTool({ cwd }),
|
|
44
|
+
glob: globTool({ cwd }),
|
|
44
45
|
};
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
Pass `tools` straight into a `generateText` / `streamText` call or an
|
|
48
49
|
`Agent`/`ToolLoopAgent`.
|
|
49
50
|
|
|
51
|
+
## Background processes
|
|
52
|
+
|
|
53
|
+
`bashTool`/`multiBashTool` block until the command exits, so they can't hold a
|
|
54
|
+
dev server open. `backgroundProcessTools` does: it spawns `bash -c …` without
|
|
55
|
+
awaiting, returns a process id immediately, and lets the agent tail output, kill
|
|
56
|
+
one process, or kill them all on teardown. **The caller owns lifecycle** — keep
|
|
57
|
+
the returned `manager` and call `killAll()` when the run ends, or processes leak.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { backgroundProcessTools } from '@developerz.ai/ai-claude-compat';
|
|
61
|
+
|
|
62
|
+
const { manager, backgroundBash, bashOutput, killBash, listBackground } =
|
|
63
|
+
backgroundProcessTools({ cwd });
|
|
64
|
+
|
|
65
|
+
const agentTools = { ...tools, backgroundBash, bashOutput, killBash, listBackground };
|
|
66
|
+
// agent: backgroundBash("npm run dev") -> { id }
|
|
67
|
+
// bashOutput(id) -> new stdout/stderr since last poll, running, exitCode
|
|
68
|
+
// killBash(id)
|
|
69
|
+
try {
|
|
70
|
+
await agent.generate({ prompt: 'start the dev server and check it serves /health' });
|
|
71
|
+
} finally {
|
|
72
|
+
manager.killAll(); // teardown — nothing else guarantees the server is stopped
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`bashOutput` is **incremental** (like Claude Code's BashOutput): each call returns
|
|
77
|
+
only the bytes produced since the last call. Buffers are capped (256 KiB/stream by
|
|
78
|
+
default); past the cap the oldest bytes are dropped and `truncated` is set.
|
|
79
|
+
Browser/CDP automation is intentionally **out of scope** — drive a browser with a
|
|
80
|
+
dedicated MCP server (e.g. Playwright MCP), not from this library.
|
|
81
|
+
|
|
50
82
|
## Subagents (subagent-as-tool)
|
|
51
83
|
|
|
52
84
|
`createSubagent` wraps the boilerplate of a `ToolLoopAgent` (model + tools +
|
|
@@ -99,7 +131,9 @@ for (const dir of claudeDirs(process.cwd())) {
|
|
|
99
131
|
| --- | --- |
|
|
100
132
|
| `readFileTool`, `writeFileTool` | Read (offset/limit window) + Write, cwd-scoped |
|
|
101
133
|
| `editFileTool`, `multiEditTool`, `applyEdit` | Exact-string Edit, batched MultiEdit, pure edit helper |
|
|
102
|
-
| `bashTool` |
|
|
134
|
+
| `bashTool` | Run one shell command, cwd as initial dir; returns stdout/stderr/exitCode |
|
|
135
|
+
| `multiBashTool` | Run an ordered sequence of commands; stops at the first non-zero exit |
|
|
136
|
+
| `backgroundProcessTools`, `ProcessManager` | Non-blocking background commands (dev servers): start / tail output / kill / killAll |
|
|
103
137
|
| `globTool`, `grepTool`, `globToRegExp` | File glob + content search |
|
|
104
138
|
| `composeSystemPrompt`, `createSubagent` | System-prompt composer + subagent-as-tool factory |
|
|
105
139
|
| `envBlock` | Render the `<env>` system-context block from `EnvInfo` |
|
|
@@ -116,6 +150,30 @@ ESM only. Runs unchanged on **Node ≥ 20, Bun, and Deno ≥ 1.40**. Peer dep: `
|
|
|
116
150
|
(AI SDK v6). No Anthropic SDK — "Claude-compat" refers to the *conventions*, not
|
|
117
151
|
the provider.
|
|
118
152
|
|
|
153
|
+
## Platform: Linux only
|
|
154
|
+
|
|
155
|
+
The shell tools (`bashTool`, `multiBashTool`) target **Linux**. They spawn
|
|
156
|
+
`bash -c …`, so they need a POSIX `bash` on `PATH` — they are **not** supported
|
|
157
|
+
on native Windows (use WSL) and are only best-effort on macOS. The pure
|
|
158
|
+
filesystem/edit/search tools are platform-neutral, but the package as a whole is
|
|
159
|
+
developed and tested on Linux; treat anything else as unsupported.
|
|
160
|
+
|
|
161
|
+
### Shell environment (rbenv / nvm / asdf, `~/.bashrc`)
|
|
162
|
+
|
|
163
|
+
Commands run via a **non-login, non-interactive** `bash -c` with `BASH_ENV`
|
|
164
|
+
scrubbed. That is deliberate: a login/interactive shell would source
|
|
165
|
+
`/etc/profile` and `~/.bashrc`, which often `cd` away and would defeat the
|
|
166
|
+
cwd lock. The consequence:
|
|
167
|
+
|
|
168
|
+
- **PATH-based tools work** — `rbenv`/`asdf` shims, and any binary already on the
|
|
169
|
+
`PATH` of the process that launched the agent, are inherited (the child gets
|
|
170
|
+
`process.env`). If you can run `ruby`/`node` from the shell you start the agent
|
|
171
|
+
in, the agent can too.
|
|
172
|
+
- **Shell-function tools do not** — `nvm`, and anything that exists only as a
|
|
173
|
+
function defined in `~/.bashrc`, is **not** loaded. Source it yourself inside
|
|
174
|
+
the command (`source ~/.nvm/nvm.sh && nvm use && …`) or put the resolved
|
|
175
|
+
binary on `PATH` before launching.
|
|
176
|
+
|
|
119
177
|
## License
|
|
120
178
|
|
|
121
179
|
MIT · part of [`developerz-ai/ai-task-master`](https://github.com/developerz-ai/ai-task-master)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import { type Tool } from 'ai';
|
|
3
|
+
export type SpawnFn = typeof nodeSpawn;
|
|
4
|
+
export type BackgroundProcessInit = {
|
|
5
|
+
cwd: string;
|
|
6
|
+
maxBufferBytes?: number;
|
|
7
|
+
spawn?: SpawnFn;
|
|
8
|
+
};
|
|
9
|
+
export type ProcessStatus = {
|
|
10
|
+
id: string;
|
|
11
|
+
command: string;
|
|
12
|
+
running: boolean;
|
|
13
|
+
exitCode: number | null;
|
|
14
|
+
pid: number | null;
|
|
15
|
+
};
|
|
16
|
+
export type ProcessOutput = {
|
|
17
|
+
id: string;
|
|
18
|
+
stdout: string;
|
|
19
|
+
stderr: string;
|
|
20
|
+
running: boolean;
|
|
21
|
+
exitCode: number | null;
|
|
22
|
+
truncated: boolean;
|
|
23
|
+
};
|
|
24
|
+
export declare class ProcessManager {
|
|
25
|
+
private readonly cwd;
|
|
26
|
+
private readonly cap;
|
|
27
|
+
private readonly spawn;
|
|
28
|
+
private readonly procs;
|
|
29
|
+
private counter;
|
|
30
|
+
constructor(init: BackgroundProcessInit);
|
|
31
|
+
start(command: string): ProcessStatus;
|
|
32
|
+
output(id: string): ProcessOutput | null;
|
|
33
|
+
kill(id: string, signal?: NodeJS.Signals): boolean;
|
|
34
|
+
killAll(signal?: NodeJS.Signals): void;
|
|
35
|
+
list(): ProcessStatus[];
|
|
36
|
+
private statusOf;
|
|
37
|
+
}
|
|
38
|
+
export type BackgroundBashInput = {
|
|
39
|
+
command: string;
|
|
40
|
+
};
|
|
41
|
+
export type BackgroundBashOutput = ProcessStatus;
|
|
42
|
+
export type BashOutputInput = {
|
|
43
|
+
id: string;
|
|
44
|
+
};
|
|
45
|
+
export type KillBashInput = {
|
|
46
|
+
id: string;
|
|
47
|
+
};
|
|
48
|
+
export type KillBashOutput = {
|
|
49
|
+
id: string;
|
|
50
|
+
killed: boolean;
|
|
51
|
+
};
|
|
52
|
+
export type ListBackgroundOutput = {
|
|
53
|
+
processes: ProcessStatus[];
|
|
54
|
+
};
|
|
55
|
+
export type BackgroundProcessTools = {
|
|
56
|
+
manager: ProcessManager;
|
|
57
|
+
backgroundBash: Tool<BackgroundBashInput, BackgroundBashOutput>;
|
|
58
|
+
bashOutput: Tool<BashOutputInput, ProcessOutput>;
|
|
59
|
+
killBash: Tool<KillBashInput, KillBashOutput>;
|
|
60
|
+
listBackground: Tool<Record<string, never>, ListBackgroundOutput>;
|
|
61
|
+
};
|
|
62
|
+
export declare function backgroundProcessTools(init: BackgroundProcessInit): BackgroundProcessTools;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import { tool } from 'ai';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
const DEFAULT_MAX_BUFFER_BYTES = 256 * 1024;
|
|
5
|
+
class CappedStream {
|
|
6
|
+
cap;
|
|
7
|
+
retained = '';
|
|
8
|
+
produced = 0;
|
|
9
|
+
returned = 0;
|
|
10
|
+
constructor(cap) {
|
|
11
|
+
this.cap = cap;
|
|
12
|
+
}
|
|
13
|
+
append(chunk) {
|
|
14
|
+
this.produced += chunk.length;
|
|
15
|
+
this.retained += chunk;
|
|
16
|
+
if (this.retained.length > this.cap) {
|
|
17
|
+
this.retained = this.retained.slice(this.retained.length - this.cap);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
read() {
|
|
21
|
+
const retainedStart = this.produced - this.retained.length;
|
|
22
|
+
const from = Math.max(this.returned, retainedStart);
|
|
23
|
+
const chunk = this.retained.slice(from - retainedStart);
|
|
24
|
+
const truncated = retainedStart > this.returned;
|
|
25
|
+
this.returned = this.produced;
|
|
26
|
+
return { chunk, truncated };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class ProcessManager {
|
|
30
|
+
cwd;
|
|
31
|
+
cap;
|
|
32
|
+
spawn;
|
|
33
|
+
procs = new Map();
|
|
34
|
+
counter = 0;
|
|
35
|
+
constructor(init) {
|
|
36
|
+
this.cwd = init.cwd;
|
|
37
|
+
this.cap = init.maxBufferBytes ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
38
|
+
this.spawn = init.spawn ?? nodeSpawn;
|
|
39
|
+
}
|
|
40
|
+
start(command) {
|
|
41
|
+
const id = `bg-${++this.counter}`;
|
|
42
|
+
const proc = this.spawn('bash', ['-c', command], {
|
|
43
|
+
cwd: this.cwd,
|
|
44
|
+
env: { ...process.env, BASH_ENV: '' },
|
|
45
|
+
});
|
|
46
|
+
const entry = {
|
|
47
|
+
command,
|
|
48
|
+
proc,
|
|
49
|
+
stdout: new CappedStream(this.cap),
|
|
50
|
+
stderr: new CappedStream(this.cap),
|
|
51
|
+
running: true,
|
|
52
|
+
exitCode: null,
|
|
53
|
+
};
|
|
54
|
+
proc.stdout?.on('data', (d) => entry.stdout.append(d.toString()));
|
|
55
|
+
proc.stderr?.on('data', (d) => entry.stderr.append(d.toString()));
|
|
56
|
+
proc.on('error', (err) => {
|
|
57
|
+
entry.stderr.append(err.message);
|
|
58
|
+
entry.running = false;
|
|
59
|
+
if (entry.exitCode === null)
|
|
60
|
+
entry.exitCode = 1;
|
|
61
|
+
});
|
|
62
|
+
proc.on('exit', (code, signal) => {
|
|
63
|
+
entry.running = false;
|
|
64
|
+
entry.exitCode = code ?? (signal ? 1 : 0);
|
|
65
|
+
});
|
|
66
|
+
this.procs.set(id, entry);
|
|
67
|
+
return this.statusOf(id, entry);
|
|
68
|
+
}
|
|
69
|
+
output(id) {
|
|
70
|
+
const entry = this.procs.get(id);
|
|
71
|
+
if (!entry)
|
|
72
|
+
return null;
|
|
73
|
+
const out = entry.stdout.read();
|
|
74
|
+
const err = entry.stderr.read();
|
|
75
|
+
return {
|
|
76
|
+
id,
|
|
77
|
+
stdout: out.chunk,
|
|
78
|
+
stderr: err.chunk,
|
|
79
|
+
running: entry.running,
|
|
80
|
+
exitCode: entry.exitCode,
|
|
81
|
+
truncated: out.truncated || err.truncated,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
kill(id, signal = 'SIGTERM') {
|
|
85
|
+
const entry = this.procs.get(id);
|
|
86
|
+
if (!entry)
|
|
87
|
+
return false;
|
|
88
|
+
if (entry.running)
|
|
89
|
+
entry.proc.kill(signal);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
killAll(signal = 'SIGTERM') {
|
|
93
|
+
for (const entry of this.procs.values()) {
|
|
94
|
+
if (entry.running)
|
|
95
|
+
entry.proc.kill(signal);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
list() {
|
|
99
|
+
return [...this.procs.entries()].map(([id, entry]) => this.statusOf(id, entry));
|
|
100
|
+
}
|
|
101
|
+
statusOf(id, entry) {
|
|
102
|
+
return {
|
|
103
|
+
id,
|
|
104
|
+
command: entry.command,
|
|
105
|
+
running: entry.running,
|
|
106
|
+
exitCode: entry.exitCode,
|
|
107
|
+
pid: entry.proc.pid ?? null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const backgroundBashInputSchema = z.object({ command: z.string().min(1) });
|
|
112
|
+
const idInputSchema = z.object({ id: z.string().min(1) });
|
|
113
|
+
export function backgroundProcessTools(init) {
|
|
114
|
+
const manager = new ProcessManager(init);
|
|
115
|
+
const backgroundBash = tool({
|
|
116
|
+
description: 'Start a long-running shell command in the background (e.g. a dev server) and return immediately with a process id. Does NOT wait for it to exit. Poll its output with bashOutput(id) and stop it with killBash(id). For a command that finishes quickly, use the blocking bash tool instead.',
|
|
117
|
+
inputSchema: backgroundBashInputSchema,
|
|
118
|
+
execute: async (input) => manager.start(input.command),
|
|
119
|
+
});
|
|
120
|
+
const bashOutput = tool({
|
|
121
|
+
description: 'Read new stdout/stderr produced since the last bashOutput call for a background process id, plus whether it is still running and its exit code once finished.',
|
|
122
|
+
inputSchema: idInputSchema,
|
|
123
|
+
execute: async (input) => manager.output(input.id) ?? {
|
|
124
|
+
id: input.id,
|
|
125
|
+
stdout: '',
|
|
126
|
+
stderr: `no background process with id ${input.id}`,
|
|
127
|
+
running: false,
|
|
128
|
+
exitCode: 1,
|
|
129
|
+
truncated: false,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
const killBash = tool({
|
|
133
|
+
description: 'Stop a background process by id (sends SIGTERM). Idempotent.',
|
|
134
|
+
inputSchema: idInputSchema,
|
|
135
|
+
execute: async (input) => ({
|
|
136
|
+
id: input.id,
|
|
137
|
+
killed: manager.kill(input.id),
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
const listBackground = tool({
|
|
141
|
+
description: 'List all background processes started this session with their running state.',
|
|
142
|
+
inputSchema: z.object({}),
|
|
143
|
+
execute: async () => ({ processes: manager.list() }),
|
|
144
|
+
});
|
|
145
|
+
return { manager, backgroundBash, bashOutput, killBash, listBackground };
|
|
146
|
+
}
|
package/dist/bash-tool.d.ts
CHANGED
|
@@ -5,16 +5,29 @@ declare const bashInputSchema: z.ZodObject<{
|
|
|
5
5
|
command: z.ZodString;
|
|
6
6
|
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
7
7
|
}, z.core.$strip>;
|
|
8
|
+
declare const multiBashInputSchema: z.ZodObject<{
|
|
9
|
+
commands: z.ZodArray<z.ZodString>;
|
|
10
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
11
|
+
}, z.core.$strip>;
|
|
8
12
|
export type BashInput = z.infer<typeof bashInputSchema>;
|
|
9
13
|
export type BashOutput = {
|
|
10
14
|
stdout: string;
|
|
11
15
|
stderr: string;
|
|
12
16
|
exitCode: number;
|
|
13
17
|
};
|
|
18
|
+
export type MultiBashInput = z.infer<typeof multiBashInputSchema>;
|
|
19
|
+
export type MultiBashOutput = {
|
|
20
|
+
results: Array<{
|
|
21
|
+
command: string;
|
|
22
|
+
} & BashOutput>;
|
|
23
|
+
exitCode: number;
|
|
24
|
+
failedAt: number | null;
|
|
25
|
+
};
|
|
14
26
|
export type BashToolInit = {
|
|
15
27
|
cwd: string;
|
|
16
28
|
defaultTimeoutMs?: number;
|
|
17
29
|
exec?: typeof execa;
|
|
18
30
|
};
|
|
19
31
|
export declare function bashTool(init: BashToolInit): Tool<BashInput, BashOutput>;
|
|
32
|
+
export declare function multiBashTool(init: BashToolInit): Tool<MultiBashInput, MultiBashOutput>;
|
|
20
33
|
export {};
|
package/dist/bash-tool.js
CHANGED
|
@@ -5,42 +5,67 @@ const bashInputSchema = z.object({
|
|
|
5
5
|
command: z.string().min(1),
|
|
6
6
|
timeoutMs: z.number().int().positive().optional(),
|
|
7
7
|
});
|
|
8
|
+
const multiBashInputSchema = z.object({
|
|
9
|
+
commands: z.array(z.string().min(1)).min(1),
|
|
10
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
11
|
+
});
|
|
8
12
|
const DEFAULT_BASH_TIMEOUT_MS = 60_000;
|
|
9
13
|
const MAX_BASH_TIMEOUT_MS = 600_000;
|
|
14
|
+
async function runBash(exec, cwd, command, timeout) {
|
|
15
|
+
try {
|
|
16
|
+
const r = await exec('bash', ['-c', command], {
|
|
17
|
+
cwd,
|
|
18
|
+
timeout,
|
|
19
|
+
env: { ...process.env, BASH_ENV: '' },
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
stdout: typeof r.stdout === 'string' ? r.stdout : '',
|
|
23
|
+
stderr: typeof r.stderr === 'string' ? r.stderr : '',
|
|
24
|
+
exitCode: r.exitCode ?? 0,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err instanceof ExecaError) {
|
|
29
|
+
return {
|
|
30
|
+
stdout: typeof err.stdout === 'string' ? err.stdout : '',
|
|
31
|
+
stderr: typeof err.stderr === 'string' ? err.stderr : err.message,
|
|
32
|
+
exitCode: err.exitCode ?? 1,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
stdout: '',
|
|
37
|
+
stderr: err instanceof Error ? err.message : String(err),
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
10
42
|
export function bashTool(init) {
|
|
11
43
|
const exec = init.exec ?? execa;
|
|
12
44
|
const defaultTimeout = init.defaultTimeoutMs ?? DEFAULT_BASH_TIMEOUT_MS;
|
|
13
45
|
return tool({
|
|
14
46
|
description: 'Run a shell command inside the current worktree. Returns stdout, stderr, and exit code. The command runs via `bash -c` with its initial cwd set to the worktree.',
|
|
15
47
|
inputSchema: bashInputSchema,
|
|
48
|
+
execute: (input) => runBash(exec, init.cwd, input.command, Math.min(input.timeoutMs ?? defaultTimeout, MAX_BASH_TIMEOUT_MS)),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function multiBashTool(init) {
|
|
52
|
+
const exec = init.exec ?? execa;
|
|
53
|
+
const defaultTimeout = init.defaultTimeoutMs ?? DEFAULT_BASH_TIMEOUT_MS;
|
|
54
|
+
return tool({
|
|
55
|
+
description: 'Run a sequence of shell commands inside the current worktree, one after another. Stops at the first command that exits non-zero — the remaining commands are not run. Each command runs in its own `bash -c` with cwd reset to the worktree, so chain `cd x && …` within a single command if you need a directory change to persist. Returns one result per command that ran, the overall exit code, and the index of the failing command (failedAt) if any.',
|
|
56
|
+
inputSchema: multiBashInputSchema,
|
|
16
57
|
execute: async (input) => {
|
|
17
58
|
const timeout = Math.min(input.timeoutMs ?? defaultTimeout, MAX_BASH_TIMEOUT_MS);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
stdout: typeof r.stdout === 'string' ? r.stdout : '',
|
|
26
|
-
stderr: typeof r.stderr === 'string' ? r.stderr : '',
|
|
27
|
-
exitCode: r.exitCode ?? 0,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
catch (err) {
|
|
31
|
-
if (err instanceof ExecaError) {
|
|
32
|
-
return {
|
|
33
|
-
stdout: typeof err.stdout === 'string' ? err.stdout : '',
|
|
34
|
-
stderr: typeof err.stderr === 'string' ? err.stderr : err.message,
|
|
35
|
-
exitCode: err.exitCode ?? 1,
|
|
36
|
-
};
|
|
59
|
+
const results = [];
|
|
60
|
+
for (let i = 0; i < input.commands.length; i++) {
|
|
61
|
+
const command = input.commands[i] ?? '';
|
|
62
|
+
const out = await runBash(exec, init.cwd, command, timeout);
|
|
63
|
+
results.push({ command, ...out });
|
|
64
|
+
if (out.exitCode !== 0) {
|
|
65
|
+
return { results, exitCode: out.exitCode, failedAt: i };
|
|
37
66
|
}
|
|
38
|
-
return {
|
|
39
|
-
stdout: '',
|
|
40
|
-
stderr: err instanceof Error ? err.message : String(err),
|
|
41
|
-
exitCode: 1,
|
|
42
|
-
};
|
|
43
67
|
}
|
|
68
|
+
return { results, exitCode: 0, failedAt: null };
|
|
44
69
|
},
|
|
45
70
|
});
|
|
46
71
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { type AgentDefinition, claudeDirs, loadAgents } from './agents-loader.ts';
|
|
2
|
-
export { type
|
|
2
|
+
export { type BackgroundBashInput, type BackgroundBashOutput, type BackgroundProcessInit, type BackgroundProcessTools, type BashOutputInput, backgroundProcessTools, type KillBashInput, type KillBashOutput, type ListBackgroundOutput, ProcessManager, type ProcessOutput, type ProcessStatus, type SpawnFn, } from './background-process.ts';
|
|
3
|
+
export { type BashInput, type BashOutput, type BashToolInit, bashTool, type MultiBashInput, type MultiBashOutput, multiBashTool, } from './bash-tool.ts';
|
|
3
4
|
export { applyEdit, type EditFileInput, type EditFileOutput, type EditSpec, editFileTool, type MultiEditInput, type MultiEditOutput, multiEditTool, } from './edit-tools.ts';
|
|
4
5
|
export { type EnvInfo, envBlock } from './env-block.ts';
|
|
5
6
|
export { asString, asStringArray, type Frontmatter, type FrontmatterValue, parseFrontmatter, } from './frontmatter.ts';
|
|
@@ -7,4 +8,4 @@ export { type ReadFileInput, type ReadFileOutput, readFileTool, type ToolInit, t
|
|
|
7
8
|
export { resolveInside } from './safe-path.ts';
|
|
8
9
|
export { type GlobInput, type GlobOutput, type GrepInput, type GrepOutput, globTool, globToRegExp, grepTool, } from './search-tools.ts';
|
|
9
10
|
export { loadSkills, type SkillDefinition } from './skills-loader.ts';
|
|
10
|
-
export { composeSystemPrompt, createSubagent, type SubagentConfig } from './subagent.ts';
|
|
11
|
+
export { composeSystemPrompt, createSubagent, SUBMIT_TOOL_NAME, type SubagentConfig, submittedOutput, } from './subagent.ts';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { claudeDirs, loadAgents } from "./agents-loader.js";
|
|
2
|
-
export {
|
|
2
|
+
export { backgroundProcessTools, ProcessManager, } from "./background-process.js";
|
|
3
|
+
export { bashTool, multiBashTool, } from "./bash-tool.js";
|
|
3
4
|
export { applyEdit, editFileTool, multiEditTool, } from "./edit-tools.js";
|
|
4
5
|
export { envBlock } from "./env-block.js";
|
|
5
6
|
export { asString, asStringArray, parseFrontmatter, } from "./frontmatter.js";
|
|
@@ -7,4 +8,4 @@ export { readFileTool, writeFileTool, } from "./fs-tools.js";
|
|
|
7
8
|
export { resolveInside } from "./safe-path.js";
|
|
8
9
|
export { globTool, globToRegExp, grepTool, } from "./search-tools.js";
|
|
9
10
|
export { loadSkills } from "./skills-loader.js";
|
|
10
|
-
export { composeSystemPrompt, createSubagent } from "./subagent.js";
|
|
11
|
+
export { composeSystemPrompt, createSubagent, SUBMIT_TOOL_NAME, submittedOutput, } from "./subagent.js";
|
package/dist/subagent.d.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
import { type LanguageModel, type
|
|
1
|
+
import { type LanguageModel, type Tool, ToolLoopAgent, type ToolSet } from 'ai';
|
|
2
|
+
import type { z } from 'zod';
|
|
2
3
|
import { type EnvInfo } from './env-block.ts';
|
|
4
|
+
export declare const SUBMIT_TOOL_NAME = "submit";
|
|
3
5
|
export declare function composeSystemPrompt(style: string, rolePrefix: string, env: string | EnvInfo): string;
|
|
4
|
-
export type SubagentConfig<TOOLS extends ToolSet
|
|
6
|
+
export type SubagentConfig<TOOLS extends ToolSet> = {
|
|
5
7
|
model: LanguageModel;
|
|
6
8
|
tools: TOOLS;
|
|
7
9
|
systemPrompt: string;
|
|
8
|
-
|
|
10
|
+
submit: Tool;
|
|
9
11
|
maxSteps?: number;
|
|
10
12
|
};
|
|
11
|
-
export declare function createSubagent<TOOLS extends ToolSet
|
|
13
|
+
export declare function createSubagent<TOOLS extends ToolSet>(config: SubagentConfig<TOOLS>, defaultMaxSteps: number): ToolLoopAgent<never, TOOLS>;
|
|
14
|
+
type StepsResult = {
|
|
15
|
+
steps: ReadonlyArray<{
|
|
16
|
+
toolCalls: ReadonlyArray<{
|
|
17
|
+
toolName: string;
|
|
18
|
+
input: unknown;
|
|
19
|
+
}>;
|
|
20
|
+
}>;
|
|
21
|
+
};
|
|
22
|
+
export declare function submittedOutput<OUTPUT>(result: StepsResult, outputSchema: z.ZodType<OUTPUT>): OUTPUT | undefined;
|
|
23
|
+
export {};
|
package/dist/subagent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { stepCountIs, ToolLoopAgent } from 'ai';
|
|
1
|
+
import { hasToolCall, stepCountIs, ToolLoopAgent, } from 'ai';
|
|
2
2
|
import { envBlock } from "./env-block.js";
|
|
3
|
+
export const SUBMIT_TOOL_NAME = 'submit';
|
|
3
4
|
export function composeSystemPrompt(style, rolePrefix, env) {
|
|
4
5
|
const info = typeof env === 'string' ? { cwd: env, isGitRepo: true } : env;
|
|
5
6
|
return `${style}${rolePrefix}\n${envBlock(info)}`;
|
|
@@ -7,9 +8,16 @@ export function composeSystemPrompt(style, rolePrefix, env) {
|
|
|
7
8
|
export function createSubagent(config, defaultMaxSteps) {
|
|
8
9
|
return new ToolLoopAgent({
|
|
9
10
|
model: config.model,
|
|
10
|
-
tools: config.tools,
|
|
11
|
+
tools: { ...config.tools, submit: config.submit },
|
|
11
12
|
instructions: config.systemPrompt,
|
|
12
|
-
|
|
13
|
-
stopWhen: stepCountIs(config.maxSteps ?? defaultMaxSteps),
|
|
13
|
+
stopWhen: [stepCountIs(config.maxSteps ?? defaultMaxSteps), hasToolCall(SUBMIT_TOOL_NAME)],
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
+
export function submittedOutput(result, outputSchema) {
|
|
17
|
+
const call = result.steps
|
|
18
|
+
.flatMap((step) => step.toolCalls)
|
|
19
|
+
.find((toolCall) => toolCall.toolName === SUBMIT_TOOL_NAME);
|
|
20
|
+
if (!call)
|
|
21
|
+
return undefined;
|
|
22
|
+
return outputSchema.parse(call.input);
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@developerz.ai/ai-claude-compat",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Claude-Code-style agent primitives for the Vercel AI SDK: FS/bash tools, an <env> system-context block, a subagent-as-tool factory, and .claude/ skills/agents loading.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|