@hailer/mcp 0.1.3 → 0.1.4

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.
@@ -1,22 +1,24 @@
1
- # Insight JOIN Patterns
1
+ ---
2
+ name: insight-join-patterns
3
+ description: Correct JOIN syntax for Hailer insights with ActivityLink fields
4
+ triggers: JOIN query errors, missing columns, NULL results in insight queries
5
+ ---
2
6
 
3
- ## Pattern
7
+ <problem>
4
8
  When joining workflows with ActivityLink fields in Hailer insights, you must:
5
9
  1. Include `_id` meta field in BOTH source definitions
6
10
  2. Join ON the activitylink field value equals target _id
7
11
  3. Use the activitylink fieldId (NOT the key) for the JOIN condition
12
+ </problem>
8
13
 
9
- ## Critical Rules
10
-
11
- **Always Required:**
14
+ <rules>
12
15
  - Both workflows need `{ name: 'id', meta: '_id' }` in their fields array
13
16
  - JOIN condition: `source1.activityLinkFieldName = source2.id`
14
17
  - Use LEFT JOIN for optional relationships (activitylink can be null)
15
18
  - Use INNER JOIN only when relationship must exist
19
+ </rules>
16
20
 
17
- ## Correct Examples
18
-
19
- ### Basic ActivityLink JOIN
21
+ <correct>
20
22
  ```javascript
21
23
  // Players workflow has "club" field (activitylink to Clubs workflow)
22
24
  {
@@ -42,7 +44,38 @@ When joining workflows with ActivityLink fields in Hailer insights, you must:
42
44
  query: 'SELECT p.player_name, c.club_name FROM p LEFT JOIN c ON p.club = c.id'
43
45
  }
44
46
  ```
47
+ </correct>
48
+
49
+ <wrong>
50
+ ```javascript
51
+ // ❌ WRONG - Missing _id in clubs source
52
+ {
53
+ sources: [
54
+ {
55
+ name: 'p',
56
+ workflowId: 'players-id',
57
+ fields: [
58
+ { name: 'player_name', meta: 'name' },
59
+ { name: 'id', meta: '_id' },
60
+ { name: 'club', fieldId: 'club-field-id' }
61
+ ]
62
+ },
63
+ {
64
+ name: 'c',
65
+ workflowId: 'clubs-id',
66
+ fields: [
67
+ { name: 'club_name', meta: 'name' }
68
+ // Missing: { name: 'id', meta: '_id' }
69
+ ]
70
+ }
71
+ ],
72
+ query: 'SELECT p.player_name, c.club_name FROM p LEFT JOIN c ON p.club = c.id'
73
+ }
74
+ // Error: "no such column: c.id"
75
+ ```
76
+ </wrong>
45
77
 
78
+ <examples>
46
79
  ### Three-Way JOIN (Tasks -> Topics -> Projects)
47
80
  ```javascript
48
81
  {
@@ -53,7 +86,7 @@ When joining workflows with ActivityLink fields in Hailer insights, you must:
53
86
  fields: [
54
87
  { name: 'task_name', meta: 'name' },
55
88
  { name: 'id', meta: '_id' },
56
- { name: 'topic', fieldId: 'topic-field-id' } // Links to Topics
89
+ { name: 'topic', fieldId: 'topic-field-id' }
57
90
  ]
58
91
  },
59
92
  {
@@ -62,7 +95,7 @@ When joining workflows with ActivityLink fields in Hailer insights, you must:
62
95
  fields: [
63
96
  { name: 'topic_name', meta: 'name' },
64
97
  { name: 'id', meta: '_id' },
65
- { name: 'project', fieldId: 'project-field-id' } // Links to Projects
98
+ { name: 'project', fieldId: 'project-field-id' }
66
99
  ]
67
100
  },
68
101
  {
@@ -75,10 +108,7 @@ When joining workflows with ActivityLink fields in Hailer insights, you must:
75
108
  }
76
109
  ],
77
110
  query: `
78
- SELECT
79
- t.task_name,
80
- top.topic_name,
81
- p.project_name
111
+ SELECT t.task_name, top.topic_name, p.project_name
82
112
  FROM t
83
113
  LEFT JOIN top ON t.topic = top.id
84
114
  LEFT JOIN p ON top.project = p.id
