@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 +476 -0
- package/dist/adapters/anthropic.d.mts +64 -0
- package/dist/adapters/anthropic.d.ts +64 -0
- package/dist/adapters/anthropic.js +77 -0
- package/dist/adapters/anthropic.js.map +1 -0
- package/dist/adapters/anthropic.mjs +72 -0
- package/dist/adapters/anthropic.mjs.map +1 -0
- package/dist/adapters/langchain.d.mts +31 -0
- package/dist/adapters/langchain.d.ts +31 -0
- package/dist/adapters/langchain.js +49 -0
- package/dist/adapters/langchain.js.map +1 -0
- package/dist/adapters/langchain.mjs +46 -0
- package/dist/adapters/langchain.mjs.map +1 -0
- package/dist/adapters/mastra.d.mts +23 -0
- package/dist/adapters/mastra.d.ts +23 -0
- package/dist/adapters/mastra.js +26 -0
- package/dist/adapters/mastra.js.map +1 -0
- package/dist/adapters/mastra.mjs +24 -0
- package/dist/adapters/mastra.mjs.map +1 -0
- package/dist/adapters/openai.d.mts +76 -0
- package/dist/adapters/openai.d.ts +76 -0
- package/dist/adapters/openai.js +81 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/adapters/openai.mjs +76 -0
- package/dist/adapters/openai.mjs.map +1 -0
- package/dist/index.d.mts +616 -0
- package/dist/index.d.ts +616 -0
- package/dist/index.js +1801 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1773 -0
- package/dist/index.mjs.map +1 -0
- package/dist/types-D46q5WTh.d.mts +595 -0
- package/dist/types-D46q5WTh.d.ts +595 -0
- package/package.json +105 -0
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"]}
|