adaptive-memory-multi-model-router 2.15.3 → 2.15.5

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.
Files changed (101) hide show
  1. package/.github/workflows/adapters-ci.yml +142 -0
  2. package/.github/workflows/ci.yml +2 -5
  3. package/.github/workflows/pypi-publish.yml +102 -0
  4. package/CHANGELOG.md +7 -1
  5. package/README.md +260 -137
  6. package/README_ja.md +2 -2
  7. package/README_zh.md +1 -1
  8. package/adapters/README.md +36 -0
  9. package/adapters/__init__.py +25 -0
  10. package/adapters/a3m_adapter/__init__.py +51 -0
  11. package/adapters/a3m_adapter/adapter/__init__.py +22 -0
  12. package/adapters/a3m_adapter/adapter/autogen.py +169 -0
  13. package/adapters/a3m_adapter/adapter/config.py +100 -0
  14. package/adapters/a3m_adapter/adapter/haystack.py +197 -0
  15. package/adapters/a3m_adapter/adapter/langchain.py +155 -0
  16. package/adapters/a3m_adapter/adapter/langgraph.py +196 -0
  17. package/adapters/a3m_adapter/adapter/llamaindex.py +162 -0
  18. package/adapters/a3m_adapter/adapter/pinecone.py +217 -0
  19. package/adapters/a3m_adapter/adapter/vercel.py +188 -0
  20. package/adapters/a3m_adapter/tests/__init__.py +1 -0
  21. package/adapters/a3m_adapter/tests/test_adapters.py +118 -0
  22. package/adapters/a3m_adapter/tests/test_integration.py +80 -0
  23. package/adapters/requirements-dev.txt +6 -0
  24. package/adapters/requirements.txt +4 -0
  25. package/adapters/setup.py +23 -0
  26. package/apps/cost-calculator/README.md +72 -0
  27. package/apps/cost-calculator/calculator.css +280 -0
  28. package/apps/cost-calculator/calculator.js +150 -0
  29. package/apps/cost-calculator/index.html +321 -0
  30. package/apps/cost-calculator/package.json +13 -0
  31. package/articles/ANNOUNCEMENT_reddit_ml.md +76 -0
  32. package/articles/ANNOUNCEMENT_vc/347/244/276/345/214/272.md +71 -0
  33. package/articles/ANNOUNCEMENT_vercel.md +85 -0
  34. package/demo.py +251 -0
  35. package/dist/providers/providerConfig.d.ts +5 -1
  36. package/dist/providers/providerConfig.js +1006 -1
  37. package/dist/providers/providerConfig.js.map +1 -1
  38. package/docker-compose.yml +84 -60
  39. package/docs/comparison.md +2 -2
  40. package/docs/llms-full.txt +360 -138
  41. package/docs/llms.txt +70 -71
  42. package/docs-site/index.html +9 -9
  43. package/llms.txt +70 -62
  44. package/package.json +41 -91
  45. package/packages/a3m-vercel-ai/README.md +161 -0
  46. package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +12 -0
  47. package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +1 -0
  48. package/packages/a3m-vercel-ai/dist/a3m-language-model.js +289 -0
  49. package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +1 -0
  50. package/packages/a3m-vercel-ai/dist/index.d.ts +82 -0
  51. package/packages/a3m-vercel-ai/dist/index.d.ts.map +1 -0
  52. package/packages/a3m-vercel-ai/dist/index.js +79 -0
  53. package/packages/a3m-vercel-ai/dist/index.js.map +1 -0
  54. package/packages/a3m-vercel-ai/dist/types.d.ts +97 -0
  55. package/packages/a3m-vercel-ai/dist/types.d.ts.map +1 -0
  56. package/packages/a3m-vercel-ai/dist/types.js +5 -0
  57. package/packages/a3m-vercel-ai/dist/types.js.map +1 -0
  58. package/packages/a3m-vercel-ai/package-lock.json +969 -0
  59. package/packages/a3m-vercel-ai/package.json +46 -0
  60. package/packages/a3m-vercel-ai/src/a3m-language-model.ts +381 -0
  61. package/packages/a3m-vercel-ai/src/index.ts +104 -0
  62. package/packages/a3m-vercel-ai/src/types.ts +116 -0
  63. package/packages/a3m-vercel-ai/tsconfig.json +20 -0
  64. package/packages/agentkit-adapter/LICENSE +21 -0
  65. package/packages/agentkit-adapter/README.md +126 -0
  66. package/packages/agentkit-adapter/examples/agentkit-example.ts +139 -0
  67. package/packages/agentkit-adapter/package.json +57 -0
  68. package/packages/agentkit-adapter/src/adapter.ts +381 -0
  69. package/packages/agentkit-adapter/src/index.ts +36 -0
  70. package/packages/agentkit-adapter/src/types.ts +105 -0
  71. package/packages/agentkit-adapter/src/util.ts +13 -0
  72. package/packages/agentkit-adapter/tsconfig.json +22 -0
  73. package/prometheus.yml +8 -0
  74. package/python/README.md +92 -65
  75. package/python/a3m/__init__.py +32 -3
  76. package/python/a3m/adapters/__init__.py +21 -0
  77. package/python/a3m/adapters/langchain.py +190 -0
  78. package/python/a3m/adapters/llamaindex.py +249 -0
  79. package/python/a3m/adapters/qdrant.py +240 -0
  80. package/python/a3m/adapters/weaviate.py +263 -0
  81. package/python/a3m/client.py +5 -0
  82. package/python/a3m_router.egg-info/PKG-INFO +172 -0
  83. package/python/a3m_router.egg-info/SOURCES.txt +17 -0
  84. package/python/a3m_router.egg-info/dependency_links.txt +1 -0
  85. package/python/a3m_router.egg-info/requires.txt +24 -0
  86. package/python/a3m_router.egg-info/top_level.txt +1 -0
  87. package/python/build_verify.sh +32 -0
  88. package/python/dist/a3m_router-2.2.1-py3-none-any.whl +0 -0
  89. package/python/dist/a3m_router-2.2.1.tar.gz +0 -0
  90. package/python/dist/a3m_router-2.2.2-py3-none-any.whl +0 -0
  91. package/python/dist/a3m_router-2.2.2.tar.gz +0 -0
  92. package/python/mcp-server/README.md +172 -0
  93. package/python/mcp-server/a3m_mcp/__init__.py +15 -0
  94. package/python/mcp-server/a3m_mcp/__main__.py +15 -0
  95. package/python/mcp-server/a3m_mcp/server.py +339 -0
  96. package/python/mcp-server/pyproject.toml +24 -0
  97. package/python/pyproject.toml +56 -5
  98. package/python/setup.py +3 -28
  99. package/src/providers/providerConfig.ts +1053 -1
  100. package/summary.txt +38 -0
  101. package/tsconfig.build.json +1 -2
