@ixo/common 1.1.31 → 1.1.33

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 (37) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/ai/models/openai.d.ts +16 -0
  3. package/dist/ai/models/openai.d.ts.map +1 -1
  4. package/dist/ai/models/openai.js +26 -0
  5. package/dist/ai/models/openai.js.map +1 -1
  6. package/dist/ai/nodes/generic-chat/generic-chat.node.d.ts +1 -1
  7. package/dist/ai/semantic-router-factory/create-semantic-router.d.ts +1 -2
  8. package/dist/ai/semantic-router-factory/create-semantic-router.d.ts.map +1 -1
  9. package/dist/ai/semantic-router-factory/create-semantic-router.js +2 -3
  10. package/dist/ai/semantic-router-factory/create-semantic-router.js.map +1 -1
  11. package/dist/ai/tools/ask-ixo-guru/ask-ixo-guru.d.ts +1 -1
  12. package/dist/ai/tools/ask-ixo-guru/ask-ixo-guru.d.ts.map +1 -1
  13. package/dist/ai/tools/parser-action-tool.d.ts +1 -1
  14. package/dist/ai/tools/parser-action-tool.d.ts.map +1 -1
  15. package/dist/ai/tools/parser-browser-tool.d.ts +1 -1
  16. package/dist/ai/tools/parser-browser-tool.d.ts.map +1 -1
  17. package/dist/ai/tools/scrape-web-page.d.ts +1 -1
  18. package/dist/ai/tools/scrape-web-page.d.ts.map +1 -1
  19. package/dist/ai/tools/web-search-tool.d.ts +1 -1
  20. package/dist/ai/tools/web-search-tool.d.ts.map +1 -1
  21. package/dist/ai/utils/transformGraphStateMessageToListMessageResponse.d.ts +10 -0
  22. package/dist/ai/utils/transformGraphStateMessageToListMessageResponse.d.ts.map +1 -1
  23. package/dist/ai/utils/transformGraphStateMessageToListMessageResponse.js +2 -0
  24. package/dist/ai/utils/transformGraphStateMessageToListMessageResponse.js.map +1 -1
  25. package/dist/services/env/env.service.js.map +1 -1
  26. package/dist/services/session-manager/session-manager.service.d.ts +2 -2
  27. package/dist/services/session-manager/session-manager.service.d.ts.map +1 -1
  28. package/dist/services/session-manager/session-manager.service.js +48 -26
  29. package/dist/services/session-manager/session-manager.service.js.map +1 -1
  30. package/package.json +13 -14
  31. package/src/ai/models/openai.ts +31 -0
  32. package/src/ai/semantic-router-factory/create-semantic-router.test.ts +0 -3
  33. package/src/ai/semantic-router-factory/create-semantic-router.ts +2 -7
  34. package/src/ai/utils/transformGraphStateMessageToListMessageResponse.ts +16 -0
  35. package/src/services/env/env.service.ts +1 -1
  36. package/src/services/session-manager/session-manager.service.ts +57 -25
  37. package/tsconfig.tsbuildinfo +1 -1
@@ -8,6 +8,37 @@ import {
8
8
  } from '@langchain/openai';
9
9
  import OpenAI, { type ClientOptions } from 'openai';
10
10
 
11
+ export type LLMProvider = 'openrouter' | 'nebius';
12
+
13
+ export function getLLMProvider(): LLMProvider {
14
+ const raw = (process.env.LLM_PROVIDER ?? 'openrouter').toLowerCase();
15
+ if (raw === 'nebius') return 'nebius';
16
+ return 'openrouter';
17
+ }
18
+
19
+ /** Provider-aware base URL and API key. */
20
+ export function getProviderConfig() {
21
+ const provider = getLLMProvider();
22
+
23
+ if (provider === 'nebius') {
24
+ return {
25
+ provider,
26
+ baseURL: 'https://api.tokenfactory.nebius.com/v1/',
27
+ apiKey: process.env.NEBIUS_API_KEY ?? '',
28
+ headers: {} as Record<string, string>,
29
+ };
30
+ }
31
+
32
+ return {
33
+ provider,
34
+ baseURL: 'https://openrouter.ai/api/v1',
35
+ apiKey: process.env.OPEN_ROUTER_API_KEY ?? '',
36
+ headers: {
37
+ 'HTTP-Referer': 'oracle-app.com',
38
+ 'X-Title': process.env.ORACLE_NAME ?? 'Oracle App',
39
+ },
40
+ };
41
+ }
11
42
  const getChatOpenAiModel = (params?: ChatOpenAIFields): ChatOpenAI =>
