@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.
@@ -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
@@ -1,6 +0,0 @@
1
- import { tool, type Options } from '@anthropic-ai/claude-agent-sdk';
2
- export type CastariServerOptions = Partial<Options> & {
3
- port?: number;
4
- tools?: ReturnType<typeof tool>[];
5
- };
6
- export declare function serve(options?: CastariServerOptions): Promise<void>;
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
- }