@@ -0,0 +1,126 @@
1
+ # A3M Router AgentKit Adapter
2
+
3
+ AgentKit adapter that routes LLM calls through A3M Router for intelligent, cost-optimized model selection.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @a3m/agentkit-adapter
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { createAgentKitAdapter } from '@a3m/agentkit-adapter';
15
+ import { createAgent, run } from '@stablelib/agentkit';
16
+
17
+ // Create A3M-powered adapter
18
+ const a3mAdapter = createAgentKitAdapter({
19
+ baseUrl: 'http://localhost:8787',
20
+ model: 'auto',
21
+ temperature: 0.7,
22
+ maxTokens: 4096,
23
+ });
24
+
25
+ // Create agent with A3M routing
26
+ const agent = createAgent({
27
+ name: 'a3m-assistant',
28
+ description: 'AI assistant powered by A3M Router',
29
+ llm: a3mAdapter,
30
+ tools: [
31
+ // your tools
32
+ ],
33
+ });
34
+
35
+ // Run the agent
36
+ const result = await run(agent, {
37
+ input: 'Hello, what is 2+2?',
38
+ });
39
+ ```
40
+
41
+ ## API
42
+
43
+ ### `createAgentKitAdapter(config)`
44
+
45
+ Creates an A3M Router adapter for AgentKit.
46
+
47
+ **Config options:**
48
+
49
+ | Option | Type | Default | Description |
50
+ |--------|------|---------|-------------|
51
+ | `baseUrl` | `string` | `'http://localhost:8787'` | A3M Router server URL |
52
+ | `model` | `string` | `'auto'` | Model to use (`'auto'` for intelligent routing) |
53
+ | `temperature` | `number` | `0.7` | Sampling temperature |
54
+ | `maxTokens` | `number` | `4096` | Max tokens to generate |
55
+ | `parallelEnsemble` | `number` | `1` | Number of providers for ensemble |
56
+ | `apiKey` | `string` | — | Optional API key |
57
+ | `systemPrompt` | `string` | — | Optional system prompt |
58
+ | `tools` | `AgentTool[]` | `[]` | Available tools |
59
+
60
+ **Returns:** `A3MAgentKitAdapter` instance
61
+
62
+ ### Adapter Methods
63
+
64
+ #### `chat(messages, tools?)`
65
+
66
+ Send a chat completion request.
67
+
68
+ ```typescript
69
+ const response = await a3mAdapter.chat([
70
+ { role: 'user', content: 'What is AI?' },
71
+ ]);
72
+ ```
73
+
74
+ #### `stream(messages)`
75
+
76
+ Stream a chat completion response.
77
+
78
+ ```typescript
79
+ for await (const chunk of a3mAdapter.stream(messages)) {
80
+ process.stdout.write(chunk.content);
81
+ }
82
+ ```
83
+
84
+ #### `getTools()`
85
+
86
+ Get configured tools for function calling.
87
+
88
+ ```typescript
89
+ const tools = a3mAdapter.getTools();
90
+ ```
91
+
92
+ ## How It Works
93
+
94
+ 1. Incoming requests are forwarded to A3M Router at `baseUrl`
95
+ 2. A3M Router analyzes query complexity and routes to cheapest capable provider
96
+ 3. Response is returned with routing metadata
97
+ 4. Falls back gracefully if A3M Router is unavailable
98
+
99
+ ## Example with Tools
100
+
101
+ ```typescript
102
+ import { createAgentKitAdapter } from '@a3m/agentkit-adapter';
103
+
104
+ const calculatorTool = {
105
+ name: 'calculator',
106
+ description: 'Evaluate a mathematical expression',
107
+ parameters: {
108
+ expression: { type: 'string', description: 'The math expression to evaluate' },
109
+ },
110
+ };
111
+
112
+ const adapter = createAgentKitAdapter({
113
+ baseUrl: 'http://localhost:8787',
114
+ model: 'auto',
115
+ tools: [calculatorTool],
116
+ });
117
+
118
+ const response = await adapter.chat(
119
+ [{ role: 'user', content: 'What is 2+2?' }],
120
+ [calculatorTool]
121
+ );
122
+ ```
123
+
124
+ ## License
125
+
126
+ MIT
@@ -0,0 +1,139 @@
1
+ /**
2
+ * A3M Router AgentKit Adapter - Example Usage
3
+ *
4
+ * Run: npx ts-node examples/agentkit-example.ts
5
+ */
6
+
7
+ import { createAgentKitAdapter, A3MAdapterConfig } from '../src';
8
+
9
+ // Example 1: Basic chat
10
+ async function basicChat() {
11
+ console.log('=== Example 1: Basic Chat ===');
12
+
13
+ const adapter = createAgentKitAdapter({
14
+ baseUrl: 'http://localhost:8787',
15
+ model: 'auto',
16
+ temperature: 0.7,
17
+ });
18
+
19
+ const response = await adapter.chat([
20
+ { role: 'user', content: 'What is 2+2?' },
21
+ ]);
22
+
23
+ console.log('Response:', response.content);
24
+ console.log('Provider:', response.provider);
25
+ console.log('Model:', response.model);
26
+ console.log('Tier:', response.tier);
27
+ console.log();
28
+ }
29
+
30
+ // Example 2: Streaming
31
+ async function streamingChat() {
32
+ console.log('=== Example 2: Streaming ===');
33
+
34
+ const adapter = createAgentKitAdapter({
35
+ baseUrl: 'http://localhost:8787',
36
+ model: 'auto',
37
+ });
38
+
39
+ process.stdout.write('Stream: ');
40
+ for await (const chunk of adapter.stream([
41
+ { role: 'user', content: 'Count to 5' },
42
+ ])) {
43
+ process.stdout.write(chunk.content);
44
+ }
45
+ console.log('\n');
46
+ }
47
+
48
+ // Example 3: With tools
49
+ async function toolChat() {
50
+ console.log('=== Example 3: With Tools ===');
51
+
52
+ const calculatorTool = {
53
+ name: 'calculator',
54
+ description: 'Evaluate a mathematical expression',
55
+ parameters: {
56
+ type: 'object',
57
+ properties: {
58
+ expression: {
59
+ type: 'string',
60
+ description: 'The math expression to evaluate'
61
+ },
62
+ },
63
+ required: ['expression'],
64
+ },
65
+ };
66
+
67
+ const weatherTool = {
68
+ name: 'get_weather',
69
+ description: 'Get current weather for a location',
70
+ parameters: {
71
+ type: 'object',
72
+ properties: {
73
+ location: {
74
+ type: 'string',
75
+ description: 'City name'
76
+ },
77
+ },
78
+ required: ['location'],
79
+ },
80
+ };
81
+
82
+ const adapter = createAgentKitAdapter({
83
+ baseUrl: 'http://localhost:8787',
84
+ model: 'auto',
85
+ temperature: 0.7,
86
+ tools: [calculatorTool, weatherTool],
87
+ });
88
+
89
+ const response = await adapter.chat(
90
+ [
91
+ {
92
+ role: 'user',
93
+ content: 'What is the weather in San Francisco and what is 50 * 23?'
94
+ },
95
+ ],
96
+ [calculatorTool, weatherTool]
97
+ );
98
+
99
+ console.log('Response:', response.content);
100
+ console.log('Provider:', response.provider);
101
+ console.log('Tool Calls:', response.toolCalls);
102
+ console.log();
103
+ }
104
+
105
+ // Example 4: Parallel ensemble
106
+ async function ensembleChat() {
107
+ console.log('=== Example 4: Parallel Ensemble ===');
108
+
109
+ const adapter = createAgentKitAdapter({
110
+ baseUrl: 'http://localhost:8787',
111
+ model: 'auto',
112
+ parallelEnsemble: 3, // Call 3 providers, pick best
113
+ });
114
+
115
+ const response = await adapter.chat([
116
+ { role: 'user', content: 'Explain quantum entanglement in one sentence' },
117
+ ]);
118
+
119
+ console.log('Best provider:', response.provider);
120
+ console.log('Response:', response.content);
121
+ console.log('All candidates:', response.candidates);
122
+ console.log();
123
+ }
124
+
125
+ // Main
126
+ async function main() {
127
+ try {
128
+ await basicChat();
129
+ await streamingChat();
130
+ await toolChat();
131
+ await ensembleChat();
132
+ console.log('All examples completed!');
133
+ } catch (error) {
134
+ console.error('Error:', error);
135
+ console.log('\nMake sure A3M Router is running: npx a3m-router serve');
136
+ }
137
+ }
138
+
139
+ main();
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@a3m/agentkit-adapter",
3
+ "version": "1.0.0",
4
+ "description": "A3M Router adapter for AgentKit - enables AI agents with intelligent multi-provider routing",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "module": "dist/index.mjs",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
20
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
21
+ "test": "vitest",
22
+ "lint": "eslint src/",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "agentkit",
27
+ "a3m",
28
+ "router",
29
+ "llm",
30
+ "multi-provider",
31
+ "ai",
32
+ "agent",
33
+ "openai",
34
+ "anthropic",
35
+ "routing"
36
+ ],
37
+ "author": "",
38
+ "license": "MIT",
39
+ "peerDependencies": {
40
+ "@inngest/agent-kit": ">=0.0.1"
41
+ },
42
+ "dependencies": {
43
+ "@inngest/ai": ">=0.1.0",
44
+ "zod": "^3.22.4"
45
+ },
46
+ "devDependencies": {
47
+ "@inngest/agent-kit": "^0.13.0",
48
+ "@types/node": "^20.10.0",
49
+ "tsup": "^8.0.0",
50
+ "typescript": "^5.3.0",
51
+ "vitest": "^1.0.0"
52
+ },
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "https://github.com/Das-rebel/adaptive-memory-multi-model-router"
56
+ }
57
+ }
@@ -0,0 +1,381 @@
1
+ /**
2
+ * A3M Router AgentKit Adapter
3
+ *
4
+ * Wraps A3M Router as an AgentKit-compatible LLM backend.
5
+ * Supports tool calling, streaming, and parallel ensemble mode.
6
+ */
7
+
8
+ import { type AiAdapter } from "@inngest/ai";
9
+ import { z } from "zod";
10
+ import { stringifyError } from "./util";
11
+ import type {
12
+ A3MConfig,
13
+ A3MInferenceOptions,
14
+ A3MMessage,
15
+ A3MTool,
16
+ A3MStreamChunk,
17
+ A3MToolCall,
18
+ } from "./types";
19
+
20
+ /**
21
+ * A3M Router adapter for AgentKit
22
+ */
23
+ export class A3MAdapter {
24
+ private config: Required<A3MConfig>;
25
+ private format: AiAdapter.Format = "openai-chat";
26
+ private url: string;
27
+ private authKey: string = "";
28
+
29
+ constructor(config: A3MConfig = {}) {
30
+ this.config = {
31
+ baseUrl: config.baseUrl || "http://localhost:8787",
32
+ apiKey: config.apiKey || "",
33
+ defaultModel: config.defaultModel || "",
34
+ parallel: config.parallel ?? false,
35
+ temperature: config.temperature ?? 0.7,
36
+ maxTokens: config.maxTokens ?? 4096,
37
+ headers: config.headers || {},
38
+ };
39
+ this.url = `${this.config.baseUrl}/v1/chat/completions`;
40
+ this.authKey = this.config.apiKey;
41
+ }
42
+
43
+ /**
44
+ * Get the adapter format
45
+ */
46
+ getFormat(): AiAdapter.Format {
47
+ return this.format;
48
+ }
49
+
50
+ /**
51
+ * Get adapter options (required by AgentKit)
52
+ */
53
+ getOptions(): AiAdapter.Any["options"] {
54
+ return {
55
+ model: this.config.defaultModel || "auto",
56
+ apiKey: this.config.apiKey,
57
+ baseUrl: this.config.baseUrl,
58
+ } as AiAdapter.Any["options"];
59
+ }
60
+
61
+ /**
62
+ * Convert AgentKit messages to A3M format
63
+ */
64
+ private convertMessages(messages: Array<{
65
+ type: string;
66
+ role?: string;
67
+ content?: string;
68
+ tool?: { id?: string; name?: string };
69
+ tools?: unknown[];
70
+ }>): A3MMessage[] {
71
+ return messages.map((m) => {
72
+ if (m.type === "tool_result") {
73
+ return {
74
+ role: "tool" as const,
75
+ content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
76
+ toolCallId: m.tool?.id,
77
+ };
78
+ }
79
+ if (m.type === "tool_call") {
80
+ // Tool calls are handled separately
81
+ return {
82
+ role: "assistant" as const,
83
+ content: "",
84
+ };
85
+ }
86
+ return {
87
+ role: (m.role as "user" | "assistant" | "system") || "user",
88
+ content: m.content || "",
89
+ };
90
+ }).filter((m) => m.content || m.role === "system");
91
+ }
92
+
93
+ /**
94
+ * Convert AgentKit tools to A3M/OpenAI format
95
+ */
96
+ private convertTools(tools: Array<{
97
+ name: string;
98
+ description?: string;
99
+ parameters?: z.ZodType<unknown>;
100
+ strict?: boolean;
101
+ }>) {
102
+ return tools.map((t) => ({
103
+ type: "function",
104
+ function: {
105
+ name: t.name,
106
+ description: t.description || "",
107
+ parameters: t.parameters ? z.toJSONSchema(t.parameters, { target: "draft-7" }) : undefined,
108
+ strict: t.strict ?? Boolean(t.parameters),
109
+ },
110
+ }));
111
+ }
112
+
113
+ /**
114
+ * Parse A3M/OpenAI response to AgentKit messages
115
+ */
116
+ private parseResponse(input: Record<string, unknown>): Array<{
117
+ type: string;
118
+ role?: string;
119
+ content?: string;
120
+ tools?: A3MToolCall[];
121
+ stop_reason?: string;
122
+ }> {
123
+ const result: Array<{
124
+ type: string;
125
+ role?: string;
126
+ content?: string;
127
+ tools?: A3MToolCall[];
128
+ stop_reason?: string;
129
+ }> = [];
130
+
131
+ if (input.error) {
132
+ throw new Error(`A3M request failed: ${JSON.stringify(input.error)}`);
133
+ }
134
+
135
+ const choices = (input.choices as Array<Record<string, unknown>>) || [];
136
+
137
+ for (const choice of choices) {
138
+ const message = choice.message as Record<string, unknown> || {};
139
+ const finishReason = choice.finish_reason as string || "stop";
140
+
141
+ // Text content
142
+ if (message.content && (message.content as string).trim()) {
143
+ result.push({
144
+ type: "text",
145
+ role: message.role as string,
146
+ content: message.content as string,
147
+ stop_reason: finishReason,
148
+ });
149
+ }
150
+
151
+ // Tool calls
152
+ const toolCalls = message.tool_calls as Array<Record<string, unknown>> || [];
153
+ if (toolCalls.length > 0) {
154
+ result.push({
155
+ type: "tool_call",
156
+ role: message.role as string,
157
+ tools: toolCalls.map((tc) => ({
158
+ id: tc.id as string,
159
+ name: (tc.function as Record<string, string>)?.name || "",
160
+ arguments: JSON.parse((tc.function as Record<string, string>)?.arguments || "{}"),
161
+ })),
162
+ stop_reason: "tool",
163
+ });
164
+ }
165
+ }
166
+
167
+ return result;
168
+ }
169
+
170
+ /**
171
+ * Run inference through A3M Router
172
+ */
173
+ async infer(
174
+ stepID: string,
175
+ messages: Array<{
176
+ type: string;
177
+ role?: string;
178
+ content?: string;
179
+ tool?: { id?: string; name?: string };
180
+ tools?: unknown[];
181
+ }>,
182
+ tools: Array<{
183
+ name: string;
184
+ description?: string;
185
+ parameters?: z.ZodType<unknown>;
186
+ strict?: boolean;
187
+ }>,
188
+ toolChoice: "auto" | "none" | { type: "function"; function: { name: string } } = "auto",
189
+ options: A3MInferenceOptions = {}
190
+ ): Promise<{
191
+ output: Array<{ type: string; role?: string; content?: string; tools?: A3MToolCall[]; stop_reason?: string }>;
192
+ raw: Record<string, unknown>;
193
+ }> {
194
+ const body: Record<string, unknown> = {
195
+ model: options.model || this.config.defaultModel || "auto",
196
+ messages: this.convertMessages(messages),
197
+ temperature: options.temperature ?? this.config.temperature,
198
+ max_tokens: options.maxTokens ?? this.config.maxTokens,
199
+ };
200
+
201
+ if (tools.length > 0) {
202
+ body.tools = this.convertTools(tools);
203
+ if (toolChoice === "auto") {
204
+ body.tool_choice = "auto";
205
+ } else if (toolChoice === "none") {
206
+ body.tool_choice = "none";
207
+ } else if (toolChoice && typeof toolChoice === "object") {
208
+ body.tool_choice = {
209
+ type: "function",
210
+ function: { name: toolChoice.function.name },
211
+ };
212
+ }
213
+ }
214
+
215
+ if (options.provider) {
216
+ body.provider = options.provider;
217
+ }
218
+
219
+ if (options.stop) {
220
+ body.stop = options.stop;
221
+ }
222
+
223
+ // Parallel ensemble mode
224
+ if (options.parallel ?? this.config.parallel) {
225
+ body.parallel = true;
226
+ }
227
+
228
+ const headers: Record<string, string> = {
229
+ "Content-Type": "application/json",
230
+ ...this.config.headers,
231
+ };
232
+
233
+ if (this.authKey) {
234
+ headers["Authorization"] = `Bearer ${this.authKey}`;
235
+ }
236
+
237
+ try {
238
+ const response = await fetch(this.url, {
239
+ method: "POST",
240
+ headers,
241
+ body: JSON.stringify(body),
242
+ });
243
+
244
+ if (!response.ok) {
245
+ throw new Error(`A3M request failed: ${response.status} ${response.statusText}`);
246
+ }
247
+
248
+ const result = (await response.json()) as Record<string, unknown>;
249
+ return {
250
+ output: this.parseResponse(result),
251
+ raw: result,
252
+ };
253
+ } catch (err) {
254
+ throw new Error(`A3M inference error: ${stringifyError(err)}`);
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Stream inference through A3M Router
260
+ */
261
+ async *stream(
262
+ messages: A3MMessage[],
263
+ tools: A3MTool[] = [],
264
+ options: A3MInferenceOptions = {}
265
+ ): AsyncGenerator<A3MStreamChunk> {
266
+ const body: Record<string, unknown> = {
267
+ model: options.model || this.config.defaultModel || "auto",
268
+ messages,
269
+ temperature: options.temperature ?? this.config.temperature,
270
+ max_tokens: options.maxTokens ?? this.config.maxTokens,
271
+ stream: true,
272
+ };
273
+
274
+ if (tools.length > 0) {
275
+ body.tools = this.convertTools(tools);
276
+ body.tool_choice = "auto";
277
+ }
278
+
279
+ if (options.provider) {
280
+ body.provider = options.provider;
281
+ }
282
+
283
+ const headers: Record<string, string> = {
284
+ "Content-Type": "application/json",
285
+ ...this.config.headers,
286
+ };
287
+
288
+ if (this.authKey) {
289
+ headers["Authorization"] = `Bearer ${this.authKey}`;
290
+ }
291
+
292
+ try {
293
+ const response = await fetch(this.url, {
294
+ method: "POST",
295
+ headers,
296
+ body: JSON.stringify(body),
297
+ });
298
+
299
+ if (!response.ok) {
300
+ throw new Error(`A3M stream failed: ${response.status} ${response.statusText}`);
301
+ }
302
+
303
+ if (!response.body) {
304
+ throw new Error("A3M stream: no response body");
305
+ }
306
+
307
+ const reader = response.body.getReader();
308
+ const decoder = new TextDecoder();
309
+ let buffer = "";
310
+
311
+ while (true) {
312
+ const { done, value } = await reader.read();
313
+ if (done) break;
314
+
315
+ buffer += decoder.decode(value, { stream: true });
316
+ const lines = buffer.split("\n");
317
+ buffer = lines.pop() || "";
318
+
319
+ for (const line of lines) {
320
+ const trimmed = line.trim();
321
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
322
+
323
+ const data = trimmed.slice(6);
324
+ if (data === "[DONE]") {
325
+ yield { type: "done" };
326
+ return;
327
+ }
328
+
329
+ try {
330
+ const chunk = JSON.parse(data) as Record<string, unknown>;
331
+ const delta = chunk.choices?.[0]?.delta as Record<string, unknown>;
332
+
333
+ if (delta?.content) {
334
+ yield { type: "text", content: delta.content as string };
335
+ }
336
+
337
+ if (delta?.tool_calls) {
338
+ for (const tc of delta.tool_calls as Array<Record<string, unknown>>) {
339
+ yield {
340
+ type: "tool_call",
341
+ toolCall: {
342
+ id: tc.id as string,
343
+ name: (tc.function as Record<string, string>)?.name || "",
344
+ arguments: JSON.parse((tc.function as Record<string, string>)?.arguments || "{}"),
345
+ },
346
+ };
347
+ }
348
+ }
349
+ } catch {
350
+ // Skip malformed JSON
351
+ }
352
+ }
353
+ }
354
+
355
+ yield { type: "done" };
356
+ } catch (err) {
357
+ yield { type: "error", error: stringifyError(err) };
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Check if A3M Router is available
363
+ */
364
+ async healthCheck(): Promise<boolean> {
365
+ try {
366
+ const response = await fetch(`${this.config.baseUrl}/health`, {
367
+ method: "GET",
368
+ });
369
+ return response.ok;
370
+ } catch {
371
+ return false;
372
+ }
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Create an A3M adapter with default settings
378
+ */
379
+ export function createA3MAdapter(config?: A3MConfig): A3MAdapter {
380
+ return new A3MAdapter(config);
381
+ }