@olane/o-context 0.8.9
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/LICENSE +34 -0
- package/README.md +614 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/o-context.browser.d.ts +11 -0
- package/dist/src/o-context.browser.d.ts.map +1 -0
- package/dist/src/o-context.browser.js +33 -0
- package/dist/src/o-context.d.ts +15 -0
- package/dist/src/o-context.d.ts.map +1 -0
- package/dist/src/o-context.js +1 -0
- package/dist/src/o-context.node.d.ts +10 -0
- package/dist/src/o-context.node.d.ts.map +1 -0
- package/dist/src/o-context.node.js +18 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Dual License: MIT OR Apache-2.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Olane Inc.
|
|
4
|
+
|
|
5
|
+
Olane OS is dual-licensed under your choice of either:
|
|
6
|
+
|
|
7
|
+
- **MIT License** (LICENSE-MIT or https://opensource.org/licenses/MIT)
|
|
8
|
+
- **Apache License, Version 2.0** (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
|
|
9
|
+
|
|
10
|
+
## Why Dual Licensing?
|
|
11
|
+
|
|
12
|
+
Dual licensing provides developers with flexibility when integrating Olane OS into their projects:
|
|
13
|
+
|
|
14
|
+
- **Choose MIT**: If you prefer a simple, permissive license without patent provisions
|
|
15
|
+
- **Choose Apache 2.0**: If you desire explicit patent protection and other features of the Apache license
|
|
16
|
+
|
|
17
|
+
You may choose either license to govern your use of this software.
|
|
18
|
+
|
|
19
|
+
## License Terms
|
|
20
|
+
|
|
21
|
+
### MIT License
|
|
22
|
+
The MIT license is very permissive and allows users to do almost anything with the software, including using, copying, modifying, merging, publishing, distributing, and selling it. The primary requirement is that the original copyright and license notice must be included in all copies of the software.
|
|
23
|
+
|
|
24
|
+
See LICENSE-MIT for the full license text.
|
|
25
|
+
|
|
26
|
+
### Apache License 2.0
|
|
27
|
+
The Apache License 2.0 is also a permissive free software license. It grants users the right to use, modify, and distribute the software. It also includes provisions regarding patent grants, which are not present in the MIT license.
|
|
28
|
+
|
|
29
|
+
See LICENSE-APACHE for the full license text.
|
|
30
|
+
|
|
31
|
+
## Contributing
|
|
32
|
+
|
|
33
|
+
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you shall be dual-licensed as above, without any additional terms or conditions.
|
|
34
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
# @olane/o-core
|
|
2
|
+
|
|
3
|
+
The kernel layer of Olane OS - an agentic operating system where AI agents are the users, and you build tool nodes as applications.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@olane/o-core)
|
|
6
|
+
[](https://opensource.org/licenses/ISC)
|
|
7
|
+
|
|
8
|
+
## What is o-core?
|
|
9
|
+
|
|
10
|
+
**o-core** is the **kernel layer** of Olane OS - the foundational runtime for building tool nodes that AI agents use. Think of it as the Linux kernel: it defines how processes work (lifecycle, IPC, addressing, routing) but doesn't implement specific transport layers.
|
|
11
|
+
|
|
12
|
+
### The Three-Layer Model
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
AI Agents (LLMs|Humans) → use → Tool Nodes (you build) → run on → Olane OS (o-core)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- **Agents (Users)**: GPT-4, Claude, Humans, etc. - the intelligent users
|
|
19
|
+
- **Tool Nodes (Applications)**: Domain-specific capabilities you build
|
|
20
|
+
- **o-core (OS Kernel)**: The runtime infrastructure that makes it all work
|
|
21
|
+
|
|
22
|
+
This is **NOT** a network framework or API library - it's the abstract operating system layer that makes inter-process communication (IPC), resource addressing, and hierarchical organization possible.
|
|
23
|
+
|
|
24
|
+
## Key Features
|
|
25
|
+
|
|
26
|
+
- 🏗️ **Tool Node Runtime**: Base infrastructure for creating tool node processes that agents use
|
|
27
|
+
- 📍 **Hierarchical Addressing**: `o://` protocol for filesystem-like tool node addressing
|
|
28
|
+
- 🔀 **Intelligent Routing**: Automatic request routing through tool node hierarchies
|
|
29
|
+
- 🔌 **Transport-Agnostic**: Abstract layer - works with any communication layer (libp2p, HTTP, custom)
|
|
30
|
+
- 🌳 **Hierarchy Management**: Built-in parent-child relationships between tool nodes
|
|
31
|
+
- 🔄 **Lifecycle Management**: Complete process state management and graceful shutdown
|
|
32
|
+
- 📊 **Observable**: Built-in metrics, logging, and request tracking
|
|
33
|
+
- 🛡️ **Fault-Tolerant**: Error handling, graceful degradation, and automatic cleanup
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm install @olane/o-core @olane/o-protocol
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### Creating Your First Tool Node
|
|
44
|
+
|
|
45
|
+
> **Note**: `oCore` is an abstract base class. For production use, extend higher-level packages like `oNodeTool` (from `@olane/o-node`), `oLaneTool` (from `@olane/o-lane`), or `McpTool` (from `@olane/o-mcp`). The example below demonstrates the core concepts.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { oCore, oAddress, NodeType, NodeState } from '@olane/o-core';
|
|
49
|
+
import { oRequest, oResponse } from '@olane/o-core';
|
|
50
|
+
|
|
51
|
+
// Extend oCore to create your tool node
|
|
52
|
+
class MyToolNode extends oCore {
|
|
53
|
+
constructor(config: { address: oAddress }) {
|
|
54
|
+
super({
|
|
55
|
+
address: config.address,
|
|
56
|
+
type: NodeType.AGENT, // Type remains AGENT for now (legacy naming)
|
|
57
|
+
description: 'My first tool node',
|
|
58
|
+
methods: {
|
|
59
|
+
greet: {
|
|
60
|
+
name: 'greet',
|
|
61
|
+
description: 'Greets the user',
|
|
62
|
+
parameters: {
|
|
63
|
+
name: { type: 'string', required: true }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Implement required abstract methods
|
|
71
|
+
async execute(request: oRequest): Promise<any> {
|
|
72
|
+
const { method, params } = request;
|
|
73
|
+
|
|
74
|
+
if (method === 'greet') {
|
|
75
|
+
return { message: `Hello, ${params.name}!` };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
throw new Error(`Unknown method: ${method}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
configureTransports(): any[] {
|
|
82
|
+
// Configure your transport layer (libp2p, HTTP, etc.)
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async connect(nextHop: oAddress, target: oAddress) {
|
|
87
|
+
// Implement connection logic
|
|
88
|
+
return this.connectionManager.connect({ nextHop, target });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
initializeRouter(): void {
|
|
92
|
+
// Initialize routing logic
|
|
93
|
+
this.router = new MyRouter();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async register(): Promise<void> {
|
|
97
|
+
// Register with parent or leader node
|
|
98
|
+
console.log('Tool node registered');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async unregister(): Promise<void> {
|
|
102
|
+
// Cleanup registration
|
|
103
|
+
console.log('Tool node unregistered');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Create and start your tool node
|
|
108
|
+
// IMPORTANT: Use simple (non-nested) addresses in constructors.
|
|
109
|
+
// Nested addresses like 'o://company/customer-service' are created
|
|
110
|
+
// automatically at runtime during parent-child registration.
|
|
111
|
+
const toolNode = new MyToolNode({
|
|
112
|
+
address: new oAddress('o://customer-service')
|
|
113
|
+
});
|
|
114
|
+
await toolNode.start();
|
|
115
|
+
|
|
116
|
+
// AI agents can now use this tool node via its o:// address
|
|
117
|
+
const response = await toolNode.use(
|
|
118
|
+
new oAddress('o://customer-service'),
|
|
119
|
+
{
|
|
120
|
+
method: 'greet',
|
|
121
|
+
params: { name: 'Alice' }
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Response follows the standard wrapping structure:
|
|
126
|
+
// response.result.success - boolean indicating success/failure
|
|
127
|
+
// response.result.data - the returned data on success
|
|
128
|
+
// response.result.error - error details on failure
|
|
129
|
+
if (response.result.success) {
|
|
130
|
+
console.log(response.result.data); // { message: "Hello, Alice!" }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Stop the tool node gracefully
|
|
134
|
+
await toolNode.stop();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Tool Node Communication (IPC)
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// Tool Node A can communicate with Tool Node B using o:// addresses
|
|
141
|
+
// Use simple addresses in constructors; nested addresses are created at runtime
|
|
142
|
+
const salesTool = new MyToolNode({ address: new oAddress('o://sales') });
|
|
143
|
+
const analyticsTool = new MyToolNode({ address: new oAddress('o://analytics') });
|
|
144
|
+
|
|
145
|
+
await salesTool.start();
|
|
146
|
+
await analyticsTool.start();
|
|
147
|
+
|
|
148
|
+
// Sales tool calls analytics tool (inter-process communication)
|
|
149
|
+
const result = await salesTool.use(
|
|
150
|
+
new oAddress('o://analytics'),
|
|
151
|
+
{
|
|
152
|
+
method: 'analyze',
|
|
153
|
+
params: { data: salesData }
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Hierarchical Organization
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
// Create a parent-child hierarchy of tool nodes
|
|
162
|
+
// Use simple addresses in constructors - nested addresses are created at runtime
|
|
163
|
+
const parent = new MyToolNode({ address: new oAddress('o://company') });
|
|
164
|
+
|
|
165
|
+
// Children use simple addresses with a parent reference
|
|
166
|
+
// During registration, child addresses become nested (e.g., 'o://company/sales')
|
|
167
|
+
const child1 = new MyToolNode({ address: new oAddress('o://sales') });
|
|
168
|
+
const child2 = new MyToolNode({ address: new oAddress('o://marketing') });
|
|
169
|
+
|
|
170
|
+
await parent.start();
|
|
171
|
+
await child1.start();
|
|
172
|
+
await child2.start();
|
|
173
|
+
|
|
174
|
+
// Register children with parent - this creates the nested addresses
|
|
175
|
+
parent.addChildNode(child1);
|
|
176
|
+
parent.addChildNode(child2);
|
|
177
|
+
|
|
178
|
+
// After registration:
|
|
179
|
+
// child1.address -> 'o://company/sales'
|
|
180
|
+
// child2.address -> 'o://company/marketing'
|
|
181
|
+
// Routing happens automatically through the hierarchy
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Core Concepts
|
|
185
|
+
|
|
186
|
+
### Tool Node Lifecycle States
|
|
187
|
+
|
|
188
|
+
Tool nodes (processes) transition through the following states:
|
|
189
|
+
|
|
190
|
+
- `STOPPED` - Initial state, tool node is not running
|
|
191
|
+
- `STARTING` - Tool node is initializing
|
|
192
|
+
- `RUNNING` - Tool node is active and processing requests
|
|
193
|
+
- `STOPPING` - Tool node is shutting down gracefully
|
|
194
|
+
- `ERROR` - Tool node encountered an error
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
console.log(toolNode.state); // NodeState.RUNNING
|
|
198
|
+
|
|
199
|
+
await toolNode.stop();
|
|
200
|
+
console.log(toolNode.state); // NodeState.STOPPED
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### The o:// Protocol
|
|
204
|
+
|
|
205
|
+
Addresses in Olane OS follow a hierarchical filesystem-like pattern:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
// Hierarchical addresses exist at runtime after parent-child registration.
|
|
209
|
+
// They are created by the system, not by constructors directly.
|
|
210
|
+
// For example, after registration a child address may become:
|
|
211
|
+
const address1 = new oAddress('o://company/finance/accounting');
|
|
212
|
+
const address2 = new oAddress('o://users/alice/inbox');
|
|
213
|
+
|
|
214
|
+
// Address operations
|
|
215
|
+
console.log(address1.paths); // "company/finance/accounting"
|
|
216
|
+
console.log(address1.root); // "o://company"
|
|
217
|
+
console.log(address1.validate()); // true
|
|
218
|
+
|
|
219
|
+
// Static vs dynamic addresses
|
|
220
|
+
const staticAddr = address1.toStaticAddress();
|
|
221
|
+
console.log(staticAddr.toString()); // "o://accounting"
|
|
222
|
+
|
|
223
|
+
// IMPORTANT: When creating nodes in constructors, always use simple
|
|
224
|
+
// (non-nested) addresses. Nested addresses are created automatically
|
|
225
|
+
// during parent-child registration at runtime.
|
|
226
|
+
// ✅ new oAddress('o://my-tool')
|
|
227
|
+
// ❌ new oAddress('o://parent/my-tool') -- violates validateNotNested()
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**📖 For complete details on address resolution, routing algorithms, and custom resolvers, see the [Router System documentation](./src/router/README.md).**
|
|
231
|
+
|
|
232
|
+
### Request/Response Pattern
|
|
233
|
+
|
|
234
|
+
All inter-process communication (IPC) follows a request/response pattern using JSON-RPC 2.0:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// Making a request from one tool node to another
|
|
238
|
+
// Full signature: use(address: oAddress, data?: { method: string, params: any, id?: string }, options?: UseOptions)
|
|
239
|
+
const response: oResponse = await toolNode.use(
|
|
240
|
+
new oAddress('o://target-toolnode'),
|
|
241
|
+
{
|
|
242
|
+
method: 'processData',
|
|
243
|
+
params: { key: 'value' },
|
|
244
|
+
id: 'unique-request-id'
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// Always check the response structure
|
|
249
|
+
if (response.result.success) {
|
|
250
|
+
console.log('Data:', response.result.data);
|
|
251
|
+
} else {
|
|
252
|
+
console.error('Error:', response.result.error);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Transport-level errors throw exceptions
|
|
256
|
+
try {
|
|
257
|
+
const response = await toolNode.use(targetAddress, requestData);
|
|
258
|
+
// Check response.result.success for application-level errors
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error instanceof oError) {
|
|
261
|
+
console.error(`Transport error ${error.code}: ${error.message}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**📖 Learn more about JSON-RPC messaging, request states, and connection lifecycle in the [Connection System documentation](./src/connection/README.md).**
|
|
267
|
+
|
|
268
|
+
### Response Structure
|
|
269
|
+
|
|
270
|
+
When you call `use()`, the response follows a standard wrapping structure created by the `ResponseBuilder`. In higher-level packages (`@olane/o-node`, `@olane/o-tool`, `@olane/o-lane`), responses are wrapped with `success`, `data`, and `error` fields:
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
const response = await toolNode.use(targetAddress, {
|
|
274
|
+
method: 'processData',
|
|
275
|
+
params: { key: 'value' }
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Standard response structure:
|
|
279
|
+
// {
|
|
280
|
+
// jsonrpc: "2.0",
|
|
281
|
+
// id: "request-id",
|
|
282
|
+
// result: {
|
|
283
|
+
// success: boolean, // Whether the operation succeeded
|
|
284
|
+
// data: any, // The returned data (on success)
|
|
285
|
+
// error?: string // Error details (on failure)
|
|
286
|
+
// }
|
|
287
|
+
// }
|
|
288
|
+
|
|
289
|
+
// Always check success before accessing data
|
|
290
|
+
if (response.result.success) {
|
|
291
|
+
const data = response.result.data;
|
|
292
|
+
console.log('Result:', data);
|
|
293
|
+
} else {
|
|
294
|
+
console.error('Error:', response.result.error);
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
> **Important**: Access data via `response.result.data`, not `response.result` directly. The `result` object contains the wrapping fields (`success`, `data`, `error`), not the raw return value.
|
|
299
|
+
|
|
300
|
+
### Metrics and Observability
|
|
301
|
+
|
|
302
|
+
Every tool node tracks metrics automatically:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// Access tool node metrics
|
|
306
|
+
console.log(toolNode.metrics.successCount);
|
|
307
|
+
console.log(toolNode.metrics.errorCount);
|
|
308
|
+
|
|
309
|
+
// Built-in logging
|
|
310
|
+
toolNode.logger.debug('Debug message');
|
|
311
|
+
toolNode.logger.info('Info message');
|
|
312
|
+
toolNode.logger.warn('Warning message');
|
|
313
|
+
toolNode.logger.error('Error message');
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Architecture
|
|
317
|
+
|
|
318
|
+
### Abstract Base Class
|
|
319
|
+
|
|
320
|
+
`oCore` is an abstract base class that provides:
|
|
321
|
+
|
|
322
|
+
- **Lifecycle Management**: `start()`, `stop()`, `initialize()`, `teardown()`
|
|
323
|
+
- **Communication**: `use()`, `useDirect()`, `connect()`
|
|
324
|
+
- **Routing**: `router`, `initializeRouter()`
|
|
325
|
+
- **Hierarchy**: `addChildNode()`, `removeChildNode()`, `hierarchyManager`
|
|
326
|
+
- **State Management**: `state`, `NodeState` enum
|
|
327
|
+
- **Observability**: `metrics`, `logger`, `requestManager`
|
|
328
|
+
|
|
329
|
+
### Key Components
|
|
330
|
+
|
|
331
|
+
#### 1. Router System (oAddress & oRouter)
|
|
332
|
+
Hierarchical addressing and intelligent routing for tool nodes
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
const addr = new oAddress('o://domain/subdomain/resource');
|
|
336
|
+
addr.validate(); // Check if address is valid
|
|
337
|
+
addr.toStaticAddress(); // Convert to static address
|
|
338
|
+
addr.toCID(); // Convert to Content ID
|
|
339
|
+
|
|
340
|
+
// Router determines the next hop in the network
|
|
341
|
+
const { nextHopAddress, targetAddress } = await router.translate(
|
|
342
|
+
address,
|
|
343
|
+
node
|
|
344
|
+
);
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**📚 [View detailed Router System documentation →](./src/router/README.md)**
|
|
348
|
+
|
|
349
|
+
#### 2. Connection System (oConnection & oConnectionManager)
|
|
350
|
+
Inter-Process Communication (IPC) layer for tool-node-to-tool-node messaging
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// Connections are cached and reused
|
|
354
|
+
const connection = await connectionManager.connect({
|
|
355
|
+
nextHop: nextHopAddress,
|
|
356
|
+
target: targetAddress
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// Send data over the connection
|
|
360
|
+
const response = await connection.send({
|
|
361
|
+
address: 'o://target/service',
|
|
362
|
+
payload: { key: 'value' }
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**📚 [View detailed Connection System documentation →](./src/connection/README.md)**
|
|
367
|
+
|
|
368
|
+
#### 3. oHierarchyManager
|
|
369
|
+
Manages parent-child relationships between tool nodes
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
toolNode.hierarchyManager.addChild(childAddress);
|
|
373
|
+
toolNode.hierarchyManager.removeChild(childAddress);
|
|
374
|
+
console.log(toolNode.hierarchyManager.children);
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Advanced Usage
|
|
378
|
+
|
|
379
|
+
### Custom Transport & Connection Implementation
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
import { oTransport, TransportType, oConnection, oConnectionConfig } from '@olane/o-core';
|
|
383
|
+
|
|
384
|
+
class MyCustomTransport extends oTransport {
|
|
385
|
+
constructor() {
|
|
386
|
+
super(TransportType.CUSTOM);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async send(data: any): Promise<any> {
|
|
390
|
+
// Implement your transport logic (HTTP, WebSocket, etc.)
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Custom connection implementation
|
|
395
|
+
class MyConnection extends oConnection {
|
|
396
|
+
async transmit(request: oRequest): Promise<oResponse> {
|
|
397
|
+
// Implement your connection logic
|
|
398
|
+
const response = await fetch(this.nextHopAddress.toString(), {
|
|
399
|
+
method: 'POST',
|
|
400
|
+
body: request.toString()
|
|
401
|
+
});
|
|
402
|
+
return new oResponse(await response.json());
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
class MyToolNode extends oCore {
|
|
407
|
+
configureTransports(): any[] {
|
|
408
|
+
return [new MyCustomTransport()];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**📖 For connection pooling, retry logic, middleware, and transport-specific implementations, see the [Connection System documentation](./src/connection/README.md).**
|
|
414
|
+
|
|
415
|
+
### Custom Router Implementation
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { oRouter, RouteResponse } from '@olane/o-core';
|
|
419
|
+
|
|
420
|
+
class MyRouter extends oRouter {
|
|
421
|
+
async translate(address: oAddress, node: oCore): Promise<RouteResponse> {
|
|
422
|
+
// Implement custom routing logic
|
|
423
|
+
return {
|
|
424
|
+
nextHopAddress: calculatedNextHop,
|
|
425
|
+
targetAddress: address
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
isInternal(address: oAddress, node: oCore): boolean {
|
|
430
|
+
// Determine if address is internal to this node
|
|
431
|
+
return address.root === node.address.root;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async route(request: oRouterRequest, node: oCore): Promise<RouteResponse> {
|
|
435
|
+
// Handle routing requests
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**📖 For advanced routing patterns, custom resolvers, and hierarchical routing strategies, see the [Router System documentation](./src/router/README.md).**
|
|
441
|
+
|
|
442
|
+
### Error Handling
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { oError, oErrorCodes } from '@olane/o-core';
|
|
446
|
+
|
|
447
|
+
// In your execute method
|
|
448
|
+
async execute(request: oRequest): Promise<any> {
|
|
449
|
+
if (!isValid(request.params)) {
|
|
450
|
+
throw new oError(
|
|
451
|
+
oErrorCodes.INVALID_PARAMS,
|
|
452
|
+
'Invalid parameters provided'
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
return await processRequest(request);
|
|
458
|
+
} catch (error) {
|
|
459
|
+
throw new oError(
|
|
460
|
+
oErrorCodes.EXECUTION_ERROR,
|
|
461
|
+
error.message
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Graceful Shutdown
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
import { setupGracefulShutdown } from '@olane/o-core';
|
|
471
|
+
|
|
472
|
+
const toolNode = new MyToolNode({ address: new oAddress('o://my-toolnode') });
|
|
473
|
+
await toolNode.start();
|
|
474
|
+
|
|
475
|
+
// Setup graceful shutdown handlers
|
|
476
|
+
setupGracefulShutdown(async () => {
|
|
477
|
+
console.log('Shutting down gracefully...');
|
|
478
|
+
await toolNode.stop();
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// Tool node will automatically stop on SIGINT/SIGTERM
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
## API Reference
|
|
485
|
+
|
|
486
|
+
### oCore Class
|
|
487
|
+
|
|
488
|
+
Abstract base class for building tool nodes.
|
|
489
|
+
|
|
490
|
+
#### Properties
|
|
491
|
+
- `address: oAddress` - The tool node's hierarchical address
|
|
492
|
+
- `state: NodeState` - Current lifecycle state
|
|
493
|
+
- `metrics: oMetrics` - Performance and usage metrics
|
|
494
|
+
- `hierarchyManager: oHierarchyManager` - Manages child nodes
|
|
495
|
+
- `router: oRouter` - Routing logic
|
|
496
|
+
- `connectionManager: oConnectionManager` - Connection pooling
|
|
497
|
+
|
|
498
|
+
#### Methods
|
|
499
|
+
- `async start(): Promise<void>` - Start the tool node
|
|
500
|
+
- `async stop(): Promise<void>` - Stop the tool node gracefully
|
|
501
|
+
- `async use(address: oAddress, data?: UseDataConfig, options?: UseOptions): Promise<oResponse>` - Communicate with another tool node (IPC). The `options` parameter supports `noRouting`, `isStream`, `onChunk`, `readTimeoutMs`, `drainTimeoutMs`, and `abortSignal`.
|
|
502
|
+
- `async useDirect(address: oAddress, data?: UseDataConfig): Promise<oResponse>` - Send a request without routing (equivalent to `use()` with `{ noRouting: true }`)
|
|
503
|
+
- `async useStream(address: oAddress, data: UseDataConfig, options: UseStreamOptions): Promise<oResponse>` - Send a streaming request to another tool node
|
|
504
|
+
- `async execute(request): Promise<any>` - Execute a request (abstract - you implement this)
|
|
505
|
+
- `addChildNode(node): void` - Add a child tool node
|
|
506
|
+
- `removeChildNode(node): void` - Remove a child tool node
|
|
507
|
+
- `async whoami(): Promise<any>` - Get tool node information
|
|
508
|
+
|
|
509
|
+
### oAddress Class
|
|
510
|
+
|
|
511
|
+
#### Methods
|
|
512
|
+
- `validate(): boolean` - Validate address format
|
|
513
|
+
- `toStaticAddress(): oAddress` - Convert to static address
|
|
514
|
+
- `toString(): string` - Get string representation
|
|
515
|
+
- `equals(other): boolean` - Compare addresses
|
|
516
|
+
- `async toCID(): Promise<CID>` - Convert to Content ID
|
|
517
|
+
|
|
518
|
+
### oError Class
|
|
519
|
+
|
|
520
|
+
#### Constructor
|
|
521
|
+
```typescript
|
|
522
|
+
new oError(code: oErrorCodes, message: string, data?: any)
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
#### Methods
|
|
526
|
+
- `toJSON(): object` - Serialize error
|
|
527
|
+
- `static fromJSON(json): oError` - Deserialize error
|
|
528
|
+
|
|
529
|
+
## Testing
|
|
530
|
+
|
|
531
|
+
```bash
|
|
532
|
+
# Run tests
|
|
533
|
+
pnpm test
|
|
534
|
+
|
|
535
|
+
# Run tests in Node.js
|
|
536
|
+
pnpm run test:node
|
|
537
|
+
|
|
538
|
+
# Run tests in browser
|
|
539
|
+
pnpm run test:browser
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
## Development
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
# Install dependencies
|
|
546
|
+
pnpm install
|
|
547
|
+
|
|
548
|
+
# Build the package
|
|
549
|
+
pnpm run build
|
|
550
|
+
|
|
551
|
+
# Run in development mode
|
|
552
|
+
pnpm run dev
|
|
553
|
+
|
|
554
|
+
# Lint the code
|
|
555
|
+
pnpm run lint
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## Use Cases
|
|
559
|
+
|
|
560
|
+
o-core enables you to:
|
|
561
|
+
|
|
562
|
+
1. **Build Specialized Tool Nodes** with unique o:// addresses and capabilities
|
|
563
|
+
2. **Create Hierarchical Tool Networks** that AI agents discover and use
|
|
564
|
+
3. **Route Requests Intelligently** through tool node hierarchies
|
|
565
|
+
4. **Manage Tool Node Lifecycles** with automatic cleanup and error handling
|
|
566
|
+
5. **Share Knowledge and Capabilities** across tool networks through addressable resources
|
|
567
|
+
|
|
568
|
+
## What o-core is NOT
|
|
569
|
+
|
|
570
|
+
- ❌ **Not a network framework** - It's the OS kernel for tool nodes
|
|
571
|
+
- ❌ **Not an orchestration tool** - It enables emergent coordination, not explicit workflows
|
|
572
|
+
- ❌ **Not a REST API** - It's a runtime for inter-process communication (IPC)
|
|
573
|
+
- ❌ **Not a complete solution** - It's an abstract foundation; use o-node for production
|
|
574
|
+
- ❌ **Not what you use directly** - Extend it or use higher-level packages (o-node, o-tool, o-lane)
|
|
575
|
+
|
|
576
|
+
## Related Packages
|
|
577
|
+
|
|
578
|
+
- `@olane/o-protocol` - Protocol definitions and types
|
|
579
|
+
- `@olane/o-node` - Complete node implementation (extends o-core)
|
|
580
|
+
- `@olane/o-tool` - Tool system for agent capabilities
|
|
581
|
+
- `@olane/o-storage` - Storage layer for agent state
|
|
582
|
+
- `@olane/o-network-cli` - CLI for managing agent networks
|
|
583
|
+
|
|
584
|
+
## Component Documentation
|
|
585
|
+
|
|
586
|
+
For in-depth documentation on specific o-core components, see:
|
|
587
|
+
|
|
588
|
+
- **[Router System](./src/router/README.md)** - Deep dive into the `o://` protocol, address resolution, routing logic, and custom resolvers
|
|
589
|
+
- **[Connection System](./src/connection/README.md)** - Complete guide to IPC, JSON-RPC messaging, connection pooling, and transport implementations
|
|
590
|
+
|
|
591
|
+
## Documentation
|
|
592
|
+
|
|
593
|
+
- [Full Documentation](https://olane.com/docs)
|
|
594
|
+
- [Quickstart Guide](https://olane.com/docs/quickstart)
|
|
595
|
+
- [Architecture Overview](https://olane.com/docs/architecture/overview)
|
|
596
|
+
- [API Reference](https://olane.com/docs/api)
|
|
597
|
+
|
|
598
|
+
## Support
|
|
599
|
+
|
|
600
|
+
- [GitHub Issues](https://github.com/olane-labs/olane/issues)
|
|
601
|
+
- [Community Forum](https://olane.com/community)
|
|
602
|
+
- [Email Support](mailto:support@olane.com)
|
|
603
|
+
|
|
604
|
+
## Contributing
|
|
605
|
+
|
|
606
|
+
We welcome contributions! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
607
|
+
|
|
608
|
+
## License
|
|
609
|
+
|
|
610
|
+
ISC © Olane Inc.
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
614
|
+
**Part of the Olane OS ecosystem** - An agentic operating system where AI agents are the users and you build tool nodes as applications.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Main entry point — tsc compiles this for the `node` condition.
|
|
2
|
+
// The `default` (browser/RN) condition points directly at o-context.browser.js
|
|
3
|
+
// via conditional exports in package.json, so this file is never loaded there.
|
|
4
|
+
export { createOContext } from './o-context.node.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { OContext } from './o-context.js';
|
|
2
|
+
export type { OContext } from './o-context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create an `OContext<T>` using a simple variable swap.
|
|
5
|
+
*
|
|
6
|
+
* Safe for single-user environments (browsers, React Native) where there are
|
|
7
|
+
* no concurrent requests. Contains zero `node:` imports so bundlers like
|
|
8
|
+
* Metro will never try to resolve Node-only modules.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createOContext<T>(): OContext<T>;
|
|
11
|
+
//# sourceMappingURL=o-context.browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"o-context.browser.d.ts","sourceRoot":"","sources":["../../src/o-context.browser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAyB/C"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create an `OContext<T>` using a simple variable swap.
|
|
3
|
+
*
|
|
4
|
+
* Safe for single-user environments (browsers, React Native) where there are
|
|
5
|
+
* no concurrent requests. Contains zero `node:` imports so bundlers like
|
|
6
|
+
* Metro will never try to resolve Node-only modules.
|
|
7
|
+
*/
|
|
8
|
+
export function createOContext() {
|
|
9
|
+
let currentStore;
|
|
10
|
+
return {
|
|
11
|
+
run(store, fn) {
|
|
12
|
+
const prev = currentStore;
|
|
13
|
+
currentStore = store;
|
|
14
|
+
try {
|
|
15
|
+
const result = fn();
|
|
16
|
+
if (result instanceof Promise) {
|
|
17
|
+
return result.finally(() => {
|
|
18
|
+
currentStore = prev;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
currentStore = prev;
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
currentStore = prev;
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
getStore() {
|
|
30
|
+
return currentStore;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic async context primitive.
|
|
3
|
+
*
|
|
4
|
+
* `run()` establishes a store for the duration of `fn` (and any async work it
|
|
5
|
+
* spawns). `getStore()` retrieves the current store from anywhere inside that
|
|
6
|
+
* call tree.
|
|
7
|
+
*
|
|
8
|
+
* Platform-specific implementations live in `o-context.node.ts` (AsyncLocalStorage)
|
|
9
|
+
* and `o-context.browser.ts` (simple variable swap).
|
|
10
|
+
*/
|
|
11
|
+
export interface OContext<T> {
|
|
12
|
+
run<R>(store: T, fn: () => R): R;
|
|
13
|
+
getStore(): T | undefined;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=o-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"o-context.d.ts","sourceRoot":"","sources":["../../src/o-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACjC,QAAQ,IAAI,CAAC,GAAG,SAAS,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OContext } from './o-context.js';
|
|
2
|
+
export type { OContext } from './o-context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create an `OContext<T>` backed by Node.js `AsyncLocalStorage`.
|
|
5
|
+
*
|
|
6
|
+
* Each async call tree started via `run()` gets its own isolated store,
|
|
7
|
+
* making this safe for concurrent requests on a server.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createOContext<T>(): OContext<T>;
|
|
10
|
+
//# sourceMappingURL=o-context.node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"o-context.node.d.ts","sourceRoot":"","sources":["../../src/o-context.node.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAW/C"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
/**
|
|
3
|
+
* Create an `OContext<T>` backed by Node.js `AsyncLocalStorage`.
|
|
4
|
+
*
|
|
5
|
+
* Each async call tree started via `run()` gets its own isolated store,
|
|
6
|
+
* making this safe for concurrent requests on a server.
|
|
7
|
+
*/
|
|
8
|
+
export function createOContext() {
|
|
9
|
+
const als = new AsyncLocalStorage();
|
|
10
|
+
return {
|
|
11
|
+
run(store, fn) {
|
|
12
|
+
return als.run(store, fn);
|
|
13
|
+
},
|
|
14
|
+
getStore() {
|
|
15
|
+
return als.getStore();
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@olane/o-context",
|
|
3
|
+
"version": "0.8.9",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/src/index.d.ts",
|
|
10
|
+
"node": "./dist/src/index.js",
|
|
11
|
+
"default": "./dist/src/o-context.browser.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/**/*",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "aegir test",
|
|
21
|
+
"test:node": "aegir test -t node",
|
|
22
|
+
"test:browser": "aegir test -t browser",
|
|
23
|
+
"deep:clean": "rm -rf node_modules && rm package-lock.json",
|
|
24
|
+
"build": "rm -rf dist && tsc",
|
|
25
|
+
"start:prod": "node dist/index.js",
|
|
26
|
+
"prepublishOnly": "npm run build",
|
|
27
|
+
"lint": "aegir lint"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/olane-labs/olane.git"
|
|
32
|
+
},
|
|
33
|
+
"author": "Olane Inc.",
|
|
34
|
+
"license": "(MIT OR Apache-2.0)",
|
|
35
|
+
"description": "Generic async context primitive with platform-specific implementations (AsyncLocalStorage for Node, variable swap for browser/RN)",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
38
|
+
"@eslint/js": "^9.29.0",
|
|
39
|
+
"@tsconfig/node20": "^20.1.6",
|
|
40
|
+
"@types/jest": "^30.0.0",
|
|
41
|
+
"@types/uuid": "^10.0.0",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^8.34.1",
|
|
43
|
+
"@typescript-eslint/parser": "^8.34.1",
|
|
44
|
+
"aegir": "^47.0.21",
|
|
45
|
+
"eslint": "^9.29.0",
|
|
46
|
+
"eslint-config-prettier": "^10.1.6",
|
|
47
|
+
"eslint-plugin-prettier": "^5.5.0",
|
|
48
|
+
"globals": "^16.2.0",
|
|
49
|
+
"jest": "^30.0.0",
|
|
50
|
+
"prettier": "^3.5.3",
|
|
51
|
+
"ts-jest": "^29.4.0",
|
|
52
|
+
"ts-node": "^10.9.2",
|
|
53
|
+
"tsconfig-paths": "^4.2.0",
|
|
54
|
+
"tsx": "^4.20.3",
|
|
55
|
+
"typescript": "^5.8.3"
|
|
56
|
+
},
|
|
57
|
+
"gitHead": "982373dd4827ee23bdb0fcba913a718d4bc64f1a"
|
|
58
|
+
}
|