@@ -109,76 +139,16 @@ When joining workflows with ActivityLink fields in Hailer insights, you must:
109
139
  }
110
140
  ],
111
141
  query: `
112
- SELECT
113
- teams.team_name,
114
- COUNT(*) as match_count
142
+ SELECT teams.team_name, COUNT(*) as match_count
115
143
  FROM matches
116
144
  LEFT JOIN teams ON matches.home_team = teams.id
117
145
  GROUP BY teams.team_name
118
146
  `
119
147
  }
120
148
  ```
149
+ </examples>
121
150
 
122
- ## Wrong Examples
123
-
124
- ### Missing _id Field
125
- ```javascript
126
- // ❌ WRONG - Missing _id in clubs source
127
- {
128
- sources: [
129
- {
130
- name: 'p',
131
- workflowId: 'players-id',
132
- fields: [
133
- { name: 'player_name', meta: 'name' },
134
- { name: 'id', meta: '_id' },
135
- { name: 'club', fieldId: 'club-field-id' }
136
- ]
137
- },
138
- {
139
- name: 'c',
140
- workflowId: 'clubs-id',
141
- fields: [
142
- { name: 'club_name', meta: 'name' }
143
- // Missing: { name: 'id', meta: '_id' }
144
- ]
145
- }
146
- ],
147
- query: 'SELECT p.player_name, c.club_name FROM p LEFT JOIN c ON p.club = c.id'
148
- }
149
- // Error: "no such column: c.id"
150
- ```
151
-
152
- ### Using Key Instead of FieldId
153
- ```javascript
154
- // ❌ WRONG - Using field key instead of fieldId
155
- {
156
- sources: [
157
- {
158
- name: 'p',
159
- workflowId: 'players-id',
160
- fields: [
161
- { name: 'player_name', meta: 'name' },
162
- { name: 'id', meta: '_id' },
163
- { name: 'club', key: 'club' } // Wrong! Use fieldId
164
- ]
165
- }
166
- ]
167
- }
168
- ```
169
-
170
- ### Wrong JOIN Syntax
171
- ```javascript
172
- // ❌ WRONG - Trying to join on name instead of id
173
- query: 'SELECT p.player_name, c.club_name FROM p LEFT JOIN c ON p.club = c.club_name'
174
-
175
- // ❌ WRONG - Using field key in JOIN instead of column name
176
- query: 'SELECT p.player_name, c.club_name FROM p LEFT JOIN c ON p.clubId = c.id'
177
- // (Should use column name from sources definition: p.club = c.id)
178
- ```
179
-
180
- ## Troubleshooting
181
-
151
+ <troubleshooting>
182
152
  **Error: "no such column: c.id"**
183
153
  - Missing `{ name: 'id', meta: '_id' }` in target workflow source
184
154
 
@@ -189,21 +159,13 @@ query: 'SELECT p.player_name, c.club_name FROM p LEFT JOIN c ON p.clubId = c.id'
189
159
  **NULL results for joined data**
190
160
  - ActivityLink field is empty/null for some activities (expected with LEFT JOIN)
191
161
  - Use INNER JOIN if you only want activities with relationships
162
+ </troubleshooting>
192
163
 
193
- ## Workflow
194
-
195
- 1. Get schema: `get_workflow_schema({ workflowId, phaseId })`
196
- 2. Find ActivityLink field ID and target workflow ID
197
- 3. Build sources with BOTH _id fields
198
- 4. Preview query: `preview_insight({ sources, query })`
199
- 5. Fix errors, re-preview
200
- 6. Create insight when preview succeeds
201
-
202
- ## Quick Checklist
203
-
164
+ <checklist>
204
165
  Before creating an insight with JOINs:
205
166
  - [ ] Both workflow sources include `{ name: 'id', meta: '_id' }`
206
167
  - [ ] ActivityLink field uses `fieldId` (NOT `key`)
207
168
  - [ ] JOIN condition uses column names from sources (e.g., `p.club = c.id`)
208
169
  - [ ] Using LEFT JOIN (unless relationship required)
209
170
  - [ ] Tested with `preview_insight` first
171
+ </checklist>
@@ -1,8 +1,12 @@
1
- # JSON-Only Output
1
+ ---
2
+ name: json-only-output
3
+ description: Fix agents adding prose after JSON responses
4
+ triggers: Agent outputs explanation text after valid JSON
5
+ ---
2
6
 
3
- <pattern>
7
+ <problem>
4
8
  Agents violate JSON-only protocol by adding prose explanations AFTER their JSON response.
5
- </pattern>
9
+ </problem>
6
10
 
7
11
  <correct>
8
12
  ```json
