@aroha-sdk/langchain-bridge 1.0.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/package.json +21 -0
- package/src/bridge.test.ts +257 -0
- package/src/bridge.ts +220 -0
- package/src/index.ts +2 -0
- package/src/types.ts +39 -0
- package/tsconfig.json +9 -0
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aroha-sdk/langchain-bridge",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Aroha ↔ LangChain/AutoGen/Semantic Kernel bridge — use Aroha as transport for any agent framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.json",
|
|
10
|
+
"test": "vitest run --passWithNoTests",
|
|
11
|
+
"dev": "tsc -p tsconfig.json --watch"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@aroha-sdk/core": "^1.0.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"typescript": "^5.4.5",
|
|
18
|
+
"vitest": "^1.6.0",
|
|
19
|
+
"@types/node": "^20.14.0"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
arohaCapabilityToLangChainTool,
|
|
4
|
+
arohaCapabilityToOpenAITool,
|
|
5
|
+
langChainToolsToCapabilities,
|
|
6
|
+
langChainAgentToArohaProvider,
|
|
7
|
+
} from "./bridge.js";
|
|
8
|
+
import type { LangChainToolLike } from "./types.js";
|
|
9
|
+
import { ArohaClient, ArohaServer, generateDID } from "@aroha-sdk/core";
|
|
10
|
+
|
|
11
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function makeTool(
|
|
14
|
+
name: string,
|
|
15
|
+
description = "A test tool",
|
|
16
|
+
result = "tool result"
|
|
17
|
+
): LangChainToolLike {
|
|
18
|
+
return {
|
|
19
|
+
name,
|
|
20
|
+
description,
|
|
21
|
+
call: vi.fn().mockResolvedValue(result),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── langChainToolsToCapabilities ────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
describe("langChainToolsToCapabilities", () => {
|
|
28
|
+
it("maps each tool to a ArohaCapabilityDescriptor", () => {
|
|
29
|
+
const tools = [
|
|
30
|
+
makeTool("search-flights", "Search for available flights"),
|
|
31
|
+
makeTool("search-hotels", "Search for hotels"),
|
|
32
|
+
];
|
|
33
|
+
const caps = langChainToolsToCapabilities(tools);
|
|
34
|
+
|
|
35
|
+
expect(caps).toHaveLength(2);
|
|
36
|
+
expect(caps[0].capabilityId).toBe("search-flights");
|
|
37
|
+
expect(caps[0].description).toBe("Search for available flights");
|
|
38
|
+
expect(caps[1].capabilityId).toBe("search-hotels");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("passes through the tool schema when present", () => {
|
|
42
|
+
const schema = { type: "object", properties: { q: { type: "string" } } };
|
|
43
|
+
const tools = [{ name: "search", description: "d", schema, call: vi.fn() }];
|
|
44
|
+
const caps = langChainToolsToCapabilities(tools);
|
|
45
|
+
expect(caps[0].inputSchema).toEqual(schema);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("sets inputSchema to undefined when the tool has no schema", () => {
|
|
49
|
+
const caps = langChainToolsToCapabilities([makeTool("no-schema")]);
|
|
50
|
+
expect(caps[0].inputSchema).toBeUndefined();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("returns an empty array for an empty tool list", () => {
|
|
54
|
+
expect(langChainToolsToCapabilities([])).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ─── arohaCapabilityToLangChainTool ────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
describe("arohaCapabilityToLangChainTool", () => {
|
|
61
|
+
it("returns a tool with the correct name (sanitised to word-chars)", () => {
|
|
62
|
+
const tool = arohaCapabilityToLangChainTool("search-flights", {
|
|
63
|
+
endpoint: "http://localhost:3001",
|
|
64
|
+
agentDID: "did:aroha:remote",
|
|
65
|
+
callerDID: "did:aroha:caller",
|
|
66
|
+
callerPrivateKey: new Uint8Array(32),
|
|
67
|
+
description: "Search flights",
|
|
68
|
+
client: { send: vi.fn() } as unknown as ArohaClient,
|
|
69
|
+
});
|
|
70
|
+
// Hyphens are replaced with underscores by the sanitiser
|
|
71
|
+
expect(tool.name).toMatch(/^[a-zA-Z0-9_-]+$/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns the provided description", () => {
|
|
75
|
+
const tool = arohaCapabilityToLangChainTool("cap", {
|
|
76
|
+
endpoint: "http://localhost:3001",
|
|
77
|
+
agentDID: "did:aroha:remote",
|
|
78
|
+
callerDID: "did:aroha:caller",
|
|
79
|
+
callerPrivateKey: new Uint8Array(32),
|
|
80
|
+
description: "Does something useful",
|
|
81
|
+
client: { send: vi.fn() } as unknown as ArohaClient,
|
|
82
|
+
});
|
|
83
|
+
expect(tool.description).toBe("Does something useful");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("passes through the schema option", () => {
|
|
87
|
+
const schema = { type: "object" };
|
|
88
|
+
const tool = arohaCapabilityToLangChainTool("cap", {
|
|
89
|
+
endpoint: "http://localhost:3001",
|
|
90
|
+
agentDID: "did:aroha:remote",
|
|
91
|
+
callerDID: "did:aroha:caller",
|
|
92
|
+
callerPrivateKey: new Uint8Array(32),
|
|
93
|
+
description: "d",
|
|
94
|
+
schema,
|
|
95
|
+
client: { send: vi.fn() } as unknown as ArohaClient,
|
|
96
|
+
});
|
|
97
|
+
expect(tool.schema).toEqual(schema);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns the result string from a successful ArohaResponse", async () => {
|
|
101
|
+
const identity = await generateDID("caller", "http://localhost:3001");
|
|
102
|
+
const mockSend = vi.fn().mockResolvedValue({
|
|
103
|
+
type: "ArohaResponse",
|
|
104
|
+
body: { capability: "greet", result: { greeting: "hello" } },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const tool = arohaCapabilityToLangChainTool("greet", {
|
|
108
|
+
endpoint: "http://localhost:3002",
|
|
109
|
+
agentDID: "did:aroha:remote",
|
|
110
|
+
callerDID: identity.did,
|
|
111
|
+
callerPrivateKey: identity.privateKey,
|
|
112
|
+
description: "Greet",
|
|
113
|
+
client: { send: mockSend } as unknown as ArohaClient,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const result = await tool.call({ name: "World" });
|
|
117
|
+
expect(result).toBe(JSON.stringify({ greeting: "hello" }));
|
|
118
|
+
expect(mockSend).toHaveBeenCalledTimes(1);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("returns a plain string result as-is", async () => {
|
|
122
|
+
const identity = await generateDID("caller", "http://localhost:3001");
|
|
123
|
+
const mockSend = vi.fn().mockResolvedValue({
|
|
124
|
+
type: "ArohaResponse",
|
|
125
|
+
body: { capability: "echo", result: "pong" },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const tool = arohaCapabilityToLangChainTool("echo", {
|
|
129
|
+
endpoint: "http://localhost:3002",
|
|
130
|
+
agentDID: "did:aroha:remote",
|
|
131
|
+
callerDID: identity.did,
|
|
132
|
+
callerPrivateKey: identity.privateKey,
|
|
133
|
+
description: "Echo",
|
|
134
|
+
client: { send: mockSend } as unknown as ArohaClient,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const result = await tool.call("{}");
|
|
138
|
+
expect(result).toBe("pong");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("throws when the response is a ArohaError", async () => {
|
|
142
|
+
const identity = await generateDID("caller", "http://localhost:3001");
|
|
143
|
+
const mockSend = vi.fn().mockResolvedValue({
|
|
144
|
+
type: "ArohaError",
|
|
145
|
+
body: { code: "Aroha_UNKNOWN_CAPABILITY", message: "No such capability" },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const tool = arohaCapabilityToLangChainTool("missing-cap", {
|
|
149
|
+
endpoint: "http://localhost:3002",
|
|
150
|
+
agentDID: "did:aroha:remote",
|
|
151
|
+
callerDID: identity.did,
|
|
152
|
+
callerPrivateKey: identity.privateKey,
|
|
153
|
+
description: "d",
|
|
154
|
+
client: { send: mockSend } as unknown as ArohaClient,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
await expect(tool.call({})).rejects.toThrow("No such capability");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("throws when the client returns no response", async () => {
|
|
161
|
+
const identity = await generateDID("caller", "http://localhost:3001");
|
|
162
|
+
const mockSend = vi.fn().mockResolvedValue(null);
|
|
163
|
+
|
|
164
|
+
const tool = arohaCapabilityToLangChainTool("cap", {
|
|
165
|
+
endpoint: "http://localhost:3002",
|
|
166
|
+
agentDID: "did:aroha:remote",
|
|
167
|
+
callerDID: identity.did,
|
|
168
|
+
callerPrivateKey: identity.privateKey,
|
|
169
|
+
description: "d",
|
|
170
|
+
client: { send: mockSend } as unknown as ArohaClient,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await expect(tool.call({})).rejects.toThrow(/No response/);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("accepts string input and parses it as JSON", async () => {
|
|
177
|
+
const identity = await generateDID("caller", "http://localhost:3001");
|
|
178
|
+
const mockSend = vi.fn().mockResolvedValue({
|
|
179
|
+
type: "ArohaResponse",
|
|
180
|
+
body: { capability: "cap", result: { ok: true } },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const tool = arohaCapabilityToLangChainTool("cap", {
|
|
184
|
+
endpoint: "http://localhost:3002",
|
|
185
|
+
agentDID: "did:aroha:remote",
|
|
186
|
+
callerDID: identity.did,
|
|
187
|
+
callerPrivateKey: identity.privateKey,
|
|
188
|
+
description: "d",
|
|
189
|
+
client: { send: mockSend } as unknown as ArohaClient,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await tool.call('{"key":"value"}');
|
|
193
|
+
// Should not throw — the string input is parsed and used as params
|
|
194
|
+
expect(mockSend).toHaveBeenCalledTimes(1);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ─── arohaCapabilityToOpenAITool ───────────────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
describe("arohaCapabilityToOpenAITool", () => {
|
|
201
|
+
it("returns an OpenAI-shaped function tool", () => {
|
|
202
|
+
const tool = arohaCapabilityToOpenAITool("greet", {
|
|
203
|
+
endpoint: "http://localhost:3002",
|
|
204
|
+
agentDID: "did:aroha:remote",
|
|
205
|
+
callerDID: "did:aroha:caller",
|
|
206
|
+
callerPrivateKey: new Uint8Array(32),
|
|
207
|
+
description: "Greet a user",
|
|
208
|
+
client: { send: vi.fn() } as unknown as ArohaClient,
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(tool.type).toBe("function");
|
|
212
|
+
expect(tool.function).toBeDefined();
|
|
213
|
+
expect(tool.function.description).toBe("Greet a user");
|
|
214
|
+
expect(typeof tool.invoke).toBe("function");
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it("invoke() parses a JSON string result back to an object", async () => {
|
|
218
|
+
const identity = await generateDID("caller", "http://localhost:3001");
|
|
219
|
+
const mockSend = vi.fn().mockResolvedValue({
|
|
220
|
+
type: "ArohaResponse",
|
|
221
|
+
body: { capability: "greet", result: { msg: "hi" } },
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const tool = arohaCapabilityToOpenAITool("greet", {
|
|
225
|
+
endpoint: "http://localhost:3002",
|
|
226
|
+
agentDID: "did:aroha:remote",
|
|
227
|
+
callerDID: identity.did,
|
|
228
|
+
callerPrivateKey: identity.privateKey,
|
|
229
|
+
description: "d",
|
|
230
|
+
client: { send: mockSend } as unknown as ArohaClient,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const result = await tool.invoke({ name: "World" });
|
|
234
|
+
expect(result).toEqual({ msg: "hi" });
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// ─── langChainAgentToArohaProvider ─────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
describe("langChainAgentToArohaProvider", () => {
|
|
241
|
+
it("returns a ArohaServer instance without starting it", async () => {
|
|
242
|
+
const identity = await generateDID("lc-bridge", "http://localhost:3099");
|
|
243
|
+
|
|
244
|
+
const server = langChainAgentToArohaProvider(
|
|
245
|
+
[makeTool("search-flights"), makeTool("book-hotel")],
|
|
246
|
+
{
|
|
247
|
+
agentDID: identity.did,
|
|
248
|
+
agentPrivateKey: identity.privateKey,
|
|
249
|
+
port: 3099,
|
|
250
|
+
didDocument: identity.document,
|
|
251
|
+
resolvePublicKey: async () => null,
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
expect(server).toBeInstanceOf(ArohaServer);
|
|
256
|
+
});
|
|
257
|
+
});
|
package/src/bridge.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aroha ↔ LangChain/AutoGen Bridge
|
|
3
|
+
*
|
|
4
|
+
* Aroha is the transport and identity layer. Your existing agent framework
|
|
5
|
+
* (LangChain, AutoGen, Semantic Kernel, LlamaIndex) keeps its reasoning,
|
|
6
|
+
* memory, and tool-calling logic unchanged.
|
|
7
|
+
*
|
|
8
|
+
* This bridge lets you:
|
|
9
|
+
* 1. Expose LangChain tools as Aroha capabilities (so other Aroha agents
|
|
10
|
+
* can discover and call your tools with signed envelopes).
|
|
11
|
+
* 2. Wrap Aroha capabilities as LangChain tools (so your LangChain agent
|
|
12
|
+
* can call remote Aroha agents transparently).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
ArohaClient,
|
|
17
|
+
ArohaServer,
|
|
18
|
+
buildEnvelope,
|
|
19
|
+
newCorrelationId,
|
|
20
|
+
type ArohaEnvelope,
|
|
21
|
+
type ArohaServerOptions,
|
|
22
|
+
type MessageHandler,
|
|
23
|
+
type ArohaRequestBody,
|
|
24
|
+
type ArohaResponseBody,
|
|
25
|
+
} from "@aroha-sdk/core";
|
|
26
|
+
import {
|
|
27
|
+
type LangChainToolLike,
|
|
28
|
+
type OpenAIFunctionLike,
|
|
29
|
+
type ArohaCapabilityDescriptor,
|
|
30
|
+
} from "./types.js";
|
|
31
|
+
|
|
32
|
+
// ─── Aroha capability → LangChain tool ────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
export interface ArohaCapabilityToToolOptions {
|
|
35
|
+
/** The Aroha endpoint URL of the remote agent. */
|
|
36
|
+
endpoint: string;
|
|
37
|
+
/** The DID of the remote agent (the "to" field). */
|
|
38
|
+
agentDID: string;
|
|
39
|
+
/** DID of the calling agent (the "from" field). */
|
|
40
|
+
callerDID: string;
|
|
41
|
+
/** Ed25519 private key of the calling agent for signing envelopes. */
|
|
42
|
+
callerPrivateKey: Uint8Array;
|
|
43
|
+
/** Human-readable description used by the LLM to decide when to call this tool. */
|
|
44
|
+
description: string;
|
|
45
|
+
/** JSON Schema for the capability's params. Optional. */
|
|
46
|
+
schema?: Record<string, unknown>;
|
|
47
|
+
client?: ArohaClient;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Wrap a single Aroha capability as a LangChain-compatible tool.
|
|
52
|
+
*
|
|
53
|
+
* The returned tool sends a signed ArohaRequest to the remote agent and
|
|
54
|
+
* returns the JSON-serialised result as a string (what LangChain expects).
|
|
55
|
+
*/
|
|
56
|
+
export function arohaCapabilityToLangChainTool(
|
|
57
|
+
capabilityId: string,
|
|
58
|
+
opts: ArohaCapabilityToToolOptions
|
|
59
|
+
): LangChainToolLike {
|
|
60
|
+
const client = opts.client ?? new ArohaClient();
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name: capabilityId.replace(/[^a-zA-Z0-9_-]/g, "_"),
|
|
64
|
+
description: opts.description,
|
|
65
|
+
schema: opts.schema,
|
|
66
|
+
|
|
67
|
+
async call(input: string | Record<string, unknown>): Promise<string> {
|
|
68
|
+
const params: Record<string, unknown> =
|
|
69
|
+
typeof input === "string" ? JSON.parse(input) : input;
|
|
70
|
+
|
|
71
|
+
const body: ArohaRequestBody = { capability: capabilityId, params };
|
|
72
|
+
const correlationId = newCorrelationId();
|
|
73
|
+
const envelope = await buildEnvelope(
|
|
74
|
+
"ArohaRequest",
|
|
75
|
+
opts.callerDID,
|
|
76
|
+
opts.agentDID,
|
|
77
|
+
body,
|
|
78
|
+
correlationId,
|
|
79
|
+
opts.callerPrivateKey
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const response = await client.send(opts.endpoint, envelope);
|
|
83
|
+
if (!response) throw new Error(`No response from ${opts.agentDID}`);
|
|
84
|
+
if (response.type === "ArohaError") {
|
|
85
|
+
throw new Error((response.body as { message: string }).message);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const result = (response.body as ArohaResponseBody).result;
|
|
89
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Wrap a single Aroha capability as an OpenAI function tool.
|
|
96
|
+
* Compatible with AutoGen, Semantic Kernel, and GPT-4 tool calling.
|
|
97
|
+
*/
|
|
98
|
+
export function arohaCapabilityToOpenAITool(
|
|
99
|
+
capabilityId: string,
|
|
100
|
+
opts: ArohaCapabilityToToolOptions
|
|
101
|
+
): OpenAIFunctionLike {
|
|
102
|
+
const langChainTool = arohaCapabilityToLangChainTool(capabilityId, opts);
|
|
103
|
+
return {
|
|
104
|
+
type: "function",
|
|
105
|
+
function: {
|
|
106
|
+
name: langChainTool.name,
|
|
107
|
+
description: langChainTool.description,
|
|
108
|
+
parameters: opts.schema,
|
|
109
|
+
},
|
|
110
|
+
async invoke(args: Record<string, unknown>): Promise<unknown> {
|
|
111
|
+
const result = await langChainTool.call(args);
|
|
112
|
+
try { return JSON.parse(result); } catch { return result; }
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── LangChain tools → Aroha provider ─────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
export interface LangChainAgentToArohaOptions {
|
|
120
|
+
agentDID: string;
|
|
121
|
+
agentPrivateKey: Uint8Array;
|
|
122
|
+
port: number;
|
|
123
|
+
resolvePublicKey: ArohaServerOptions["resolvePublicKey"];
|
|
124
|
+
didDocument: ArohaServerOptions["didDocument"];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Expose a set of LangChain tools as a Aroha provider agent.
|
|
129
|
+
*
|
|
130
|
+
* Each tool becomes a Aroha capability. Inbound ArohaRequest envelopes are
|
|
131
|
+
* routed to the matching tool. The tool's string result is returned as a
|
|
132
|
+
* ArohaResponse.
|
|
133
|
+
*
|
|
134
|
+
* Returns the ArohaServer — call .start() to begin accepting connections.
|
|
135
|
+
*/
|
|
136
|
+
export function langChainAgentToArohaProvider(
|
|
137
|
+
tools: LangChainToolLike[],
|
|
138
|
+
opts: LangChainAgentToArohaOptions
|
|
139
|
+
): ArohaServer {
|
|
140
|
+
const toolMap = new Map(tools.map((t) => [t.name, t]));
|
|
141
|
+
|
|
142
|
+
const onMessage: MessageHandler = async (envelope, respond) => {
|
|
143
|
+
if (envelope.type !== "ArohaRequest") return;
|
|
144
|
+
|
|
145
|
+
const body = envelope.body as ArohaRequestBody;
|
|
146
|
+
const tool = toolMap.get(body.capability);
|
|
147
|
+
|
|
148
|
+
if (!tool) {
|
|
149
|
+
const errEnvelope = await buildEnvelope(
|
|
150
|
+
"ArohaError",
|
|
151
|
+
opts.agentDID,
|
|
152
|
+
envelope.from,
|
|
153
|
+
{
|
|
154
|
+
code: "Aroha_UNKNOWN_CAPABILITY" as never,
|
|
155
|
+
message: `Unknown capability: ${body.capability}`,
|
|
156
|
+
retryable: false,
|
|
157
|
+
},
|
|
158
|
+
envelope.correlationId,
|
|
159
|
+
opts.agentPrivateKey
|
|
160
|
+
);
|
|
161
|
+
respond(errEnvelope);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const rawResult = await tool.call(body.params);
|
|
167
|
+
let result: Record<string, unknown>;
|
|
168
|
+
try { result = JSON.parse(rawResult); } catch { result = { output: rawResult }; }
|
|
169
|
+
|
|
170
|
+
const responseBody: ArohaResponseBody = { capability: body.capability, result };
|
|
171
|
+
const reply = await buildEnvelope(
|
|
172
|
+
"ArohaResponse",
|
|
173
|
+
opts.agentDID,
|
|
174
|
+
envelope.from,
|
|
175
|
+
responseBody,
|
|
176
|
+
envelope.correlationId,
|
|
177
|
+
opts.agentPrivateKey
|
|
178
|
+
);
|
|
179
|
+
respond(reply);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
const errEnvelope = await buildEnvelope(
|
|
182
|
+
"ArohaError",
|
|
183
|
+
opts.agentDID,
|
|
184
|
+
envelope.from,
|
|
185
|
+
{
|
|
186
|
+
code: "Aroha_INTERNAL_ERROR" as never,
|
|
187
|
+
message: String(err),
|
|
188
|
+
retryable: true,
|
|
189
|
+
},
|
|
190
|
+
envelope.correlationId,
|
|
191
|
+
opts.agentPrivateKey
|
|
192
|
+
);
|
|
193
|
+
respond(errEnvelope);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
return new ArohaServer({
|
|
198
|
+
agentDID: opts.agentDID,
|
|
199
|
+
didDocument: opts.didDocument,
|
|
200
|
+
port: opts.port,
|
|
201
|
+
onMessage,
|
|
202
|
+
resolvePublicKey: opts.resolvePublicKey,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ─── Describe Aroha capabilities ──────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Convert a LangChain tool list to Aroha capability descriptors.
|
|
210
|
+
* Use these to register the agent in InMemoryRegistry or ArohaRegistry.
|
|
211
|
+
*/
|
|
212
|
+
export function langChainToolsToCapabilities(
|
|
213
|
+
tools: LangChainToolLike[]
|
|
214
|
+
): ArohaCapabilityDescriptor[] {
|
|
215
|
+
return tools.map((t) => ({
|
|
216
|
+
capabilityId: t.name,
|
|
217
|
+
description: t.description,
|
|
218
|
+
inputSchema: t.schema,
|
|
219
|
+
}));
|
|
220
|
+
}
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duck-typed interfaces for LangChain, AutoGen, and Semantic Kernel tools.
|
|
3
|
+
*
|
|
4
|
+
* These interfaces are intentionally minimal — they match the public API surface
|
|
5
|
+
* that matters for Aroha bridging without requiring a hard dependency on any
|
|
6
|
+
* specific framework version.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ─── LangChain / LangGraph style ─────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export interface LangChainToolLike {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
/** JSON Schema for the tool's input. Optional — used for agent auto-binding. */
|
|
15
|
+
schema?: Record<string, unknown>;
|
|
16
|
+
/** Invoke the tool. Input may be a JSON string or an object. */
|
|
17
|
+
call(input: string | Record<string, unknown>): Promise<string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── OpenAI function / tool style (AutoGen, Semantic Kernel, GPT-4 tools) ────
|
|
21
|
+
|
|
22
|
+
export interface OpenAIFunctionLike {
|
|
23
|
+
type: "function";
|
|
24
|
+
function: {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
parameters?: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
/** Invoke the function. */
|
|
30
|
+
invoke(args: Record<string, unknown>): Promise<unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ─── Aroha capability descriptor (for converting tools → Aroha capabilities) ───
|
|
34
|
+
|
|
35
|
+
export interface ArohaCapabilityDescriptor {
|
|
36
|
+
capabilityId: string;
|
|
37
|
+
description: string;
|
|
38
|
+
inputSchema?: Record<string, unknown>;
|
|
39
|
+
}
|