@granular-software/sdk 0.1.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 ADDED
@@ -0,0 +1,476 @@
1
+ # @granular-software/sdk
2
+
3
+ The official TypeScript SDK for Granular.
4
+
5
+ **Build AI-powered domain models with secure, isolated execution.** Granular lets you define ontologies (classes, relationships), attach tools to them, and run AI-generated code that operates on typed domain objects — all in a sandboxed environment with per-user permissions.
6
+
7
+ ## Features
8
+
9
+ - đŸ—ī¸ **Domain Ontology** — Define classes, properties, and relationships as a typed graph
10
+ - 🔧 **Class-Based Tools** — Attach instance methods, static methods, and global tools with typed I/O
11
+ - 🤖 **Domain Synthesis** — Auto-generated TypeScript classes with `find()`, relationship accessors, and typed methods
12
+ - 🔒 **Secure Execution** — Run AI-generated code in isolated sandboxes
13
+ - đŸ‘Ĩ **User Permissions** — Control what each user can access
14
+ - 🔌 **Framework Adapters** — Helpers for OpenAI, LangChain, Anthropic, Mastra
15
+ - ⚡ **Real-time** — WebSocket-based streaming and events
16
+ - â†Šī¸ **Reverse RPC** — Sandbox code calls tools that execute on your server
17
+
18
+ ## Documentation
19
+
20
+ Full documentation: [docs.granular.dev](https://docs.granular.dev)
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ bun add @granular-software/sdk
26
+ # or
27
+ npm install @granular-software/sdk
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```typescript
33
+ import { Granular, type ManifestContent } from '@granular-software/sdk';
34
+
35
+ const granular = new Granular({ apiKey: process.env.GRANULAR_API_KEY });
36
+
37
+ // 1. Record user with permissions
38
+ const user = await granular.recordUser({
39
+ userId: 'user_123',
40
+ permissions: ['agent'],
41
+ });
42
+
43
+ // 2. Connect to sandbox
44
+ const env = await granular.connect({ sandbox: 'my-sandbox', user });
45
+
46
+ // 3. Define your domain ontology
47
+ const manifest: ManifestContent = {
48
+ schemaVersion: 2,
49
+ name: 'my-app',
50
+ volumes: [{
51
+ name: 'schema',
52
+ scope: 'sandbox',
53
+ imports: [{ alias: '@std', name: 'standard_modules', label: 'prod' }],
54
+ operations: [
55
+ // Define classes with typed properties
56
+ {
57
+ create: 'customer',
58
+ extends: '@std/class',
59
+ has: {
60
+ name: { type: 'string', description: 'Customer name' },
61
+ email: { type: 'string', description: 'Email address' },
62
+ tier: { type: 'string', description: 'Subscription tier' },
63
+ },
64
+ },
65
+ {
66
+ create: 'order',
67
+ extends: '@std/class',
68
+ has: {
69
+ total: { type: 'number', description: 'Order total' },
70
+ status: { type: 'string', description: 'Order status' },
71
+ },
72
+ },
73
+ // Define relationships
74
+ {
75
+ defineRelationship: {
76
+ left: 'customer', right: 'order',
77
+ leftSubmodel: 'orders', rightSubmodel: 'customer',
78
+ leftIsMany: true, rightIsMany: false,
79
+ },
80
+ },
81
+ ],
82
+ }],
83
+ };
84
+
85
+ await env.applyManifest(manifest);
86
+
87
+ // 4. Record object instances
88
+ await env.recordObject({
89
+ className: 'customer',
90
+ id: 'cust_42',
91
+ label: 'Acme Corp',
92
+ fields: { name: 'Acme Corp', email: 'billing@acme.com', tier: 'enterprise' },
93
+ });
94
+
95
+ // 5. Publish tools with typed input AND output schemas
96
+ await env.publishTools([
97
+ {
98
+ name: 'get_billing_summary',
99
+ description: 'Get billing summary for a customer',
100
+ className: 'customer', // Attached to Customer class
101
+ // static omitted → instance method (receives object ID automatically)
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ period: { type: 'string', description: 'Billing period (e.g. "2024-Q1")' },
106
+ },
107
+ },
108
+ outputSchema: {
109
+ type: 'object',
110
+ properties: {
111
+ total: { type: 'number', description: 'Total billed amount' },
112
+ invoices: { type: 'number', description: 'Number of invoices' },
113
+ period: { type: 'string', description: 'The billing period' },
114
+ },
115
+ required: ['total', 'invoices'],
116
+ },
117
+ handler: async (customerId: string, params: any) => {
118
+ // customerId comes from `this.id` in sandbox code
119
+ return { total: 4250.00, invoices: 3, period: params?.period || 'current' };
120
+ },
121
+ },
122
+ ]);
123
+
124
+ // 6. Submit a job — the sandbox gets fully typed OOP classes!
125
+ const job = await env.submitJob(`
126
+ import { Customer } from './sandbox-tools';
127
+
128
+ // Find returns a typed Customer instance with loaded field values
129
+ const acme = await Customer.find({ id: 'cust_42' });
130
+ console.log(acme.name); // "Acme Corp"
131
+ console.log(acme.email); // "billing@acme.com"
132
+
133
+ // Instance method — typed input AND output
134
+ const billing = await acme.get_billing_summary({ period: '2024-Q1' });
135
+ // billing.total, billing.invoices, billing.period are all typed
136
+
137
+ // Navigate relationships
138
+ const orders = await acme.get_orders();
139
+
140
+ return { customer: acme.name, billing, orderCount: orders.length };
141
+ `);
142
+
143
+ const result = await job.result;
144
+ ```
145
+
146
+ ## Core Flow
147
+
148
+ ```
149
+ recordUser() → connect() → applyManifest() → recordObject() → publishTools() → submitJob()
150
+ ```
151
+
152
+ 1. **`recordUser()`** — Register a user and their permission profiles
153
+ 2. **`connect()`** — Connect to a sandbox, returning an `Environment`
154
+ 3. **`applyManifest()`** — Define your domain ontology (classes, properties, relationships)
155
+ 4. **`recordObject()`** — Create/update instances of your classes with fields and relationships
156
+ 5. **`publishTools()`** — Publish tool schemas (with `inputSchema` + `outputSchema`) and register local handlers
157
+ 6. **`submitJob()`** — Execute code in the sandbox that uses the auto-generated typed classes
158
+
159
+ ## Defining the Domain Ontology
160
+
161
+ Use `applyManifest()` to declare classes, typed properties, and relationships:
162
+
163
+ ```typescript
164
+ const manifest: ManifestContent = {
165
+ schemaVersion: 2,
166
+ name: 'library-app',
167
+ volumes: [{
168
+ name: 'schema',
169
+ scope: 'sandbox',
170
+ imports: [{ alias: '@std', name: 'standard_modules', label: 'prod' }],
171
+ operations: [
172
+ // Classes with typed properties
173
+ {
174
+ create: 'author',
175
+ extends: '@std/class',
176
+ has: {
177
+ name: { type: 'string', description: 'Author full name' },
178
+ birth_year: { type: 'number', description: 'Year of birth' },
179
+ },
180
+ },
181
+ {
182
+ create: 'book',
183
+ extends: '@std/class',
184
+ has: {
185
+ title: { type: 'string', description: 'Book title' },
186
+ isbn: { type: 'string', description: 'ISBN number' },
187
+ published_year: { type: 'number', description: 'Year published' },
188
+ },
189
+ },
190
+
191
+ // Relationships (bidirectional, with cardinality)
192
+ {
193
+ defineRelationship: {
194
+ left: 'author', right: 'book',
195
+ leftSubmodel: 'books', rightSubmodel: 'author',
196
+ leftIsMany: true, rightIsMany: false,
197
+ // → author.books (one-to-many), book.author (many-to-one)
198
+ },
199
+ },
200
+ ],
201
+ }],
202
+ };
203
+
204
+ await env.applyManifest(manifest);
205
+ ```
206
+
207
+ ## Recording Object Instances
208
+
209
+ After defining the ontology, populate it with data:
210
+
211
+ ```typescript
212
+ const tolkien = await env.recordObject({
213
+ className: 'author',
214
+ id: 'tolkien', // Real-world ID (unique per class)
215
+ label: 'J.R.R. Tolkien',
216
+ fields: { name: 'J.R.R. Tolkien', birth_year: 1892 },
217
+ });
218
+ // tolkien.path → 'author_tolkien' (internal graph path, unique globally)
219
+ // tolkien.id → 'tolkien' (real-world ID)
220
+
221
+ const lotr = await env.recordObject({
222
+ className: 'book',
223
+ id: 'lotr',
224
+ label: 'The Lord of the Rings',
225
+ fields: { title: 'The Lord of the Rings', isbn: '978-0-618-64015-7', published_year: 1954 },
226
+ relationships: { author: 'tolkien' }, // Real-world ID — SDK resolves it automatically
227
+ });
228
+ ```
229
+
230
+ > **Cross-class ID uniqueness**: Two objects of different classes can share the same real-world ID (e.g., an `author` "tolkien" and a `publisher` "tolkien"). The SDK derives unique graph paths internally (`author_tolkien`, `publisher_tolkien`) so they never collide.
231
+
232
+ ## Tool Definitions
233
+
234
+ Tools can be **instance methods**, **static methods**, or **global functions**. Both `inputSchema` and `outputSchema` use JSON Schema:
235
+
236
+ ```typescript
237
+ await env.publishTools([
238
+ // Instance method: called as `tolkien.get_bio({ detailed: true })`
239
+ // Handler receives (objectId, params)
240
+ {
241
+ name: 'get_bio',
242
+ description: 'Get biography of an author',
243
+ className: 'author', // Attached to Author class
244
+ // static: false (default) // Instance method
245
+ inputSchema: {
246
+ type: 'object',
247
+ properties: {
248
+ detailed: { type: 'boolean', description: 'Include full details' },
249
+ },
250
+ },
251
+ outputSchema: {
252
+ type: 'object',
253
+ properties: {
254
+ bio: { type: 'string', description: 'The biography text' },
255
+ source: { type: 'string', description: 'Source of the bio' },
256
+ },
257
+ required: ['bio'],
258
+ },
259
+ handler: async (id: string, params: any) => {
260
+ return { bio: `Biography of ${id}`, source: 'database' };
261
+ },
262
+ },
263
+
264
+ // Static method: called as `Author.search({ query: 'tolkien' })`
265
+ // Handler receives (params) — no object ID
266
+ {
267
+ name: 'search',
268
+ description: 'Search for authors',
269
+ className: 'author',
270
+ static: true,
271
+ inputSchema: {
272
+ type: 'object',
273
+ properties: { query: { type: 'string' } },
274
+ required: ['query'],
275
+ },
276
+ outputSchema: {
277
+ type: 'object',
278
+ properties: { results: { type: 'array' } },
279
+ },
280
+ handler: async (params: any) => {
281
+ return { results: [`Found: ${params.query}`] };
282
+ },
283
+ },
284
+
285
+ // Global tool: called as `global_search({ query: 'rings' })`
286
+ // No className → standalone exported function
287
+ {
288
+ name: 'global_search',
289
+ description: 'Search across everything',
290
+ inputSchema: {
291
+ type: 'object',
292
+ properties: { query: { type: 'string' } },
293
+ required: ['query'],
294
+ },
295
+ outputSchema: {
296
+ type: 'object',
297
+ properties: { results: { type: 'array' } },
298
+ },
299
+ handler: async (params: any) => {
300
+ return { results: [`Result for: ${params.query}`] };
301
+ },
302
+ },
303
+ ]);
304
+ ```
305
+
306
+ ## Domain Synthesis (Auto-Generated Types)
307
+
308
+ After applying the manifest and publishing tools, the sandbox gets **auto-generated TypeScript classes** in `./sandbox-tools`:
309
+
310
+ ```typescript
311
+ // What the sandbox sees (auto-generated):
312
+ export declare class Author {
313
+ readonly id: string;
314
+ readonly name: string;
315
+ readonly birth_year: number;
316
+
317
+ constructor(id: string, fields?: Record<string, any>);
318
+
319
+ /** Find an Author by ID */
320
+ static find(query: { id: string }): Promise<Author | null>;
321
+
322
+ /** Get biography of an author */
323
+ get_bio(input?: { detailed?: boolean }): Promise<{ bio: string; source?: string }>;
324
+
325
+ /** Search for authors (static) */
326
+ static search(input: { query: string }): Promise<{ results?: any[] }>;
327
+
328
+ /** Navigate to books (one_to_many) */
329
+ get_books(): Promise<Book[]>;
330
+ }
331
+
332
+ export declare class Book {
333
+ readonly id: string;
334
+ readonly title: string;
335
+ readonly isbn: string;
336
+ readonly published_year: number;
337
+
338
+ static find(query: { id: string }): Promise<Book | null>;
339
+
340
+ /** Navigate to author (many_to_one) */
341
+ get_author(): Promise<Author | null>;
342
+ }
343
+
344
+ /** Search across everything */
345
+ export declare function global_search(input: { query: string }): Promise<{ results?: any[] }>;
346
+ ```
347
+
348
+ The LLM or user writes code against these typed classes:
349
+
350
+ ```typescript
351
+ import { Author, Book, global_search } from './sandbox-tools';
352
+
353
+ const tolkien = await Author.find({ id: 'tolkien' });
354
+ console.log(tolkien.name); // "J.R.R. Tolkien"
355
+
356
+ const bio = await tolkien.get_bio({ detailed: true });
357
+ console.log(bio.bio); // typed as string
358
+
359
+ const books = await tolkien.get_books();
360
+ for (const book of books) {
361
+ console.log(book.title, book.isbn); // typed properties
362
+ }
363
+
364
+ const results = await Author.search({ query: 'tolkien' });
365
+ const globalResults = await global_search({ query: 'rings' });
366
+ ```
367
+
368
+ ## GraphQL API
369
+
370
+ Every environment has a built-in GraphQL API for direct graph queries:
371
+
372
+ ```typescript
373
+ // Authenticated automatically with your API key
374
+ const result = await env.graphql(
375
+ `query { model(path: "author") { path label submodels { path label } } }`
376
+ );
377
+
378
+ // The endpoint is also available as a URL
379
+ console.log(env.apiEndpoint);
380
+ ```
381
+
382
+ ## Framework Adapters
383
+
384
+ Adapters provide two modes of integration:
385
+
386
+ ### Option A: Codegen Tool (Recommended)
387
+
388
+ Pass a **single tool** to the LLM that executes code in the sandbox. The LLM receives the typed class documentation and writes code against it:
389
+
390
+ ```typescript
391
+ import { createCodegenTool, executeCodegenToolCall } from '@granular-software/sdk/openai';
392
+
393
+ // Get auto-generated TypeScript class declarations
394
+ const docs = await environment.getDomainDocumentation();
395
+
396
+ // Create a single "codegen" tool for the LLM
397
+ const codegenTool = createCodegenTool(docs);
398
+
399
+ // Pass to OpenAI — the LLM writes typed code using Author, Book, etc.
400
+ const response = await openai.chat.completions.create({
401
+ model: 'gpt-4',
402
+ messages: [...],
403
+ tools: [codegenTool],
404
+ });
405
+
406
+ // Execute the code the LLM generates
407
+ const result = await executeCodegenToolCall(toolCall, environment);
408
+ ```
409
+
410
+ ### Option B: Schema Conversion (Legacy)
411
+
412
+ Convert tool schemas for compatibility:
413
+
414
+ ```typescript
415
+ import { toOpenAITools } from '@granular-software/sdk/openai';
416
+ import { toAnthropicTools } from '@granular-software/sdk/anthropic';
417
+ import { fromLangChainTools } from '@granular-software/sdk/langchain';
418
+
419
+ const openaiTools = toOpenAITools(myGranularTools);
420
+ ```
421
+
422
+ ## Examples
423
+
424
+ See [examples/](./examples/) for runnable code:
425
+
426
+ - **[basic.ts](./examples/basic.ts)** — Ontology + class-based tools + domain synthesis + job execution
427
+ - **[management.ts](./examples/management.ts)** — Sandbox, permission profiles, and user management
428
+ - **[adapter-openai.ts](./examples/adapter-openai.ts)** — OpenAI integration with codegen tool
429
+ - **[adapter-anthropic.ts](./examples/adapter-anthropic.ts)** — Anthropic/Claude integration
430
+ - **[adapter-langchain.ts](./examples/adapter-langchain.ts)** — LangChain integration
431
+ - **[interactive.ts](./examples/interactive.ts)** — Human-in-the-loop prompts
432
+
433
+ ## API Reference
434
+
435
+ ### `granular.recordUser(options)`
436
+ Registers a user identity and their assigned permission profiles.
437
+
438
+ ### `granular.connect(options)`
439
+ Connects to a sandbox session for the specified user.
440
+
441
+ ### `environment.applyManifest(manifest)`
442
+ Defines domain ontology: classes (with typed properties), and relationships (with cardinality).
443
+
444
+ ### `environment.recordObject(options)`
445
+ Creates or updates a class instance with fields and relationships. Returns `{ path, id, created }`.
446
+
447
+ ### `environment.getRelationships(modelPath)`
448
+ Returns relationship definitions for a given class.
449
+
450
+ ### `environment.listRelated(modelPath, submodelPath)`
451
+ Lists related instances through a relationship.
452
+
453
+ ### `environment.publishTools(tools)`
454
+ Publishes tool schemas (with `inputSchema` + `outputSchema`) and registers handlers locally. Tools can be instance methods (`className` set, `static` omitted), static methods (`static: true`), or global functions (no `className`).
455
+
456
+ ### `environment.submitJob(code)`
457
+ Submits code to be executed in the sandbox. The code imports typed classes from `./sandbox-tools`.
458
+
459
+ ### `environment.getDomainDocumentation()`
460
+ Get auto-generated TypeScript class declarations. Pass this to LLMs to help them write correct code.
461
+
462
+ ### `environment.graphql(query, variables?)`
463
+ Execute a GraphQL query against the environment's graph. Authenticated automatically.
464
+
465
+ ### `environment.on(event, handler)`
466
+ Listen for events: `'tool:invoke'`, `'tool:result'`, `'job:status'`, `'stdout'`, etc.
467
+
468
+ ### `Environment.toGraphPath(className, id)`
469
+ Convert a class name + real-world ID to a unique graph path (`{className}_{id}`).
470
+
471
+ ### `Environment.extractIdFromGraphPath(graphPath, className)`
472
+ Extract the real-world ID from a graph path by stripping the class prefix.
473
+
474
+ ## License
475
+
476
+ MIT
@@ -0,0 +1,64 @@
1
+ import { T as ToolWithHandler } from '../types-D46q5WTh.mjs';
2
+
3
+ /**
4
+ * Anthropic tool_use block from message response
5
+ */
6
+ interface AnthropicToolUseBlock {
7
+ type: 'tool_use';
8
+ id: string;
9
+ name: string;
10
+ input: unknown;
11
+ }
12
+ /**
13
+ * Anthropic tool definition
14
+ */
15
+ interface AnthropicToolDefinition {
16
+ name: string;
17
+ description?: string;
18
+ input_schema: Record<string, unknown>;
19
+ }
20
+ /**
21
+ * Helper to convert Granular tools to Anthropic tool definitions
22
+ *
23
+ * @param tools - Array of Granular tools
24
+ * @returns Array of Anthropic tool definitions
25
+ */
26
+ declare function toAnthropicTools(tools: ToolWithHandler[]): AnthropicToolDefinition[];
27
+ /**
28
+ * Helper to convert Anthropic tool calls to job code for Granular
29
+ *
30
+ * @param contentBlocks - Array of content blocks from Anthropic response
31
+ * @param customImports - Optional imports to add to top of job
32
+ * @returns generated job code, or null if no tool uses found
33
+ */
34
+ declare function toolUsesToJobCode(contentBlocks: Array<{
35
+ type: string;
36
+ id?: string;
37
+ name?: string;
38
+ input?: unknown;
39
+ }>, customImports?: string): string | null;
40
+ /**
41
+ * Create a single "codegen tool" for LLM consumption.
42
+ *
43
+ * Instead of passing N individual tools to the LLM, this returns a single
44
+ * tool that accepts TypeScript code. The LLM writes code that uses the
45
+ * available tools, and the code is executed in the sandbox.
46
+ *
47
+ * @param domainDocumentation - Documentation of available tools (from environment.getDomainDocumentation())
48
+ * @returns A single Anthropic tool definition
49
+ */
50
+ declare function createCodegenTool(domainDocumentation: string): AnthropicToolDefinition;
51
+ /**
52
+ * Helper to execute a codegen tool use block.
53
+ *
54
+ * @param toolUse - The tool_use block from the Anthropic response
55
+ * @param environment - The connected Granular environment
56
+ * @returns The job result
57
+ */
58
+ declare function executeCodegenToolUse(toolUse: AnthropicToolUseBlock, environment: {
59
+ submitJob: (code: string) => Promise<{
60
+ result: Promise<unknown>;
61
+ }>;
62
+ }): Promise<unknown>;
63
+
64
+ export { type AnthropicToolDefinition, type AnthropicToolUseBlock, createCodegenTool, executeCodegenToolUse, toAnthropicTools, toolUsesToJobCode };
@@ -0,0 +1,64 @@
1
+ import { T as ToolWithHandler } from '../types-D46q5WTh.js';
2
+
3
+ /**
4
+ * Anthropic tool_use block from message response
5
+ */
6
+ interface AnthropicToolUseBlock {
7
+ type: 'tool_use';
8
+ id: string;
9
+ name: string;
10
+ input: unknown;
11
+ }
12
+ /**
13
+ * Anthropic tool definition
14
+ */
15
+ interface AnthropicToolDefinition {
16
+ name: string;
17
+ description?: string;
18
+ input_schema: Record<string, unknown>;
19
+ }
20
+ /**
21
+ * Helper to convert Granular tools to Anthropic tool definitions
22
+ *
23
+ * @param tools - Array of Granular tools
24
+ * @returns Array of Anthropic tool definitions
25
+ */
26
+ declare function toAnthropicTools(tools: ToolWithHandler[]): AnthropicToolDefinition[];
27
+ /**
28
+ * Helper to convert Anthropic tool calls to job code for Granular
29
+ *
30
+ * @param contentBlocks - Array of content blocks from Anthropic response
31
+ * @param customImports - Optional imports to add to top of job
32
+ * @returns generated job code, or null if no tool uses found
33
+ */
34
+ declare function toolUsesToJobCode(contentBlocks: Array<{
35
+ type: string;
36
+ id?: string;
37
+ name?: string;
38
+ input?: unknown;
39
+ }>, customImports?: string): string | null;
40
+ /**
41
+ * Create a single "codegen tool" for LLM consumption.
42
+ *
43
+ * Instead of passing N individual tools to the LLM, this returns a single
44
+ * tool that accepts TypeScript code. The LLM writes code that uses the
45
+ * available tools, and the code is executed in the sandbox.
46
+ *
47
+ * @param domainDocumentation - Documentation of available tools (from environment.getDomainDocumentation())
48
+ * @returns A single Anthropic tool definition
49
+ */
50
+ declare function createCodegenTool(domainDocumentation: string): AnthropicToolDefinition;
51
+ /**
52
+ * Helper to execute a codegen tool use block.
53
+ *
54
+ * @param toolUse - The tool_use block from the Anthropic response
55
+ * @param environment - The connected Granular environment
56
+ * @returns The job result
57
+ */
58
+ declare function executeCodegenToolUse(toolUse: AnthropicToolUseBlock, environment: {
59
+ submitJob: (code: string) => Promise<{
60
+ result: Promise<unknown>;
61
+ }>;
62
+ }): Promise<unknown>;
63
+
64
+ export { type AnthropicToolDefinition, type AnthropicToolUseBlock, createCodegenTool, executeCodegenToolUse, toAnthropicTools, toolUsesToJobCode };
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ // src/adapters/anthropic.ts
4
+ function toAnthropicTools(tools) {
5
+ return tools.map((tool) => ({
6
+ name: tool.name,
7
+ description: tool.description,
8
+ input_schema: tool.inputSchema
9
+ }));
10
+ }
11
+ function toolUsesToJobCode(contentBlocks, customImports = "") {
12
+ const toolUses = contentBlocks.filter((b) => b.type === "tool_use");
13
+ if (toolUses.length === 0) {
14
+ return null;
15
+ }
16
+ let code = `
17
+ import { tools } from './sandbox-tools';
18
+ ${customImports}
19
+ const results = [];
20
+ `;
21
+ toolUses.forEach((use) => {
22
+ code += `
23
+ try {
24
+ const result_${use.id.replace(/-/g, "_")} = await tools.${use.name}(${JSON.stringify(use.input)});
25
+ results.push({
26
+ type: 'tool_result',
27
+ tool_use_id: "${use.id}",
28
+ content: result_${use.id.replace(/-/g, "_")}
29
+ });
30
+ } catch (error) {
31
+ results.push({
32
+ type: 'tool_result',
33
+ tool_use_id: "${use.id}",
34
+ content: String(error),
35
+ is_error: true
36
+ });
37
+ }
38
+ `;
39
+ });
40
+ code += `return results;`;
41
+ return code;
42
+ }
43
+ function createCodegenTool(domainDocumentation) {
44
+ return {
45
+ name: "execute_sandbox_code",
46
+ description: `Execute TypeScript code in a secure sandbox environment. The code can use the following tools by importing from "./sandbox-tools":
47
+
48
+ ${domainDocumentation}
49
+
50
+ Write complete, executable TypeScript code. Use async/await for tool calls. Return results at the end.`,
51
+ input_schema: {
52
+ type: "object",
53
+ properties: {
54
+ code: {
55
+ type: "string",
56
+ description: 'TypeScript code to execute. Must import tools from "./sandbox-tools" and use await for all tool calls.'
57
+ }
58
+ },
59
+ required: ["code"]
60
+ }
61
+ };
62
+ }
63
+ async function executeCodegenToolUse(toolUse, environment) {
64
+ if (toolUse.name !== "execute_sandbox_code") {
65
+ throw new Error(`Unknown tool: ${toolUse.name}`);
66
+ }
67
+ const input = toolUse.input;
68
+ const job = await environment.submitJob(input.code);
69
+ return job.result;
70
+ }
71
+
72
+ exports.createCodegenTool = createCodegenTool;
73
+ exports.executeCodegenToolUse = executeCodegenToolUse;
74
+ exports.toAnthropicTools = toAnthropicTools;
75
+ exports.toolUsesToJobCode = toolUsesToJobCode;
76
+ //# sourceMappingURL=anthropic.js.map
77
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/anthropic.ts"],"names":[],"mappings":";;;AA2BO,SAAS,iBAAiB,KAAA,EAAqD;AAClF,EAAA,OAAO,KAAA,CAAM,IAAI,CAAA,IAAA,MAAS;AAAA,IACtB,MAAM,IAAA,CAAK,IAAA;AAAA,IACX,aAAa,IAAA,CAAK,WAAA;AAAA,IAClB,cAAc,IAAA,CAAK;AAAA,GACvB,CAAE,CAAA;AACN;AASO,SAAS,iBAAA,CAAkB,aAAA,EAAqF,aAAA,GAAgB,EAAA,EAAmB;AACtJ,EAAA,MAAM,WAAW,aAAA,CAAc,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,CAAE,SAAS,UAAU,CAAA;AAEhE,EAAA,IAAI,QAAA,CAAS,WAAW,CAAA,EAAG;AACvB,IAAA,OAAO,IAAA;AAAA,EACX;AAEA,EAAA,IAAI,IAAA,GAAO;AAAA;AAAA,QAAA,EAEL,aAAa;AAAA;AAAA,IAAA,CAAA;AAInB,EAAA,QAAA,CAAS,OAAA,CAAQ,CAAC,GAAA,KAAQ;AACtB,IAAA,IAAA,IAAQ;AAAA;AAAA,yBAAA,EAEW,GAAA,CAAI,EAAA,CAAG,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAC,CAAA,eAAA,EAAkB,GAAA,CAAI,IAAI,CAAA,CAAA,EAAI,IAAA,CAAK,SAAA,CAAU,GAAA,CAAI,KAAK,CAAC,CAAA;AAAA;AAAA;AAAA,8BAAA,EAG3E,IAAI,EAAE,CAAA;AAAA,gCAAA,EACJ,GAAA,CAAI,EAAA,CAAG,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAC,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAAA,EAK3B,IAAI,EAAE,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAAA,CAAA;AAAA,EAMlC,CAAC,CAAA;AAED,EAAA,IAAA,IAAQ,CAAA,eAAA,CAAA;AACR,EAAA,OAAO,IAAA;AACX;AAYO,SAAS,kBAAkB,mBAAA,EAAsD;AACpF,EAAA,OAAO;AAAA,IACH,IAAA,EAAM,sBAAA;AAAA,IACN,WAAA,EAAa,CAAA;;AAAA,EAEnB,mBAAmB;;AAAA,sGAAA,CAAA;AAAA,IAGb,YAAA,EAAc;AAAA,MACV,IAAA,EAAM,QAAA;AAAA,MACN,UAAA,EAAY;AAAA,QACR,IAAA,EAAM;AAAA,UACF,IAAA,EAAM,QAAA;AAAA,UACN,WAAA,EAAa;AAAA;AACjB,OACJ;AAAA,MACA,QAAA,EAAU,CAAC,MAAM;AAAA;AACrB,GACJ;AACJ;AASA,eAAsB,qBAAA,CAClB,SACA,WAAA,EACgB;AAChB,EAAA,IAAI,OAAA,CAAQ,SAAS,sBAAA,EAAwB;AACzC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,cAAA,EAAiB,OAAA,CAAQ,IAAI,CAAA,CAAE,CAAA;AAAA,EACnD;AAEA,EAAA,MAAM,QAAQ,OAAA,CAAQ,KAAA;AACtB,EAAA,MAAM,GAAA,GAAM,MAAM,WAAA,CAAY,SAAA,CAAU,MAAM,IAAI,CAAA;AAClD,EAAA,OAAO,GAAA,CAAI,MAAA;AACf","file":"anthropic.js","sourcesContent":["import type { ToolWithHandler } from '../types';\n\n/**\n * Anthropic tool_use block from message response\n */\nexport interface AnthropicToolUseBlock {\n type: 'tool_use';\n id: string;\n name: string;\n input: unknown;\n}\n\n/**\n * Anthropic tool definition\n */\nexport interface AnthropicToolDefinition {\n name: string;\n description?: string;\n input_schema: Record<string, unknown>;\n}\n\n/**\n * Helper to convert Granular tools to Anthropic tool definitions\n * \n * @param tools - Array of Granular tools\n * @returns Array of Anthropic tool definitions\n */\nexport function toAnthropicTools(tools: ToolWithHandler[]): AnthropicToolDefinition[] {\n return tools.map(tool => ({\n name: tool.name,\n description: tool.description,\n input_schema: tool.inputSchema,\n }));\n}\n\n/**\n * Helper to convert Anthropic tool calls to job code for Granular\n * \n * @param contentBlocks - Array of content blocks from Anthropic response\n * @param customImports - Optional imports to add to top of job\n * @returns generated job code, or null if no tool uses found\n */\nexport function toolUsesToJobCode(contentBlocks: Array<{ type: string; id?: string; name?: string; input?: unknown }>, customImports = ''): string | null {\n const toolUses = contentBlocks.filter(b => b.type === 'tool_use') as AnthropicToolUseBlock[];\n\n if (toolUses.length === 0) {\n return null;\n }\n\n let code = `\n import { tools } from './sandbox-tools';\n ${customImports}\n const results = [];\n `;\n\n toolUses.forEach((use) => {\n code += `\n try {\n const result_${use.id.replace(/-/g, '_')} = await tools.${use.name}(${JSON.stringify(use.input)});\n results.push({ \n type: 'tool_result',\n tool_use_id: \"${use.id}\", \n content: result_${use.id.replace(/-/g, '_')} \n });\n } catch (error) {\n results.push({ \n type: 'tool_result',\n tool_use_id: \"${use.id}\", \n content: String(error),\n is_error: true\n });\n }\n `;\n });\n\n code += `return results;`;\n return code;\n}\n\n/**\n * Create a single \"codegen tool\" for LLM consumption.\n * \n * Instead of passing N individual tools to the LLM, this returns a single\n * tool that accepts TypeScript code. The LLM writes code that uses the\n * available tools, and the code is executed in the sandbox.\n * \n * @param domainDocumentation - Documentation of available tools (from environment.getDomainDocumentation())\n * @returns A single Anthropic tool definition\n */\nexport function createCodegenTool(domainDocumentation: string): AnthropicToolDefinition {\n return {\n name: 'execute_sandbox_code',\n description: `Execute TypeScript code in a secure sandbox environment. The code can use the following tools by importing from \"./sandbox-tools\":\n\n${domainDocumentation}\n\nWrite complete, executable TypeScript code. Use async/await for tool calls. Return results at the end.`,\n input_schema: {\n type: 'object',\n properties: {\n code: {\n type: 'string',\n description: 'TypeScript code to execute. Must import tools from \"./sandbox-tools\" and use await for all tool calls.',\n },\n },\n required: ['code'],\n },\n };\n}\n\n/**\n * Helper to execute a codegen tool use block.\n * \n * @param toolUse - The tool_use block from the Anthropic response\n * @param environment - The connected Granular environment\n * @returns The job result\n */\nexport async function executeCodegenToolUse(\n toolUse: AnthropicToolUseBlock,\n environment: { submitJob: (code: string) => Promise<{ result: Promise<unknown> }> }\n): Promise<unknown> {\n if (toolUse.name !== 'execute_sandbox_code') {\n throw new Error(`Unknown tool: ${toolUse.name}`);\n }\n\n const input = toolUse.input as { code: string };\n const job = await environment.submitJob(input.code);\n return job.result;\n}\n"]}