@@ -1,8 +1,12 @@
1
- # Optional Parameters vs Empty Values
1
+ ---
2
+ name: optional-parameters
3
+ description: Omit optional parameters instead of passing empty values
4
+ triggers: Tool error about empty array/string when parameter should be omitted
5
+ ---
2
6
 
3
- <pattern>
7
+ <problem>
4
8
  Tool parameter is required in schema but should be OMITTED (not passed at all) when not needed, rather than passed as empty array/string.
5
- </pattern>
9
+ </problem>
6
10
 
7
11
  <correct>
8
12
  ```typescript
@@ -1,8 +1,12 @@
1
- # Tool Response Verification
1
+ ---
2
+ name: tool-response-verification
3
+ description: Ensure agents verify tool execution before claiming success
4
+ triggers: Agent returns success without checking tool result
5
+ ---
2
6
 
3
- <pattern>
7
+ <problem>
4
8
  Agent fabricates success response without verifying actual tool execution result.
5
- </pattern>
9
+ </problem>
6
10
 
7
11
  <correct>
8
12
  ```typescript
@@ -0,0 +1,21 @@
1
+ /**
2
+ * MCP Assistant (Lightweight)
3
+ *
4
+ * Pure pass-through chatbot. System prompt is loaded on AI PC side.
5
+ * Just forwards user messages, no local processing.
6
+ */
7
+ export declare class McpAssistant {
8
+ private client;
9
+ constructor(config: {
10
+ apiKey: string;
11
+ baseURL?: string;
12
+ });
13
+ /**
14
+ * Chat - pure pass-through to local LLM
15
+ */
16
+ chat(userMessage: string, conversationHistory?: Array<{
17
+ role: 'user' | 'assistant';
18
+ content: string;
19
+ }>): Promise<string>;
20
+ }
21
+ //# sourceMappingURL=mcp-assistant.d.ts.map
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * MCP Assistant (Lightweight)
4
+ *
5
+ * Pure pass-through chatbot. System prompt is loaded on AI PC side.
6
+ * Just forwards user messages, no local processing.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.McpAssistant = void 0;
13
+ const openai_1 = __importDefault(require("openai"));
14
+ const logger_1 = require("../lib/logger");
15
+ const logger = (0, logger_1.createLogger)({ component: 'McpAssistant' });
16
+ class McpAssistant {
17
+ client;
18
+ constructor(config) {
19
+ this.client = new openai_1.default({
20
+ apiKey: config.apiKey,
21
+ baseURL: config.baseURL,
22
+ });
23
+ }
24
+ /**
25
+ * Chat - pure pass-through to local LLM
26
+ */
27
+ async chat(userMessage, conversationHistory = []) {
28
+ const model = process.env.OPENAI_MODEL || 'gpt-4o';
29
+ try {
30
+ const messages = [
31
+ ...conversationHistory.slice(-4).map(msg => ({
32
+ role: msg.role,
33
+ content: msg.content,
34
+ })),
35
+ { role: 'user', content: userMessage },
36
+ ];
37
+ logger.debug('Chat', { model, msgCount: messages.length });
38
+ const response = await this.client.chat.completions.create({
39
+ model,
40
+ messages,
41
+ max_tokens: 500,
42
+ temperature: 0.7,
43
+ });
44
+ const reply = response.choices[0]?.message?.content || 'Could not respond.';
45
+ logger.info('Response', {
46
+ in: response.usage?.prompt_tokens,
47
+ out: response.usage?.completion_tokens,
48
+ });
49
+ return reply;
50
+ }
51
+ catch (error) {
52
+ logger.error('Chat failed', error);
53
+ throw error;
54
+ }
55
+ }
56
+ }
57
+ exports.McpAssistant = McpAssistant;
58
+ //# sourceMappingURL=mcp-assistant.js.map
@@ -8,6 +8,7 @@ exports.McpClient = void 0;
8
8
  const message_processor_1 = require("./message-processor");