12
43
  new ChatOpenAI({
13
44
  temperature: 0.2,
@@ -2,9 +2,6 @@ import { createSemanticRouter } from './create-semantic-router.js';
2
2
 
3
3
  const parse = vi.fn();
4
4
  const create = vi.fn();
5
- vi.mock('langfuse', () => ({
6
- observeOpenAI: vi.fn((client: unknown) => client),
7
- }));
8
5
  vi.mock('openai', () => ({
9
6
  OpenAI: vi.fn().mockImplementation(() => {
10
7
  function fn(): unknown {
@@ -1,6 +1,5 @@
1
1
  import { Logger } from '@ixo/logger';
2
2
  import { PromptTemplate } from '@langchain/core/prompts';
3
- import { type LangfuseConfig, observeOpenAI } from 'langfuse';
4
3
  import { OpenAI } from 'openai';
5
4
  import { zodResponseFormat } from 'openai/helpers/zod';
6
5
  import z from 'zod';
@@ -39,10 +38,7 @@ export const createSemanticRouter = <
39
38
  | 'gpt-4.1-nano'
40
39
  | 'gpt-4.1-mini' = 'gpt-4.1-mini',
41
40
  isComplex = false,
42
- ): ((
43
- state: EnsureKeys<Record<string, unknown>, K>,
44
- traceConfig?: LangfuseConfig,
45
- ) => Promise<keyof R>) => {
41
+ ): ((state: EnsureKeys<Record<string, unknown>, K>) => Promise<keyof R>) => {
46
42
  const keys = validateRoutes(routes, basedOn);
47
43
  const schema = z.object({
48
44
  nextRoute: z.enum(
@@ -52,7 +48,6 @@ export const createSemanticRouter = <
52
48
  });
53
49
  return async <T extends Record<string, unknown>>(
54
50
  state: EnsureKeys<T, K>,
55
- traceConfig?: LangfuseConfig,
56
51
  ): Promise<keyof R> => {
57
52
  const selectedValues = {} as Record<string, string | object>;
58
53
  for (const key of basedOn) {
@@ -72,7 +67,7 @@ export const createSemanticRouter = <
72
67
  // find the route that matches the state
73
68
  const prompt = PromptTemplate.fromTemplate(semanticRouterPrompt);
74
69
 
75
- const client = observeOpenAI(new OpenAI(), traceConfig);
70
+ const client = new OpenAI();
76
71
  const promptWithState = await prompt.format({
77
72
  routes: jsonToYaml(routes),
78
73
  state: jsonToYaml(selectedValues),
@@ -14,6 +14,15 @@ interface ToolCall {
14
14
  output?: string;
15
15
  }
16
16
 
17
+ export interface AttachmentMeta {
18
+ filename: string;
19
+ mimetype: string;
20
+ size?: number;
21
+ mxcUri?: string;
22
+ eventId?: string;
23
+ category: string;
24
+ }
25
+
17
26
  interface MessageDto {
18
27
  id: string;
19
28
  type: 'ai' | 'human';
@@ -22,6 +31,7 @@ interface MessageDto {
22
31
  reasoning?: string;
23
32
  isComplete?: boolean;
24
33
  isReasoning?: boolean;
34
+ attachment?: AttachmentMeta;
25
35
  }
26
36
 
27
37
  export interface ListOracleMessagesResponse {
@@ -36,6 +46,7 @@ export interface CleanAdditionalKwargs {
36
46
  type: string;
37
47
  text: string;
38
48
  }>;
49
+ attachment?: AttachmentMeta;
39
50
  [key: string]: unknown; // Allow additional properties for LangChain compatibility
40
51
  }
41
52
 
@@ -55,6 +66,10 @@ export function transformGraphStateMessageToListMessageResponse(
55
66
  message.additional_kwargs as CleanAdditionalKwargs;
56
67
  const reasoning = additionalKwargs?.reasoning;
57
68
 
69
+ // Extract attachment metadata for human messages
70
+ const attachment =
71
+ message.type === 'human' ? additionalKwargs?.attachment : undefined;
72
+
58
73
  acc.push({
59
74
  type: message.type === 'ai' ? 'ai' : 'human',
60
75
  content: String(message.content),
@@ -68,6 +83,7 @@ export function transformGraphStateMessageToListMessageResponse(
68
83
  reasoning,
69
84
  isComplete: true, // Messages from DB are always complete
70
85
  isReasoning: false, // since this is not a reasoning message and the request is done
86
+ ...(attachment && { attachment }),
71
87
  });
72
88
  }
73
89
  if (toolMsg) {
@@ -11,7 +11,7 @@ export class EnvService<T extends z.ZodType> {
11
11
  private constructor(schema: T, onError?: (error: z.ZodError) => void) {
12
12
  try {
13
13
  // Parse and validate environment variables
14
- this.validatedEnv = schema.parse(process.env) as z.infer<T>;
14
+ this.validatedEnv = schema.parse(process.env) as unknown as z.infer<T>;
15
15
  } catch (error) {
16
16
  if (error instanceof z.ZodError) {
17
17
  if (onError) {
@@ -2,7 +2,12 @@ import { Logger } from '@ixo/logger';
2
2
  import { MatrixManager } from '@ixo/matrix';
3
3
  import { getMatrixHomeServerCroppedForDid } from '@ixo/oracles-chain-client';
4
4
  import { type Database } from 'better-sqlite3';
5
- import { getChatOpenAiModel } from '../../ai/index.js';
5
+ import {
6
+ getChatOpenAiModel,
7
+ getLLMProvider,
8
+ getOpenRouterChatModel,
9
+ getProviderConfig,
10
+ } from '../../ai/index.js';
6
11
  import { type MemoryEngineService } from '../memory-engine/memory-engine.service.js';
7
12
  import { type UserContextData } from '../memory-engine/types.js';
8
13
  import {
@@ -41,18 +46,28 @@ export class SessionManagerService {
41
46
  if (messages.length === 0) {
42
47
  return 'Untitled';
43
48
  }
44
- const llm = getChatOpenAiModel({
45
- model: 'meta-llama/llama-3.1-8b-instruct',
46
- temperature: 0.3,
47
- apiKey: process.env.OPEN_ROUTER_API_KEY,
48
- timeout: 20 * 1000 * 60, // 20 minutes
49
- configuration: {
50
- baseURL: 'https://openrouter.ai/api/v1',
51
- },
52
- });
49
+ const provider = getLLMProvider();
50
+ const config = getProviderConfig();
51
+
52
+ const llm =
53
+ provider === 'openrouter'
54
+ ? getOpenRouterChatModel({
55
+ model: 'meta-llama/llama-3.1-8b-instruct',
56
+ temperature: 0.3,
57
+ timeout: 60_000,
58
+ })
59
+ : getChatOpenAiModel({
60
+ model: 'meta-llama/Meta-Llama-3.1-8B-Instruct',
61
+ temperature: 0.3,
62
+ apiKey: config.apiKey,
63
+ timeout: 60_000,
64
+ configuration: {
65
+ baseURL: config.baseURL,
66
+ },
67
+ });
53
68
  const response = await llm.invoke(
54
- `Based on this messages messages, Add a title for this convo and only based on the messages? MAKE SURE TO ONLY RESPOND WITH THE TITLE.
55
-
69
+ `Based on this messages messages, Add a title for this convo and only based on the messages? MAKE SURE TO ONLY RESPOND WITH THE TITLE.
70
+
56
71
  ## RESPONSE FORMAT
57
72
  ONLY RESPOND WITH THE TITLE not anything else that title will be saved to the store directly from your response so generated based on the messages.
58
73
 
@@ -125,7 +140,7 @@ ___________________________________________________________
125
140
  messages,
126
141
  oracleEntityDid,
127
142
  oracleName,
128
- roomId: _roomId,
143
+ roomId,
129
144
  lastProcessedCount,
130
145
  oracleDid,
131
146
  userContext,
@@ -158,6 +173,7 @@ ___________________________________________________________
158
173
  oracleEntityDid,
159
174
  oracleDid,
160
175
  userContext,
176
+ roomId,
161
177
  slackThreadTs,
162
178
  };
163
179
 
@@ -165,8 +181,8 @@ ___________________________________________________________
165
181
  db.prepare(
166
182
  `
167
183
  INSERT INTO sessions (
168
- session_id, title, last_updated_at, created_at, oracle_name,
169
- oracle_did, oracle_entity_did, last_processed_count,
184
+ session_id, title, last_updated_at, created_at, oracle_name,
185
+ oracle_did, oracle_entity_did, last_processed_count,
170
186
  user_context, room_id, slack_thread_ts
171
187
  )
172
188
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
@@ -203,6 +219,19 @@ ___________________________________________________________
203
219
  })
204
220
  : selectedSession.title;
205
221
 
222
+ if (allowTitleUpdate && roomId && title) {
223
+ this.matrixManger
224
+ .editMessage({
225
+ messageId: sessionId,
226
+ roomId,
227
+ message: title,
228
+ isOracleAdmin: true,
229
+ })
230
+ .catch((err) => {
231
+ Logger.error('Failed to update conversation title in Matrix:', err);
232
+ });
233
+ }
234
+
206
235
  const lastUpdatedAt = new Date().toISOString();
207
236
  const updatedSession: ChatSession = {
208
237
  ...selectedSession,
@@ -214,7 +243,7 @@ ___________________________________________________________
214
243
 
215
244
  db.prepare(
216
245
  `
217
- UPDATE sessions
246
+ UPDATE sessions
218
247
  SET title = ?, last_updated_at = ?, last_processed_count = ?, slack_thread_ts = ?
219
248
  WHERE session_id = ?
220
249
  `,
@@ -237,11 +266,11 @@ ___________________________________________________________
237
266
  const db = await this.syncService.getUserDatabase(did);
238
267
  const row = db
239
268
  .prepare(
240
- `SELECT
269
+ `SELECT
241
270
  session_id, title, last_updated_at, created_at, oracle_name,
242
271
  oracle_did, oracle_entity_did, last_processed_count,
243
272
  user_context, room_id, slack_thread_ts
244
- FROM sessions
273
+ FROM sessions
245
274
  WHERE session_id = ?`,
246
275
  )
247
276
  .get(sessionId) as
@@ -300,12 +329,12 @@ ___________________________________________________________
300
329
  // Get paginated sessions with total count
301
330
  const rows = db
302
331
  .prepare(
303
- `SELECT
332
+ `SELECT
304
333
  session_id, title, last_updated_at, created_at, oracle_name,
305
334
  oracle_did, oracle_entity_did, last_processed_count,
306
335
  user_context, room_id, slack_thread_ts,
307
336
  COUNT(*) OVER() as total
308
- FROM sessions
337
+ FROM sessions
309
338
  ORDER BY last_updated_at DESC
310
339
  LIMIT ? OFFSET ?`,
311
340
  )
@@ -346,6 +375,7 @@ ___________________________________________________________
346
375
 
347
376
  public async createSession(
348
377
  createSessionDto: CreateChatSessionDto,
378
+ overrideEventId?: string,
349
379
  ): Promise<CreateChatSessionResponseDto> {
350
380
  const userHomeServer =
351
381
  createSessionDto.homeServer ||
@@ -359,11 +389,13 @@ ___________________________________________________________
359
389
  if (!roomId) {
360
390
  throw new Error('Room ID not found');
361
391
  }
362
- const eventId = await this.matrixManger.sendMessage({
363
- message: 'New Conversation Started',
364
- roomId,
365
- isOracleAdmin: true,
366
- });
392
+ const eventId =
393
+ overrideEventId ??
394
+ (await this.matrixManger.sendMessage({
395
+ message: 'New Conversation Started',
396
+ roomId,
397
+ isOracleAdmin: true,
398
+ }));
367
399
 
368
400
  // Gather user context from Memory Engine
369
401
  let userContext: UserContextData | undefined;