Skip to content

Commit 604eabe

Browse files
ZhiXiao-Linclaude
andcommitted
fix(sdk): bump SDK versions to 0.7.1 and add examples
The v0.7.1 release failed to publish npm and PyPI packages because SDK manifests (package.json, pyproject.toml, Cargo.toml) were not bumped from 0.7.0 to 0.7.1. npm returned E403 (version exists) and PyPI skipped all wheels (already exist), but both errors were silently swallowed by `|| true` and `--skip-existing`. Also adds Python and Node.js SDK examples: - sdk/python/examples/sdk_basic.py (offline tool tests) - sdk/python/examples/sdk_chat.py (LLM streaming) - sdk/node/examples/sdk_basic.mjs (offline tool tests) - sdk/node/examples/sdk_chat.mjs (LLM streaming) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 441a61c commit 604eabe

7 files changed

Lines changed: 808 additions & 3 deletions

File tree

sdk/node/examples/sdk_basic.mjs

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Basic SDK usage example — Agent.create(), session, and tool execution.
4+
*
5+
* Demonstrates:
6+
* - Loading config from `.a3s/config.hcl`
7+
* - Creating agents with Agent.create()
8+
* - Creating workspace-bound sessions
9+
* - Direct tool calls via session.tool(name, args) for all built-in tools
10+
* - Convenience methods: bash(), readFile(), glob(), grep()
11+
*
12+
* No LLM calls — runs entirely offline.
13+
*
14+
* Usage:
15+
* # Build the native module first
16+
* cd crates/code/sdk/node && npm run build
17+
*
18+
* # Run the example
19+
* node examples/sdk_basic.mjs
20+
*
21+
* # Or with a custom config path
22+
* A3S_CONFIG=/path/to/config.hcl node examples/sdk_basic.mjs
23+
*/
24+
25+
import { Agent } from '../index.js';
26+
import { mkdtempSync, writeFileSync, readFileSync, mkdirSync } from 'fs';
27+
import { join, dirname } from 'path';
28+
import { tmpdir } from 'os';
29+
import { fileURLToPath } from 'url';
30+
31+
const __dirname = dirname(fileURLToPath(import.meta.url));
32+
33+
function repoRoot() {
34+
return join(__dirname, '..', '..', '..', '..', '..');
35+
}
36+
37+
function resolveConfig() {
38+
if (process.env.A3S_CONFIG) return process.env.A3S_CONFIG;
39+
return join(repoRoot(), '.a3s', 'config.hcl');
40+
}
41+
42+
function assert(condition, message) {
43+
if (!condition) throw new Error(`Assertion failed: ${message}`);
44+
}
45+
46+
async function main() {
47+
const configPath = resolveConfig();
48+
console.log('=== A3S Code Node.js SDK Basic Example ===\n');
49+
console.log(`Config: ${configPath}\n`);
50+
51+
// -- 1. Agent.create() ---------------------------------------------------
52+
console.log('--- Agent.create() ---');
53+
const agent = await Agent.create(configPath);
54+
console.log('[ok] Agent created\n');
55+
56+
// -- 2. Create session ----------------------------------------------------
57+
const workspace = mkdtempSync(join(tmpdir(), 'a3s-node-'));
58+
const session = agent.session(workspace);
59+
console.log(`[ok] Session bound to ${workspace}\n`);
60+
61+
// -- 3. Tool: bash --------------------------------------------------------
62+
console.log('--- Tool: bash ---');
63+
const output = await session.bash("echo 'Hello from Node.js SDK'");
64+
assert(output.includes('Hello from Node.js SDK'), `unexpected: ${output}`);
65+
console.log(` output: ${output.trim()}`);
66+
67+
// -- 4. Tool: write + read ------------------------------------------------
68+
console.log('\n--- Tool: write + read ---');
69+
const filePath = join(workspace, 'demo.txt');
70+
await session.tool('write', {
71+
file_path: filePath,
72+
content: 'Hello from Node.js SDK!\nLine 2\nLine 3',
73+
});
74+
console.log(` wrote: ${filePath}`);
75+
76+
const content = await session.readFile(filePath);
77+
const lines = content.split('\n');
78+
console.log(` read back (${lines.length} lines):`);
79+
lines.slice(0, 3).forEach(line => console.log(` ${line}`));
80+
81+
// -- 5. Tool: edit --------------------------------------------------------
82+
console.log('\n--- session.tool("edit") ---');
83+
const editResult = await session.tool('edit', {
84+
file_path: filePath,
85+
old_string: 'Line 2',
86+
new_string: 'Line 2 (edited)',
87+
});
88+
console.log(` exit_code: ${editResult.exitCode}`);
89+
assert(editResult.exitCode === 0, `edit failed: ${editResult.output}`);
90+
const edited = readFileSync(join(workspace, 'demo.txt'), 'utf-8');
91+
assert(edited.includes('Line 2 (edited)'), 'edit not applied');
92+
console.log(" verified: file contains 'Line 2 (edited)'");
93+
94+
// -- 6. Tool: patch -------------------------------------------------------
95+
console.log('\n--- session.tool("patch") ---');
96+
const patchResult = await session.tool('patch', {
97+
file_path: filePath,
98+
diff: "@@ -2,2 +2,2 @@\n-Line 2 (edited)\n+Line 2 (patched)\n Line 3",
99+
});
100+
console.log(` exit_code: ${patchResult.exitCode}`);
101+
if (patchResult.exitCode === 0) {
102+
const patched = readFileSync(join(workspace, 'demo.txt'), 'utf-8');
103+
assert(patched.includes('Line 2 (patched)'), 'patch not applied');
104+
console.log(" verified: file contains 'Line 2 (patched)'");
105+
} else {
106+
console.log(` patch output: ${patchResult.output.trim()}`);
107+
}
108+
109+
// -- 7. Tool: read --------------------------------------------------------
110+
console.log('\n--- session.tool("read") ---');
111+
const readResult = await session.tool('read', {
112+
file_path: filePath,
113+
offset: 0,
114+
limit: 5,
115+
});
116+
console.log(` exit_code: ${readResult.exitCode}`);
117+
assert(readResult.exitCode === 0, `read failed: ${readResult.output}`);
118+
console.log(' content:');
119+
readResult.output.split('\n').slice(0, 3).forEach(line => console.log(` ${line}`));
120+
121+
// -- 8. Tool: ls ----------------------------------------------------------
122+
console.log('\n--- session.tool("ls") ---');
123+
writeFileSync(join(workspace, 'alpha.rs'), 'fn main() {}');
124+
writeFileSync(join(workspace, 'beta.rs'), 'fn test() {}');
125+
writeFileSync(join(workspace, 'gamma.txt'), 'text file');
126+
mkdirSync(join(workspace, 'subdir'), { recursive: true });
127+
128+
const lsResult = await session.tool('ls', { path: workspace });
129+
console.log(` exit_code: ${lsResult.exitCode}`);
130+
assert(lsResult.exitCode === 0, `ls failed: ${lsResult.output}`);
131+
assert(lsResult.output.includes('alpha.rs'), 'ls should list alpha.rs');
132+
assert(lsResult.output.includes('subdir'), 'ls should list subdir');
133+
console.log(' entries:');
134+
lsResult.output.split('\n').slice(0, 6).forEach(line => console.log(` ${line}`));
135+
136+
// -- 9. Tool: glob --------------------------------------------------------
137+
console.log('\n--- session.tool("glob") ---');
138+
const globResult = await session.tool('glob', { pattern: '*.rs' });
139+
console.log(` exit_code: ${globResult.exitCode}`);
140+
assert(globResult.exitCode === 0, `glob failed: ${globResult.output}`);
141+
assert(globResult.output.includes('alpha.rs'), 'glob should find alpha.rs');
142+
console.log(` matches: ${globResult.output.trim()}`);
143+
144+
// -- 10. Tool: grep -------------------------------------------------------
145+
console.log('\n--- session.tool("grep") ---');
146+
const grepResult = await session.tool('grep', { pattern: 'fn ', glob: '*.rs' });
147+
console.log(` exit_code: ${grepResult.exitCode}`);
148+
assert(grepResult.exitCode === 0, `grep failed: ${grepResult.output}`);
149+
assert(grepResult.output.includes('fn '), "grep should find 'fn '");
150+
console.log(' output:');
151+
grepResult.output.split('\n').slice(0, 5).forEach(line => console.log(` ${line}`));
152+
153+
// -- 11. Tool: bash (direct) ----------------------------------------------
154+
console.log('\n--- session.tool("bash") ---');
155+
const bashResult = await session.tool('bash', {
156+
command: "echo 'direct tool call' && date +%Y",
157+
timeout: 5000,
158+
});
159+
console.log(` exit_code: ${bashResult.exitCode}`);
160+
assert(bashResult.exitCode === 0, `bash failed: ${bashResult.output}`);
161+
assert(bashResult.output.includes('direct tool call'));
162+
console.log(` output: ${bashResult.output.trim()}`);
163+
164+
// -- 12. Tool: web_fetch --------------------------------------------------
165+
console.log('\n--- session.tool("web_fetch") ---');
166+
const fetchResult = await session.tool('web_fetch', {
167+
url: 'https://httpbin.org/get',
168+
format: 'text',
169+
timeout: 15,
170+
});
171+
console.log(` exit_code: ${fetchResult.exitCode}`);
172+
assert(fetchResult.exitCode === 0, `web_fetch failed: ${fetchResult.output}`);
173+
console.log(' response (first 5 lines):');
174+
fetchResult.output.split('\n').slice(0, 5).forEach(line => console.log(` ${line}`));
175+
176+
// -- 13. Tool: web_search -------------------------------------------------
177+
console.log('\n--- session.tool("web_search") ---');
178+
const searchResult = await session.tool('web_search', {
179+
query: 'Rust programming language',
180+
engines: 'ddg,wiki',
181+
limit: 3,
182+
timeout: 15,
183+
format: 'text',
184+
});
185+
console.log(` exit_code: ${searchResult.exitCode}`);
186+
if (searchResult.exitCode === 0) {
187+
console.log(' results (first 5 lines):');
188+
searchResult.output.split('\n').slice(0, 5).forEach(line => console.log(` ${line}`));
189+
} else {
190+
const firstLine = searchResult.output.split('\n')[0] || '(empty)';
191+
console.log(` [soft-fail] web_search error: ${firstLine}`);
192+
}
193+
194+
// -- 14. Tool: cron (full lifecycle) --------------------------------------
195+
console.log('\n--- session.tool("cron") -- full lifecycle ---');
196+
const root = repoRoot();
197+
const cronSession = agent.session(root);
198+
console.log(` workspace: ${root} (cron data -> .a3s/cron/)`);
199+
200+
// parse
201+
console.log("\n [parse] 'every 5 minutes'");
202+
const parseResult = await cronSession.tool('cron', { action: 'parse', input: 'every 5 minutes' });
203+
console.log(` exit_code: ${parseResult.exitCode}`);
204+
assert(parseResult.exitCode === 0, `cron parse failed: ${parseResult.output}`);
205+
assert(parseResult.output.includes('*/5'));
206+
console.log(` output: ${parseResult.output.trim()}`);
207+
208+
// add
209+
const jobName = `node-sdk-test-${Date.now()}`;
210+
console.log(`\n [add] job '${jobName}' schedule='*/10 * * * *'`);
211+
const addResult = await cronSession.tool('cron', {
212+
action: 'add',
213+
name: jobName,
214+
schedule: '*/10 * * * *',
215+
command: "echo 'node sdk cron test'",
216+
});
217+
console.log(` exit_code: ${addResult.exitCode}`);
218+
assert(addResult.exitCode === 0, `cron add failed: ${addResult.output}`);
219+
220+
// Extract job ID
221+
let jobId = null;
222+
for (const line of addResult.output.split('\n')) {
223+
const trimmed = line.trim();
224+
if (trimmed.startsWith('ID: ')) {
225+
jobId = trimmed.slice(4);
226+
break;
227+
}
228+
}
229+
assert(jobId, `could not extract job ID from: ${addResult.output}`);
230+
console.log(` job_id: ${jobId}`);
231+
232+
// list
233+
console.log('\n [list]');
234+
const listResult = await cronSession.tool('cron', { action: 'list' });
235+
assert(listResult.exitCode === 0);
236+
assert(listResult.output.includes(jobName));
237+
console.log(` found '${jobName}' in list`);
238+
239+
// get
240+
console.log(`\n [get] id=${jobId}`);
241+
const getResult = await cronSession.tool('cron', { action: 'get', id: jobId });
242+
assert(getResult.exitCode === 0);
243+
assert(getResult.output.includes(jobName));
244+
console.log(` output: ${getResult.output.split('\n')[0]}`);
245+
246+
// pause
247+
console.log(`\n [pause] id=${jobId}`);
248+
const pauseResult = await cronSession.tool('cron', { action: 'pause', id: jobId });
249+
assert(pauseResult.exitCode === 0);
250+
console.log(` output: ${pauseResult.output.trim()}`);
251+
252+
// resume
253+
console.log(`\n [resume] id=${jobId}`);
254+
const resumeResult = await cronSession.tool('cron', { action: 'resume', id: jobId });
255+
assert(resumeResult.exitCode === 0);
256+
console.log(` output: ${resumeResult.output.trim()}`);
257+
258+
// run
259+
console.log(`\n [run] id=${jobId}`);
260+
const runResult = await cronSession.tool('cron', { action: 'run', id: jobId });
261+
assert(runResult.exitCode === 0);
262+
console.log(` output: ${runResult.output.split('\n')[0]}`);
263+
264+
// history
265+
console.log(`\n [history] id=${jobId}`);
266+
const historyResult = await cronSession.tool('cron', { action: 'history', id: jobId, limit: 5 });
267+
assert(historyResult.exitCode === 0);
268+
console.log(` output: ${historyResult.output.split('\n')[0]}`);
269+
270+
// remove
271+
console.log(`\n [remove] id=${jobId}`);
272+
const removeResult = await cronSession.tool('cron', { action: 'remove', id: jobId });
273+
assert(removeResult.exitCode === 0);
274+
console.log(` output: ${removeResult.output.trim()}`);
275+
276+
// verify removal
277+
const listAfter = await cronSession.tool('cron', { action: 'list' });
278+
assert(!listAfter.output.includes(jobName));
279+
console.log(' verified: job no longer in list');
280+
281+
// -- 15. Unknown tool (error case) ----------------------------------------
282+
console.log('\n--- session.tool("nonexistent") ---');
283+
const unknownResult = await session.tool('nonexistent', {});
284+
console.log(` exit_code: ${unknownResult.exitCode}`);
285+
assert(unknownResult.exitCode === 1, 'unknown tool should fail');
286+
assert(unknownResult.output.includes('Unknown tool'));
287+
console.log(` output: ${unknownResult.output.trim()}`);
288+
289+
// -- 16. Convenience methods ----------------------------------------------
290+
console.log('\n--- Convenience methods ---');
291+
const globConv = await session.glob('*.rs');
292+
console.log(` session.glob('*.rs'): ${JSON.stringify(globConv)}`);
293+
294+
const grepConv = await session.grep('fn ');
295+
console.log(` session.grep('fn '): ${grepConv.split('\n').length} lines`);
296+
297+
// -- Done -----------------------------------------------------------------
298+
console.log('\n=== All checks passed ===');
299+
}
300+
301+
main().catch(err => {
302+
console.error('FAILED:', err);
303+
process.exit(1);
304+
});

0 commit comments

Comments
 (0)