9
9
  const openai_provider_1 = require("./providers/openai-provider");
10
10
  const anthropic_provider_1 = require("./providers/anthropic-provider");
11
+ const assistant_provider_1 = require("./providers/assistant-provider");
11
12
  const multi_bot_manager_1 = require("./multi-bot-manager");
12
13
  const agent_tracker_1 = require("./agent-tracker");
13
14
  const token_tracker_1 = require("./token-tracker");
@@ -102,6 +103,9 @@ class McpClient {
102
103
  case "anthropic":
103
104
  provider = new anthropic_provider_1.AnthropicProvider(providerConfig);
104
105
  break;
106
+ case "assistant":
107
+ provider = new assistant_provider_1.AssistantProvider(providerConfig);
108
+ break;
105
109
  case "gemini":
106
110
  // TODO: Implement GeminiProvider
107
111
  console.warn(`⚠️ Provider type ${providerConfig.type} not yet implemented`);
@@ -306,9 +310,11 @@ class McpClient {
306
310
  }
307
311
  return; // Skip LLM processing
308
312
  }
309
- // Generate and post personalized confirmation message
313
+ // Generate and post personalized confirmation message (skip if empty)
310
314
  const confirmationMessage = await provider.generateConfirmationMessage(message);
311
- await this.messageProcessor.postMessage(message.discussionId, confirmationMessage, message.workspaceId, message.mentionedOrDirectMessagedBotId);
315
+ if (confirmationMessage) {
316
+ await this.messageProcessor.postMessage(message.discussionId, confirmationMessage, message.workspaceId, message.mentionedOrDirectMessagedBotId);
317
+ }
312
318
  const response = await this.processMessage(message, provider);
313
319
  await this.handleResponse(message, response);
