@castari/sdk 0.1.4 → 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 +92 -104
- package/dist/agents.d.ts +76 -0
- package/dist/agents.d.ts.map +1 -0
- package/dist/agents.js +111 -0
- package/dist/agents.js.map +1 -0
- package/dist/auth.d.ts +27 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +33 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +51 -45
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +90 -230
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +47 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +108 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +52 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +73 -0
- package/dist/errors.js.map +1 -0
- package/dist/http.d.ts +32 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +117 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +157 -58
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/usage.d.ts +26 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +45 -0
- package/dist/usage.js.map +1 -0
- package/package.json +51 -34
- package/LICENSE +0 -21
- package/dist/const.d.ts +0 -6
- package/dist/const.js +0 -9
- package/dist/message-handler.d.ts +0 -8
- package/dist/message-handler.js +0 -96
- package/dist/server.d.ts +0 -6
- package/dist/server.js +0 -219
package/dist/message-handler.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { readdir, readFile, unlink, writeFile } from 'fs/promises';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
export async function handleMessage(ws, message, context) {
|
|
4
|
-
try {
|
|
5
|
-
const input = JSON.parse(message.toString());
|
|
6
|
-
const { messageQueue, getActiveStream, workspaceDirectory } = context;
|
|
7
|
-
if (input.type === 'user_message') {
|
|
8
|
-
messageQueue.push(input.data);
|
|
9
|
-
}
|
|
10
|
-
else if (input.type === 'interrupt') {
|
|
11
|
-
getActiveStream()?.interrupt();
|
|
12
|
-
}
|
|
13
|
-
else if (input.type === 'create_file') {
|
|
14
|
-
const targetPath = join(workspaceDirectory, input.path);
|
|
15
|
-
const encoding = input.encoding || 'utf-8';
|
|
16
|
-
const content = encoding === 'base64'
|
|
17
|
-
? Buffer.from(input.content, 'base64')
|
|
18
|
-
: input.content;
|
|
19
|
-
try {
|
|
20
|
-
await writeFile(targetPath, content);
|
|
21
|
-
ws.send(JSON.stringify({
|
|
22
|
-
type: 'file_result',
|
|
23
|
-
operation: 'create_file',
|
|
24
|
-
result: 'success',
|
|
25
|
-
}));
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
ws.send(JSON.stringify({
|
|
29
|
-
type: 'error',
|
|
30
|
-
error: `Failed to create file: ${err instanceof Error ? err.message : String(err)}`,
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
else if (input.type === 'read_file') {
|
|
35
|
-
const targetPath = join(workspaceDirectory, input.path);
|
|
36
|
-
const encoding = input.encoding || 'utf-8';
|
|
37
|
-
try {
|
|
38
|
-
const content = await readFile(targetPath, encoding === 'base64' ? 'base64' : 'utf-8');
|
|
39
|
-
ws.send(JSON.stringify({
|
|
40
|
-
type: 'file_result',
|
|
41
|
-
operation: 'read_file',
|
|
42
|
-
result: content,
|
|
43
|
-
encoding,
|
|
44
|
-
}));
|
|
45
|
-
}
|
|
46
|
-
catch (err) {
|
|
47
|
-
ws.send(JSON.stringify({
|
|
48
|
-
type: 'error',
|
|
49
|
-
error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}`,
|
|
50
|
-
}));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else if (input.type === 'delete_file') {
|
|
54
|
-
const targetPath = join(workspaceDirectory, input.path);
|
|
55
|
-
try {
|
|
56
|
-
await unlink(targetPath);
|
|
57
|
-
ws.send(JSON.stringify({
|
|
58
|
-
type: 'file_result',
|
|
59
|
-
operation: 'delete_file',
|
|
60
|
-
result: 'success',
|
|
61
|
-
}));
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
ws.send(JSON.stringify({
|
|
65
|
-
type: 'error',
|
|
66
|
-
error: `Failed to delete file: ${err instanceof Error ? err.message : String(err)}`,
|
|
67
|
-
}));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
else if (input.type === 'list_files') {
|
|
71
|
-
const targetPath = input.path
|
|
72
|
-
? join(workspaceDirectory, input.path)
|
|
73
|
-
: workspaceDirectory;
|
|
74
|
-
try {
|
|
75
|
-
const files = await readdir(targetPath);
|
|
76
|
-
ws.send(JSON.stringify({
|
|
77
|
-
type: 'file_result',
|
|
78
|
-
operation: 'list_files',
|
|
79
|
-
result: files,
|
|
80
|
-
}));
|
|
81
|
-
}
|
|
82
|
-
catch (err) {
|
|
83
|
-
ws.send(JSON.stringify({
|
|
84
|
-
type: 'error',
|
|
85
|
-
error: `Failed to list files: ${err instanceof Error ? err.message : String(err)}`,
|
|
86
|
-
}));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
ws.send(JSON.stringify({
|
|
92
|
-
type: 'error',
|
|
93
|
-
error: `Invalid message format: ${error instanceof Error ? error.message : String(error)}`,
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
}
|
package/dist/server.d.ts
DELETED
package/dist/server.js
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from 'crypto';
|
|
2
|
-
import { mkdir } from 'fs/promises';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
import { join } from 'path';
|
|
5
|
-
import { query, createSdkMcpServer, } from '@anthropic-ai/claude-agent-sdk';
|
|
6
|
-
import { CONNECTION_TOKEN_TTL_MS, SERVER_PORT, WORKSPACE_DIR_NAME, } from './const';
|
|
7
|
-
import { handleMessage } from './message-handler';
|
|
8
|
-
const workspaceDirectory = process.env.CASTARI_WORKSPACE || join(homedir(), WORKSPACE_DIR_NAME);
|
|
9
|
-
// Single WebSocket connection (only one allowed)
|
|
10
|
-
let activeConnection = null;
|
|
11
|
-
// Message queue
|
|
12
|
-
const messageQueue = [];
|
|
13
|
-
// Stream reference for interrupts
|
|
14
|
-
let activeStream = null;
|
|
15
|
-
// Stored query configuration
|
|
16
|
-
let queryConfig = {};
|
|
17
|
-
// Connection tokens
|
|
18
|
-
const connectionTokens = new Map();
|
|
19
|
-
async function ensureWorkspace() {
|
|
20
|
-
await mkdir(workspaceDirectory, { recursive: true });
|
|
21
|
-
}
|
|
22
|
-
function generateConnectionToken() {
|
|
23
|
-
const value = randomBytes(24).toString('hex');
|
|
24
|
-
const token = {
|
|
25
|
-
value,
|
|
26
|
-
createdAt: Date.now(),
|
|
27
|
-
used: false,
|
|
28
|
-
};
|
|
29
|
-
connectionTokens.set(value, token);
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
|
-
function cleanupTokens() {
|
|
33
|
-
const now = Date.now();
|
|
34
|
-
for (const [value, token] of connectionTokens.entries()) {
|
|
35
|
-
if (token.used || now - token.createdAt > CONNECTION_TOKEN_TTL_MS) {
|
|
36
|
-
connectionTokens.delete(value);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function validateAndUseToken(value) {
|
|
41
|
-
cleanupTokens();
|
|
42
|
-
if (!value)
|
|
43
|
-
return false;
|
|
44
|
-
const token = connectionTokens.get(value);
|
|
45
|
-
if (!token)
|
|
46
|
-
return false;
|
|
47
|
-
const isExpired = Date.now() - token.createdAt > CONNECTION_TOKEN_TTL_MS;
|
|
48
|
-
if (token.used || isExpired) {
|
|
49
|
-
connectionTokens.delete(value);
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
token.used = true;
|
|
53
|
-
connectionTokens.set(value, token);
|
|
54
|
-
return true;
|
|
55
|
-
}
|
|
56
|
-
// Create an async generator that yields messages from the queue
|
|
57
|
-
async function* generateMessages() {
|
|
58
|
-
while (true) {
|
|
59
|
-
while (messageQueue.length > 0) {
|
|
60
|
-
const message = messageQueue.shift();
|
|
61
|
-
if (message) {
|
|
62
|
-
yield message;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// Process messages from the SDK and send to WebSocket client
|
|
69
|
-
async function processMessages(initialOptions) {
|
|
70
|
-
try {
|
|
71
|
-
// Handle custom tools by creating an SDK MCP server
|
|
72
|
-
let mcpServers = initialOptions.mcpServers || {};
|
|
73
|
-
if (initialOptions.tools && initialOptions.tools.length > 0) {
|
|
74
|
-
const sdkServer = createSdkMcpServer({
|
|
75
|
-
name: 'castari-agent',
|
|
76
|
-
version: '1.0.0',
|
|
77
|
-
tools: initialOptions.tools,
|
|
78
|
-
});
|
|
79
|
-
mcpServers = {
|
|
80
|
-
...mcpServers,
|
|
81
|
-
'castari-agent': sdkServer,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
const options = {
|
|
85
|
-
settingSources: ['local'],
|
|
86
|
-
cwd: workspaceDirectory,
|
|
87
|
-
// Auto-approve tool usage (including file writes) inside the sandbox.
|
|
88
|
-
// Sandboxes are already isolated, so this keeps DX smooth without interactive prompts.
|
|
89
|
-
canUseTool: async (_toolName, input) => ({
|
|
90
|
-
behavior: 'allow',
|
|
91
|
-
updatedInput: input,
|
|
92
|
-
}),
|
|
93
|
-
stderr: data => {
|
|
94
|
-
if (activeConnection) {
|
|
95
|
-
const output = {
|
|
96
|
-
type: 'info',
|
|
97
|
-
data,
|
|
98
|
-
};
|
|
99
|
-
activeConnection.send(JSON.stringify(output));
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
...initialOptions, // Merge initial options (tools, systemPrompt, etc.)
|
|
103
|
-
mcpServers, // Override mcpServers with our injected one
|
|
104
|
-
...queryConfig, // Merge dynamic config from /config endpoint (overrides initial)
|
|
105
|
-
...(queryConfig.anthropicApiKey || process.env.ANTHROPIC_API_KEY
|
|
106
|
-
? {
|
|
107
|
-
env: {
|
|
108
|
-
PATH: process.env.PATH,
|
|
109
|
-
ANTHROPIC_API_KEY: queryConfig.anthropicApiKey || process.env.ANTHROPIC_API_KEY,
|
|
110
|
-
},
|
|
111
|
-
}
|
|
112
|
-
: {}),
|
|
113
|
-
};
|
|
114
|
-
console.info('Starting query with options', {
|
|
115
|
-
...options,
|
|
116
|
-
prompt: '[generator]', // avoid logging generator internals
|
|
117
|
-
});
|
|
118
|
-
if (options.resume) {
|
|
119
|
-
console.info(`📋 Resuming session: ${options.resume}`);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
console.info('📋 Starting new session');
|
|
123
|
-
}
|
|
124
|
-
activeStream = query({
|
|
125
|
-
prompt: generateMessages(),
|
|
126
|
-
options,
|
|
127
|
-
});
|
|
128
|
-
for await (const message of activeStream) {
|
|
129
|
-
if (activeConnection) {
|
|
130
|
-
const output = {
|
|
131
|
-
type: 'sdk_message',
|
|
132
|
-
data: message,
|
|
133
|
-
};
|
|
134
|
-
activeConnection.send(JSON.stringify(output));
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
console.error('Error processing messages:', error);
|
|
140
|
-
if (activeConnection) {
|
|
141
|
-
const output = {
|
|
142
|
-
type: 'error',
|
|
143
|
-
error: error instanceof Error ? error.message : 'Unknown error',
|
|
144
|
-
};
|
|
145
|
-
activeConnection.send(JSON.stringify(output));
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
export async function serve(options = {}) {
|
|
150
|
-
await ensureWorkspace();
|
|
151
|
-
// Create WebSocket server
|
|
152
|
-
const server = Bun.serve({
|
|
153
|
-
port: options.port || SERVER_PORT,
|
|
154
|
-
async fetch(req, server) {
|
|
155
|
-
const url = new URL(req.url);
|
|
156
|
-
// Configuration endpoint
|
|
157
|
-
if (url.pathname === '/config' && req.method === 'POST') {
|
|
158
|
-
try {
|
|
159
|
-
const config = (await req.json());
|
|
160
|
-
queryConfig = config;
|
|
161
|
-
const connectionToken = generateConnectionToken();
|
|
162
|
-
return Response.json({
|
|
163
|
-
success: true,
|
|
164
|
-
config: queryConfig,
|
|
165
|
-
connectionToken,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
catch {
|
|
169
|
-
return Response.json({ error: 'Invalid JSON' }, { status: 400 });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// Get current configuration
|
|
173
|
-
if (url.pathname === '/config' && req.method === 'GET') {
|
|
174
|
-
return Response.json({ config: queryConfig });
|
|
175
|
-
}
|
|
176
|
-
// WebSocket endpoint
|
|
177
|
-
if (url.pathname === '/ws') {
|
|
178
|
-
const token = url.searchParams.get('token');
|
|
179
|
-
if (!validateAndUseToken(token)) {
|
|
180
|
-
return new Response('Unauthorized', { status: 401 });
|
|
181
|
-
}
|
|
182
|
-
if (activeConnection) {
|
|
183
|
-
return new Response('Server already has an active connection', {
|
|
184
|
-
status: 409,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
if (server.upgrade(req))
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
return new Response('Not Found', { status: 404 });
|
|
191
|
-
},
|
|
192
|
-
websocket: {
|
|
193
|
-
open(ws) {
|
|
194
|
-
activeConnection = ws;
|
|
195
|
-
// Start processing messages when first connection is made
|
|
196
|
-
if (!activeStream) {
|
|
197
|
-
processMessages(options);
|
|
198
|
-
}
|
|
199
|
-
const output = { type: 'connected' };
|
|
200
|
-
ws.send(JSON.stringify(output));
|
|
201
|
-
},
|
|
202
|
-
async message(ws, message) {
|
|
203
|
-
await handleMessage(ws, message, {
|
|
204
|
-
messageQueue,
|
|
205
|
-
getActiveStream: () => activeStream,
|
|
206
|
-
workspaceDirectory,
|
|
207
|
-
});
|
|
208
|
-
},
|
|
209
|
-
close(ws) {
|
|
210
|
-
if (activeConnection === ws) {
|
|
211
|
-
activeConnection = null;
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
console.log(`🚀 Castari Server running on http://localhost:${server.port}`);
|
|
217
|
-
console.log(` Config endpoint: http://localhost:${server.port}/config`);
|
|
218
|
-
console.log(` WebSocket endpoint: ws://localhost:${server.port}/ws?token=<token>`);
|
|
219
|
-
}
|