@axiom-lattice/gateway 2.1.11 → 2.1.13
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +16 -0
- package/README.md +53 -0
- package/dist/index.js +798 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +794 -64
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/config.ts +124 -0
- package/src/controllers/assistant.ts +274 -3
- package/src/controllers/config.ts +126 -0
- package/src/controllers/models.ts +152 -0
- package/src/controllers/threads.ts +189 -0
- package/src/routes/index.ts +80 -1
- package/src/schemas/index.ts +74 -0
- package/src/services/supabase.ts +44 -10
- package/src/stores/assistant_store.ts +82 -0
- package/src/stores/thread_store.ts +115 -0
- package/src/types/index.ts +14 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration service
|
|
3
|
+
* Manages environment variables and supports dynamic updates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface GatewayConfig {
|
|
7
|
+
port?: number;
|
|
8
|
+
queueServiceType?: string;
|
|
9
|
+
redisUrl?: string;
|
|
10
|
+
redisPassword?: string;
|
|
11
|
+
queueName?: string;
|
|
12
|
+
[key: string]: any; // Allow additional config keys
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get configuration from environment variables
|
|
17
|
+
* Supports dynamic updates via updateConfig method
|
|
18
|
+
*/
|
|
19
|
+
class ConfigService {
|
|
20
|
+
private config: GatewayConfig;
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
this.config = this.loadFromEnv();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Load configuration from environment variables
|
|
28
|
+
*/
|
|
29
|
+
private loadFromEnv(): GatewayConfig {
|
|
30
|
+
return {
|
|
31
|
+
port: process.env.PORT ? Number(process.env.PORT) : undefined,
|
|
32
|
+
queueServiceType: process.env.QUEUE_SERVICE_TYPE,
|
|
33
|
+
redisUrl: process.env.REDIS_URL,
|
|
34
|
+
redisPassword: process.env.REDIS_PASSWORD,
|
|
35
|
+
queueName: process.env.QUEUE_NAME,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Update configuration from JSON object
|
|
41
|
+
* This will update both the internal config and process.env
|
|
42
|
+
*/
|
|
43
|
+
updateConfig(jsonConfig: Record<string, any>): void {
|
|
44
|
+
// Update process.env for all provided keys
|
|
45
|
+
for (const [key, value] of Object.entries(jsonConfig)) {
|
|
46
|
+
if (value !== null && value !== undefined) {
|
|
47
|
+
// Convert nested objects to environment variable format
|
|
48
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
49
|
+
// Handle nested objects like supabase: { url: "...", key: "..." }
|
|
50
|
+
for (const [nestedKey, nestedValue] of Object.entries(value)) {
|
|
51
|
+
const envKey = `${key.toUpperCase()}_${nestedKey.toUpperCase()}`;
|
|
52
|
+
process.env[envKey] = String(nestedValue);
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// Handle flat keys
|
|
56
|
+
process.env[key.toUpperCase()] = String(value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Reload config from updated environment variables
|
|
62
|
+
this.config = this.loadFromEnv();
|
|
63
|
+
|
|
64
|
+
// Deep merge the JSON config into our config object
|
|
65
|
+
this.config = this.deepMerge(this.config, jsonConfig);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Deep merge two objects
|
|
70
|
+
*/
|
|
71
|
+
private deepMerge(target: any, source: any): any {
|
|
72
|
+
const output = { ...target };
|
|
73
|
+
if (this.isObject(target) && this.isObject(source)) {
|
|
74
|
+
Object.keys(source).forEach((key) => {
|
|
75
|
+
if (this.isObject(source[key])) {
|
|
76
|
+
if (!(key in target)) {
|
|
77
|
+
Object.assign(output, { [key]: source[key] });
|
|
78
|
+
} else {
|
|
79
|
+
output[key] = this.deepMerge(target[key], source[key]);
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
Object.assign(output, { [key]: source[key] });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return output;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if value is a plain object
|
|
91
|
+
*/
|
|
92
|
+
private isObject(item: any): boolean {
|
|
93
|
+
return item && typeof item === "object" && !Array.isArray(item);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get current configuration
|
|
98
|
+
*/
|
|
99
|
+
getConfig(): GatewayConfig {
|
|
100
|
+
return { ...this.config };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Export singleton instance
|
|
105
|
+
export const configService = new ConfigService();
|
|
106
|
+
|
|
107
|
+
// Export config getter for backward compatibility
|
|
108
|
+
export const config = {
|
|
109
|
+
get port() {
|
|
110
|
+
return configService.getConfig().port;
|
|
111
|
+
},
|
|
112
|
+
get queueServiceType() {
|
|
113
|
+
return configService.getConfig().queueServiceType;
|
|
114
|
+
},
|
|
115
|
+
get redisUrl() {
|
|
116
|
+
return configService.getConfig().redisUrl;
|
|
117
|
+
},
|
|
118
|
+
get redisPassword() {
|
|
119
|
+
return configService.getConfig().redisPassword;
|
|
120
|
+
},
|
|
121
|
+
get queueName() {
|
|
122
|
+
return configService.getConfig().queueName;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
@@ -1,6 +1,277 @@
|
|
|
1
1
|
import { FastifyRequest, FastifyReply } from "fastify";
|
|
2
2
|
import { draw_graph } from "../services/agent_service";
|
|
3
|
+
import { assistantStore } from "../stores/assistant_store";
|
|
4
|
+
import { Assistant, CreateAssistantRequest } from "../types";
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
import { AgentConfig, getAllAgentConfigs } from "@axiom-lattice/core";
|
|
3
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Assistant Controller
|
|
10
|
+
* Handles assistant-related CRUD operations
|
|
11
|
+
* Merges code-configured agents (from @axiom-lattice/core) with in-memory stored assistants
|
|
12
|
+
* GET operations return both code-configured and stored assistants
|
|
13
|
+
* CUD operations only work on stored assistants
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Convert AgentConfig to Assistant format
|
|
18
|
+
*/
|
|
19
|
+
function convertAgentConfigToAssistant(config: AgentConfig): Assistant {
|
|
20
|
+
return {
|
|
21
|
+
id: config.key,
|
|
22
|
+
name: config.name,
|
|
23
|
+
description: config.description,
|
|
24
|
+
graphDefinition: config, // Store the full config as graphDefinition
|
|
25
|
+
createdAt: new Date(0), // Code-configured agents have no creation date
|
|
26
|
+
updatedAt: new Date(0), // Code-configured agents have no update date
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Assistant list response interface
|
|
32
|
+
*/
|
|
33
|
+
interface AssistantListResponse {
|
|
34
|
+
success: boolean;
|
|
35
|
+
message: string;
|
|
36
|
+
data: {
|
|
37
|
+
records: Assistant[];
|
|
38
|
+
total: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Assistant response interface
|
|
44
|
+
*/
|
|
45
|
+
interface AssistantResponse {
|
|
46
|
+
success: boolean;
|
|
47
|
+
message: string;
|
|
48
|
+
data?: Assistant;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Assistant update request body interface
|
|
53
|
+
*/
|
|
54
|
+
interface AssistantUpdateBody {
|
|
55
|
+
name?: string;
|
|
56
|
+
description?: string;
|
|
57
|
+
graphDefinition?: any;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get list of all assistants
|
|
62
|
+
* Merges code-configured agents with stored assistants
|
|
63
|
+
*/
|
|
64
|
+
export async function getAssistantList(
|
|
65
|
+
request: FastifyRequest,
|
|
66
|
+
reply: FastifyReply
|
|
67
|
+
): Promise<AssistantListResponse> {
|
|
68
|
+
// Get code-configured agents
|
|
69
|
+
const agentConfigs = await getAllAgentConfigs();
|
|
70
|
+
const codeConfiguredAssistants = agentConfigs.map(
|
|
71
|
+
convertAgentConfigToAssistant
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Get stored assistants
|
|
75
|
+
const storedAssistants = assistantStore.getAllAssistants();
|
|
76
|
+
|
|
77
|
+
// Merge both sources, stored assistants take precedence if ID conflicts
|
|
78
|
+
const assistantMap = new Map<string, Assistant>();
|
|
79
|
+
|
|
80
|
+
// First add code-configured agents
|
|
81
|
+
codeConfiguredAssistants.forEach((assistant) => {
|
|
82
|
+
assistantMap.set(assistant.id, assistant);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Then add stored assistants (overwrite if ID exists)
|
|
86
|
+
storedAssistants.forEach((assistant) => {
|
|
87
|
+
assistantMap.set(assistant.id, assistant);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const allAssistants = Array.from(assistantMap.values());
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
success: true,
|
|
94
|
+
message: "Successfully retrieved assistant list",
|
|
95
|
+
data: {
|
|
96
|
+
records: allAssistants,
|
|
97
|
+
total: allAssistants.length,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get a single assistant by ID
|
|
104
|
+
* Checks both code-configured agents and stored assistants
|
|
105
|
+
*/
|
|
106
|
+
export async function getAssistant(
|
|
107
|
+
request: FastifyRequest<{ Params: { id: string } }>,
|
|
108
|
+
reply: FastifyReply
|
|
109
|
+
): Promise<AssistantResponse> {
|
|
110
|
+
const { id } = request.params;
|
|
111
|
+
|
|
112
|
+
// First check stored assistants
|
|
113
|
+
let assistant = assistantStore.getAssistantById(id);
|
|
114
|
+
|
|
115
|
+
// If not found in store, check code-configured agents
|
|
116
|
+
if (!assistant) {
|
|
117
|
+
const agentConfigs = await getAllAgentConfigs();
|
|
118
|
+
const agentConfig = agentConfigs.find((config) => config.key === id);
|
|
119
|
+
if (agentConfig) {
|
|
120
|
+
assistant = convertAgentConfigToAssistant(agentConfig);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!assistant) {
|
|
125
|
+
return reply.status(404).send({
|
|
126
|
+
success: false,
|
|
127
|
+
message: "Assistant not found",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
success: true,
|
|
133
|
+
message: "Successfully retrieved assistant",
|
|
134
|
+
data: assistant,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create a new assistant
|
|
140
|
+
*/
|
|
141
|
+
export async function createAssistant(
|
|
142
|
+
request: FastifyRequest<{ Body: CreateAssistantRequest }>,
|
|
143
|
+
reply: FastifyReply
|
|
144
|
+
): Promise<AssistantResponse> {
|
|
145
|
+
const data = request.body;
|
|
146
|
+
|
|
147
|
+
// Validate required fields
|
|
148
|
+
if (!data.name) {
|
|
149
|
+
return reply.status(400).send({
|
|
150
|
+
success: false,
|
|
151
|
+
message: "name is required",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!data.graphDefinition) {
|
|
156
|
+
return reply.status(400).send({
|
|
157
|
+
success: false,
|
|
158
|
+
message: "graphDefinition is required",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Generate ID if not provided
|
|
163
|
+
const id = randomUUID();
|
|
164
|
+
|
|
165
|
+
// Create assistant
|
|
166
|
+
const newAssistant = assistantStore.createAssistant(id, data);
|
|
167
|
+
|
|
168
|
+
return reply.status(201).send({
|
|
169
|
+
success: true,
|
|
170
|
+
message: "Successfully created assistant",
|
|
171
|
+
data: newAssistant,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Update an existing assistant by ID
|
|
177
|
+
* Only works on stored assistants, not code-configured ones
|
|
178
|
+
*/
|
|
179
|
+
export async function updateAssistant(
|
|
180
|
+
request: FastifyRequest<{
|
|
181
|
+
Params: { id: string };
|
|
182
|
+
Body: AssistantUpdateBody;
|
|
183
|
+
}>,
|
|
184
|
+
reply: FastifyReply
|
|
185
|
+
): Promise<AssistantResponse> {
|
|
186
|
+
const { id } = request.params;
|
|
187
|
+
const updates = request.body;
|
|
188
|
+
|
|
189
|
+
// Check if it's a code-configured agent
|
|
190
|
+
const agentConfigs = await getAllAgentConfigs();
|
|
191
|
+
const isCodeConfigured = agentConfigs.some((config) => config.key === id);
|
|
192
|
+
|
|
193
|
+
if (isCodeConfigured) {
|
|
194
|
+
return reply.status(403).send({
|
|
195
|
+
success: false,
|
|
196
|
+
message:
|
|
197
|
+
"Cannot update code-configured assistant. Only stored assistants can be updated.",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check if assistant exists in store
|
|
202
|
+
if (!assistantStore.hasAssistant(id)) {
|
|
203
|
+
return reply.status(404).send({
|
|
204
|
+
success: false,
|
|
205
|
+
message: "Assistant not found",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Update assistant
|
|
210
|
+
const updatedAssistant = assistantStore.updateAssistant(id, updates);
|
|
211
|
+
|
|
212
|
+
if (!updatedAssistant) {
|
|
213
|
+
return reply.status(500).send({
|
|
214
|
+
success: false,
|
|
215
|
+
message: "Failed to update assistant",
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
success: true,
|
|
221
|
+
message: "Successfully updated assistant",
|
|
222
|
+
data: updatedAssistant,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Delete an assistant by ID
|
|
228
|
+
* Only works on stored assistants, not code-configured ones
|
|
229
|
+
*/
|
|
230
|
+
export async function deleteAssistant(
|
|
231
|
+
request: FastifyRequest<{ Params: { id: string } }>,
|
|
232
|
+
reply: FastifyReply
|
|
233
|
+
): Promise<{ success: boolean; message: string }> {
|
|
234
|
+
const { id } = request.params;
|
|
235
|
+
|
|
236
|
+
// Check if it's a code-configured agent
|
|
237
|
+
const agentConfigs = await getAllAgentConfigs();
|
|
238
|
+
const isCodeConfigured = agentConfigs.some((config) => config.key === id);
|
|
239
|
+
|
|
240
|
+
if (isCodeConfigured) {
|
|
241
|
+
return reply.status(403).send({
|
|
242
|
+
success: false,
|
|
243
|
+
message:
|
|
244
|
+
"Cannot delete code-configured assistant. Only stored assistants can be deleted.",
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check if assistant exists in store
|
|
249
|
+
if (!assistantStore.hasAssistant(id)) {
|
|
250
|
+
return reply.status(404).send({
|
|
251
|
+
success: false,
|
|
252
|
+
message: "Assistant not found",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Delete the assistant
|
|
257
|
+
const deleted = assistantStore.deleteAssistant(id);
|
|
258
|
+
|
|
259
|
+
if (!deleted) {
|
|
260
|
+
return reply.status(500).send({
|
|
261
|
+
success: false,
|
|
262
|
+
message: "Failed to delete assistant",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
success: true,
|
|
268
|
+
message: "Successfully deleted assistant",
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Get agent graph visualization
|
|
274
|
+
*/
|
|
4
275
|
export const getAgentGraph = async (
|
|
5
276
|
request: FastifyRequest<{
|
|
6
277
|
Params: { assistantId: string };
|
|
@@ -10,17 +281,17 @@ export const getAgentGraph = async (
|
|
|
10
281
|
try {
|
|
11
282
|
const { assistantId } = request.params;
|
|
12
283
|
|
|
13
|
-
//
|
|
284
|
+
// Call drawing service to get image data
|
|
14
285
|
const imageData = await draw_graph(assistantId);
|
|
15
286
|
|
|
16
|
-
//
|
|
287
|
+
// Set response header and return image data
|
|
17
288
|
reply.header("Content-Type", "application/json").send({
|
|
18
289
|
image: imageData,
|
|
19
290
|
});
|
|
20
291
|
} catch (error: any) {
|
|
21
292
|
reply.status(500).send({
|
|
22
293
|
success: false,
|
|
23
|
-
error: error.message || "
|
|
294
|
+
error: error.message || "Failed to get agent graph",
|
|
24
295
|
});
|
|
25
296
|
}
|
|
26
297
|
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
+
import { configService } from "../config";
|
|
3
|
+
import {
|
|
4
|
+
setQueueServiceType,
|
|
5
|
+
QueueServiceType,
|
|
6
|
+
} from "../services/queue_service";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Configuration Controller
|
|
10
|
+
* Handles configuration updates from frontend
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface UpdateConfigRequest {
|
|
14
|
+
Body: {
|
|
15
|
+
config: Record<string, any>;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Update gateway configuration
|
|
21
|
+
* Accepts JSON config and loads it into environment variables
|
|
22
|
+
*/
|
|
23
|
+
export async function updateConfig(
|
|
24
|
+
request: FastifyRequest<UpdateConfigRequest>,
|
|
25
|
+
reply: FastifyReply
|
|
26
|
+
) {
|
|
27
|
+
try {
|
|
28
|
+
const { config: jsonConfig } = request.body;
|
|
29
|
+
|
|
30
|
+
if (!jsonConfig || typeof jsonConfig !== "object") {
|
|
31
|
+
return reply.status(400).send({
|
|
32
|
+
success: false,
|
|
33
|
+
error: "Invalid configuration: config must be an object",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Update configuration service
|
|
38
|
+
configService.updateConfig(jsonConfig);
|
|
39
|
+
|
|
40
|
+
const warnings: string[] = [];
|
|
41
|
+
const requiresRestart: string[] = [];
|
|
42
|
+
|
|
43
|
+
// Check if port is being changed (requires restart)
|
|
44
|
+
if (jsonConfig.port !== undefined) {
|
|
45
|
+
requiresRestart.push("PORT");
|
|
46
|
+
warnings.push("Port change requires server restart to take effect");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If queue service type is being updated, reconfigure the queue service
|
|
50
|
+
if (jsonConfig.queueServiceType) {
|
|
51
|
+
setQueueServiceType(jsonConfig.queueServiceType as QueueServiceType);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// If Redis configuration is being updated and queue service is Redis, reconfigure
|
|
55
|
+
if (
|
|
56
|
+
(jsonConfig.redisUrl || jsonConfig.redisPassword) &&
|
|
57
|
+
(process.env.QUEUE_SERVICE_TYPE === "redis" ||
|
|
58
|
+
jsonConfig.queueServiceType === "redis")
|
|
59
|
+
) {
|
|
60
|
+
// Reconfigure queue service to pick up new Redis settings
|
|
61
|
+
const currentType =
|
|
62
|
+
(jsonConfig.queueServiceType as QueueServiceType) ||
|
|
63
|
+
(process.env.QUEUE_SERVICE_TYPE as QueueServiceType) ||
|
|
64
|
+
"memory";
|
|
65
|
+
if (currentType === "redis") {
|
|
66
|
+
setQueueServiceType("redis");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get updated config (without sensitive data)
|
|
71
|
+
const updatedConfig = configService.getConfig();
|
|
72
|
+
const safeConfig = {
|
|
73
|
+
...updatedConfig,
|
|
74
|
+
redisPassword: updatedConfig.redisPassword
|
|
75
|
+
? "***"
|
|
76
|
+
: updatedConfig.redisPassword,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return reply.send({
|
|
80
|
+
success: true,
|
|
81
|
+
message: "Configuration updated successfully",
|
|
82
|
+
data: safeConfig,
|
|
83
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
84
|
+
requiresRestart: requiresRestart.length > 0 ? requiresRestart : undefined,
|
|
85
|
+
});
|
|
86
|
+
} catch (error: any) {
|
|
87
|
+
console.error("Failed to update configuration", {
|
|
88
|
+
error: error.message,
|
|
89
|
+
stack: error.stack,
|
|
90
|
+
});
|
|
91
|
+
return reply.status(500).send({
|
|
92
|
+
success: false,
|
|
93
|
+
error: error.message || "Failed to update configuration",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get current gateway configuration
|
|
100
|
+
* Returns current configuration (with sensitive data masked)
|
|
101
|
+
*/
|
|
102
|
+
export async function getConfig(request: FastifyRequest, reply: FastifyReply) {
|
|
103
|
+
try {
|
|
104
|
+
const currentConfig = configService.getConfig();
|
|
105
|
+
const safeConfig = {
|
|
106
|
+
...currentConfig,
|
|
107
|
+
redisPassword: currentConfig.redisPassword
|
|
108
|
+
? "***"
|
|
109
|
+
: currentConfig.redisPassword,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return reply.send({
|
|
113
|
+
success: true,
|
|
114
|
+
data: safeConfig,
|
|
115
|
+
});
|
|
116
|
+
} catch (error: any) {
|
|
117
|
+
console.error("Failed to get configuration", {
|
|
118
|
+
error: error.message,
|
|
119
|
+
stack: error.stack,
|
|
120
|
+
});
|
|
121
|
+
return reply.status(500).send({
|
|
122
|
+
success: false,
|
|
123
|
+
error: error.message || "Failed to get configuration",
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { FastifyRequest, FastifyReply } from "fastify";
|
|
2
|
+
import { registerModelLattice, modelLatticeManager } from "@axiom-lattice/core";
|
|
3
|
+
import type { LLMConfig } from "@axiom-lattice/protocols";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Models Controller
|
|
7
|
+
* Handles model lattice registration and management
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
interface ModelConfig {
|
|
11
|
+
key: string;
|
|
12
|
+
model: string;
|
|
13
|
+
provider: "azure" | "openai" | "deepseek" | "siliconcloud" | "volcengine";
|
|
14
|
+
streaming?: boolean;
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
baseURL?: string;
|
|
17
|
+
maxTokens?: number;
|
|
18
|
+
temperature?: number;
|
|
19
|
+
timeout?: number;
|
|
20
|
+
maxRetries?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface UpdateModelsRequest {
|
|
24
|
+
Body: {
|
|
25
|
+
models: ModelConfig[];
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get all registered models
|
|
31
|
+
*/
|
|
32
|
+
export async function getModels(request: FastifyRequest, reply: FastifyReply) {
|
|
33
|
+
try {
|
|
34
|
+
const allLattices = modelLatticeManager.getAllLattices();
|
|
35
|
+
const models = allLattices.map((lattice) => {
|
|
36
|
+
// Extract config from the lattice client
|
|
37
|
+
// Note: This is a simplified approach - you may need to adjust based on actual implementation
|
|
38
|
+
const config = (lattice.client as any).config || {};
|
|
39
|
+
return {
|
|
40
|
+
key: lattice.key,
|
|
41
|
+
model: config.model || "",
|
|
42
|
+
provider: config.provider || "openai",
|
|
43
|
+
streaming: config.streaming || false,
|
|
44
|
+
apiKey: config.apiKey || "",
|
|
45
|
+
baseURL: config.baseURL || "",
|
|
46
|
+
maxTokens: config.maxTokens,
|
|
47
|
+
temperature: config.temperature,
|
|
48
|
+
timeout: config.timeout,
|
|
49
|
+
maxRetries: config.maxRetries,
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return reply.send({
|
|
54
|
+
success: true,
|
|
55
|
+
data: models,
|
|
56
|
+
});
|
|
57
|
+
} catch (error: any) {
|
|
58
|
+
console.error("Failed to get models", {
|
|
59
|
+
error: error.message,
|
|
60
|
+
stack: error.stack,
|
|
61
|
+
});
|
|
62
|
+
return reply.status(500).send({
|
|
63
|
+
success: false,
|
|
64
|
+
error: error.message || "Failed to get models",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Update models configuration
|
|
71
|
+
* Registers or updates model lattices
|
|
72
|
+
*/
|
|
73
|
+
export async function updateModels(
|
|
74
|
+
request: FastifyRequest<UpdateModelsRequest>,
|
|
75
|
+
reply: FastifyReply
|
|
76
|
+
) {
|
|
77
|
+
try {
|
|
78
|
+
const { models } = request.body;
|
|
79
|
+
|
|
80
|
+
if (!models || !Array.isArray(models)) {
|
|
81
|
+
return reply.status(400).send({
|
|
82
|
+
success: false,
|
|
83
|
+
error: "Invalid request: models must be an array",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const registeredModels: string[] = [];
|
|
88
|
+
const errors: string[] = [];
|
|
89
|
+
|
|
90
|
+
for (const modelConfig of models) {
|
|
91
|
+
if (!modelConfig.key || !modelConfig.model || !modelConfig.provider) {
|
|
92
|
+
errors.push(
|
|
93
|
+
`Model configuration is incomplete: key, model, and provider are required`
|
|
94
|
+
);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Remove existing model with the same key if it exists
|
|
100
|
+
if (modelLatticeManager.hasLattice(modelConfig.key)) {
|
|
101
|
+
modelLatticeManager.removeLattice(modelConfig.key);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Convert to LLMConfig format
|
|
105
|
+
const llmConfig: LLMConfig = {
|
|
106
|
+
provider: modelConfig.provider,
|
|
107
|
+
model: modelConfig.model,
|
|
108
|
+
streaming: modelConfig.streaming ?? false,
|
|
109
|
+
apiKey: modelConfig.apiKey,
|
|
110
|
+
baseURL: modelConfig.baseURL,
|
|
111
|
+
maxTokens: modelConfig.maxTokens,
|
|
112
|
+
temperature: modelConfig.temperature,
|
|
113
|
+
timeout: modelConfig.timeout,
|
|
114
|
+
maxRetries: modelConfig.maxRetries,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Register the new model lattice
|
|
118
|
+
registerModelLattice(modelConfig.key, llmConfig);
|
|
119
|
+
registeredModels.push(modelConfig.key);
|
|
120
|
+
} catch (error: any) {
|
|
121
|
+
errors.push(
|
|
122
|
+
`Failed to register model ${modelConfig.key}: ${error.message}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (errors.length > 0 && registeredModels.length === 0) {
|
|
128
|
+
return reply.status(400).send({
|
|
129
|
+
success: false,
|
|
130
|
+
error: errors.join("; "),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return reply.send({
|
|
135
|
+
success: true,
|
|
136
|
+
message: `Successfully registered ${registeredModels.length} model(s)`,
|
|
137
|
+
data: {
|
|
138
|
+
registered: registeredModels,
|
|
139
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
} catch (error: any) {
|
|
143
|
+
console.error("Failed to update models", {
|
|
144
|
+
error: error.message,
|
|
145
|
+
stack: error.stack,
|
|
146
|
+
});
|
|
147
|
+
return reply.status(500).send({
|
|
148
|
+
success: false,
|
|
149
|
+
error: error.message || "Failed to update models",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|