314
320
  // LOG COMPLETION EVENT - Bot finished processing
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Assistant Provider
3
+ *
4
+ * Pure pass-through chatbot. No tools, no local system prompt.
5
+ * System prompt is loaded on AI PC side via llama-server.
6
+ */
7
+ import { ChatMessage, McpResponse } from "../types";
8
+ import { LlmProvider } from "./llm-provider";
9
+ import { LlmProviderConfig } from "../types";
10
+ export declare class AssistantProvider extends LlmProvider {
11
+ private assistant;
12
+ constructor(config: LlmProviderConfig);
13
+ generateConfirmationMessage(_userMessage: ChatMessage): Promise<string>;
14
+ processMessage(userMessage: ChatMessage, _mcpServerUrl: string, _botMcpApiKey: string, _botEmail: string): Promise<McpResponse>;
15
+ protected callMcpTool(): Promise<any>;
16
+ }
17
+ //# sourceMappingURL=assistant-provider.d.ts.map
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * Assistant Provider
4
+ *
5
+ * Pure pass-through chatbot. No tools, no local system prompt.
6
+ * System prompt is loaded on AI PC side via llama-server.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.AssistantProvider = void 0;
10
+ const llm_provider_1 = require("./llm-provider");
11
+ const mcp_assistant_1 = require("../mcp-assistant");
12
+ class AssistantProvider extends llm_provider_1.LlmProvider {
13
+ assistant;
14
+ constructor(config) {
15
+ super(config);
16
+ this.assistant = new mcp_assistant_1.McpAssistant({
17
+ apiKey: config.apiKey,
18
+ baseURL: config.baseURL,
19
+ });
20
+ }
21
+ async generateConfirmationMessage(_userMessage) {
22
+ // No confirmation message - direct response only
23
+ return '';
24
+ }
25
+ async processMessage(userMessage, _mcpServerUrl, _botMcpApiKey, _botEmail) {
26
+ if (!this.isEnabled()) {
27
+ return { success: false, error: "Assistant not enabled" };
28
+ }
29
+ try {
30
+ const startTime = Date.now();
31
+ const cleanedMessage = this.removeMentions(userMessage);
32
+ this.logger.debug('Chat request', { user: userMessage.userName });
33
+ const response = await this.assistant.chat(cleanedMessage);
34
+ this.logger.info('Chat done', { duration: Date.now() - startTime });
35
+ return {
36
+ success: true,
37
+ response,
38
+ toolCalls: [],
39
+ };
40
+ }
41
+ catch (error) {
42
+ this.logError(error, "processMessage");
43
+ return { success: false, error: error.message };
44
+ }
45
+ }
46
+ async callMcpTool() {
47
+ throw new Error("Assistant does not use tools");
48
+ }
49
+ }
50
+ exports.AssistantProvider = AssistantProvider;
51
+ //# sourceMappingURL=assistant-provider.js.map
@@ -20,6 +20,7 @@ class OpenAIProvider extends llm_provider_1.LlmProvider {
20
20
  super(config);
21
21
  this.client = new openai_1.default({
22
22
  apiKey: config.apiKey,
23
+ baseURL: config.baseURL,
23
24
  });
24
25
  this.contextManager = (0, context_manager_1.getContextManager)({
25
26
  openaiApiKey: config.apiKey,
@@ -72,11 +73,19 @@ class OpenAIProvider extends llm_provider_1.LlmProvider {
72
73
  const startTime = Date.now();
73
74
  // Load tool index with automatic filtering
74
75
  // Chat bot only gets READ + WRITE tools (no PLAYGROUND tools)
76
+ // In 'minimal' mode, only load essential chat tools (~3 tools vs 40+)
75
77
  const allowedGroups = [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE];
78
+ const minimalChatTools = [
79
+ 'add_discussion_message',
80
+ 'fetch_discussion_messages',
81
+ 'search_workspace_users'
82
+ ];
83
+ const isMinimalMode = process.env.CHAT_BOT_MODE === 'minimal';
76
84
  const toolIndex = await this.toolSchemaLoader.loadToolIndex({
77
85
  mcpServerUrl,
78
86
  mcpServerApiKey: botMcpApiKey,
79
- allowedGroups
87
+ allowedGroups,
88
+ allowedTools: isMinimalMode ? minimalChatTools : undefined
80
89
  });
81
90
  if (!toolIndex || toolIndex.length === 0) {
82
91
  this.logger.warn("No MCP tools available");
@@ -25,6 +25,7 @@ export interface LoadToolsOptions {
25
25
  mcpServerUrl: string;
26
26
  mcpServerApiKey: string;
27
27
  allowedGroups: ToolGroup[];
28
+ allowedTools?: string[];
28
29
  excludeMessageFetchTools?: boolean;
29
30
  }
30
31
  export declare class ToolSchemaLoader {
@@ -22,9 +22,17 @@ class ToolSchemaLoader {
22
22
  * Returns lightweight tool list with optional exclusions
23
23
  */
24
24
  async loadToolIndex(options) {
25
- const { mcpServerUrl, mcpServerApiKey, allowedGroups, excludeMessageFetchTools } = options;
25
+ const { mcpServerUrl, mcpServerApiKey, allowedGroups, allowedTools, excludeMessageFetchTools } = options;
26
26
  // Fetch tool index from MCP server with group filtering
27
27
  let toolIndex = await this.fetchMcpToolIndex(mcpServerUrl, mcpServerApiKey, allowedGroups);
28
+ // Filter by explicit whitelist if provided (overrides group filtering)
29
+ if (allowedTools && allowedTools.length > 0) {
30
+ toolIndex = toolIndex.filter(tool => allowedTools.includes(tool.name));
31
+ logger.info("Filtered tools by whitelist", {
32
+ allowedTools,
33
+ resultCount: toolIndex.length
34
+ });
35
+ }
28
36
  // Optionally exclude message fetch tools if explicitly requested
29
37
  if (excludeMessageFetchTools) {
30
38
  const excludedTools = ['fetch_discussion_messages', 'fetch_previous_discussion_messages'];
@@ -16,8 +16,9 @@ export interface McpClientConfig {
16
16
  }
17
17
  export interface LlmProviderConfig {
18
18
  name: string;
19
- type: "openai" | "anthropic" | "gemini";
19
+ type: "openai" | "anthropic" | "gemini" | "assistant";
20
20
  apiKey: string;
21
+ baseURL?: string;
21
22
  model: string;
22
23
  enabled: boolean;
23
24
  maxTokens?: number;
package/dist/config.d.ts CHANGED
@@ -35,6 +35,7 @@ export declare const environment: {
35
35
  MCP_CLIENT_AGENT_IDS: string[];
36
36
  TOKEN_USAGE_BOT_ENABLED: boolean;
37
37
  AGENT_ACTIVITY_BOT_ENABLED: boolean;
38
+ CHAT_BOT_MODE: "full" | "minimal" | "assistant";
38
39
  ADAPTIVE_DOCUMENTATION_BOT_ENABLED: boolean;
39
40
  ADAPTIVE_AUTO_UPDATE: boolean;
40
41
  ADAPTIVE_UPDATE_INTERVAL: number;
@@ -49,6 +50,8 @@ export declare const environment: {
49
50
  WORKSPACE_CONFIG_PATH?: string | undefined;
50
51
  DEV_APPS_PATH?: string | undefined;
51
52
  OPENAI_API_KEY?: string | undefined;
53
+ OPENAI_API_BASE?: string | undefined;
54
+ OPENAI_MODEL?: string | undefined;
52
55
  ANTHROPIC_API_KEY?: string | undefined;
53
56
  };
54
57
  export interface HailerAccount {
@@ -60,8 +63,9 @@ export interface HailerAccount {
60
63
  }
61
64
  export interface LlmProvider {
62
65
  name: string;
63
- type: 'openai' | 'anthropic';
66
+ type: 'openai' | 'anthropic' | 'assistant';
64
67
  apiKey: string;
68
+ baseURL?: string;
65
69
  model: string;
66
70
  enabled: boolean;
67
71
  }
package/dist/config.js CHANGED
@@ -102,6 +102,8 @@ const environmentSchema = zod_1.z.object({
102
102
  DEV_APPS_PATH: zod_1.z.string().optional(),
103
103
  // LLM providers
104
104
  OPENAI_API_KEY: zod_1.z.string().min(1).optional(),
105
+ OPENAI_API_BASE: zod_1.z.string().url().optional(),
106
+ OPENAI_MODEL: zod_1.z.string().optional(),
105
107
  ANTHROPIC_API_KEY: zod_1.z.string().min(1).optional(),
106
108
  // MCP client settings
107
109
  MCP_SERVER_URL: zod_1.z.string().url().default('http://localhost:3030/api/mcp'),
@@ -110,6 +112,8 @@ const environmentSchema = zod_1.z.object({
110
112
  // Bot features
111
113
  TOKEN_USAGE_BOT_ENABLED: zod_1.z.string().transform(v => v !== 'false').default('true'),
112
114
  AGENT_ACTIVITY_BOT_ENABLED: zod_1.z.string().transform(v => v !== 'false').default('true'),
115
+ // Chat bot mode: 'full' = all tools, 'minimal' = chat tools only, 'assistant' = no tools (knowledge-based help)
116
+ CHAT_BOT_MODE: zod_1.z.enum(['full', 'minimal', 'assistant']).default('full'),
113
117
  ADAPTIVE_DOCUMENTATION_BOT_ENABLED: zod_1.z.string().transform(v => v === 'true').default('false'),
114
118
  ADAPTIVE_AUTO_UPDATE: zod_1.z.string().transform(v => v === 'true').default('false'),
115
119
  ADAPTIVE_UPDATE_INTERVAL: zod_1.z.string().transform(v => parseInt(v) || 60000).default('60000'),
@@ -164,12 +168,14 @@ class ApplicationConfig {
164
168
  }
165
169
  get llmProviders() {
166
170
  const providers = [];
167
- if (exports.environment.OPENAI_API_KEY) {
171
+ if (exports.environment.OPENAI_API_KEY || exports.environment.OPENAI_API_BASE) {
172
+ const isAssistantMode = exports.environment.CHAT_BOT_MODE === 'assistant';
168
173
  providers.push({
169
- name: 'openai-gpt4o',
170
- type: 'openai',
171
- apiKey: exports.environment.OPENAI_API_KEY,
172
- model: 'gpt-4o',
174
+ name: isAssistantMode ? 'mcp-assistant' : (exports.environment.OPENAI_API_BASE ? 'local-llm' : 'openai-gpt4o'),
175
+ type: isAssistantMode ? 'assistant' : 'openai',
176
+ apiKey: exports.environment.OPENAI_API_KEY || 'not-needed',
177
+ baseURL: exports.environment.OPENAI_API_BASE,
178
+ model: exports.environment.OPENAI_MODEL || 'gpt-4o',
173
179
  enabled: true,
174
180
  });
175
181
  }