morpheus-cli 0.4.0 → 0.4.2

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.
@@ -229,7 +229,10 @@ You maintain intent until resolution.
229
229
  const lastMessage = response.messages[response.messages.length - 1];
230
230
  const responseContent = (typeof lastMessage.content === 'string') ? lastMessage.content : JSON.stringify(lastMessage.content);
231
231
  // Sati Middleware: Evaluation (Fire and forget)
232
- this.satiMiddleware.afterAgent(responseContent, [...previousMessages, userMessage])
232
+ const currentSessionId = (this.history instanceof SQLiteChatMessageHistory)
233
+ ? this.history.currentSessionId
234
+ : undefined;
235
+ this.satiMiddleware.afterAgent(responseContent, [...previousMessages, userMessage], currentSessionId)
233
236
  .catch((e) => this.display.log(`Sati memory evaluation failed: ${e.message}`, { source: 'Sati' }));
234
237
  return responseContent;
235
238
  }
@@ -4,14 +4,12 @@ import { ChatOllama } from "@langchain/ollama";
4
4
  import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
5
5
  import { ProviderError } from "../errors.js";
6
6
  import { createAgent, createMiddleware } from "langchain";
7
- // import { MultiServerMCPClient, } from "@langchain/mcp-adapters"; // REMOVED
8
- import { z } from "zod";
9
7
  import { DisplayManager } from "../display.js";
10
- import { ConfigQueryTool, ConfigUpdateTool, DiagnosticTool, MessageCountTool, TokenUsageTool, ProviderModelUsageTool } from "../tools/index.js";
8
+ import { ConfigQueryTool, ConfigUpdateTool, DiagnosticTool, MessageCountTool, TokenUsageTool, ProviderModelUsageTool, ApocDelegateTool } from "../tools/index.js";
11
9
  export class ProviderFactory {
12
- static async create(config, tools = []) {
13
- let display = DisplayManager.getInstance();
14
- const toolMonitoringMiddleware = createMiddleware({
10
+ static buildMonitoringMiddleware() {
11
+ const display = DisplayManager.getInstance();
12
+ return createMiddleware({
15
13
  name: "ToolMonitoringMiddleware",
16
14
  wrapToolCall: (request, handler) => {
17
15
  display.log(`Executing tool: ${request.toolCall.name}`, { level: "warning", source: 'ConstructLoad' });
@@ -27,55 +25,84 @@ export class ProviderFactory {
27
25
  }
28
26
  },
29
27
  });
30
- let model;
31
- const responseSchema = z.object({
32
- content: z.string().describe("The main response content from the agent"),
33
- });
34
- // Removed direct MCP client instantiation
28
+ }
29
+ static buildModel(config) {
30
+ switch (config.provider) {
31
+ case 'openai':
32
+ return new ChatOpenAI({
33
+ modelName: config.model,
34
+ temperature: config.temperature,
35
+ apiKey: process.env.OPENAI_API_KEY || config.api_key,
36
+ });
37
+ case 'anthropic':
38
+ return new ChatAnthropic({
39
+ modelName: config.model,
40
+ temperature: config.temperature,
41
+ apiKey: process.env.ANTHROPIC_API_KEY || config.api_key,
42
+ });
43
+ case 'openrouter':
44
+ return new ChatOpenAI({
45
+ modelName: config.model,
46
+ temperature: config.temperature,
47
+ apiKey: process.env.OPENROUTER_API_KEY || config.api_key,
48
+ configuration: {
49
+ baseURL: config.base_url || 'https://openrouter.ai/api/v1'
50
+ }
51
+ });
52
+ case 'ollama':
53
+ return new ChatOllama({
54
+ model: config.model,
55
+ temperature: config.temperature,
56
+ baseUrl: config.base_url || config.api_key,
57
+ });
58
+ case 'gemini':
59
+ return new ChatGoogleGenerativeAI({
60
+ model: config.model,
61
+ temperature: config.temperature,
62
+ apiKey: process.env.GOOGLE_API_KEY || config.api_key
63
+ });
64
+ default:
65
+ throw new Error(`Unsupported provider: ${config.provider}`);
66
+ }
67
+ }
68
+ static handleProviderError(config, error) {
69
+ let suggestion = "Check your configuration and API keys.";
70
+ const msg = error.message?.toLowerCase() || '';
71
+ if (msg.includes("api key") && (msg.includes("missing") || msg.includes("not found"))) {
72
+ suggestion = `API Key is missing for ${config.provider}. Run 'morpheus config' or set it in .env.`;
73
+ }
74
+ else if (msg.includes("401") || msg.includes("unauthorized")) {
75
+ suggestion = `Run 'morpheus config' to update your ${config.provider} API key.`;
76
+ }
77
+ else if ((msg.includes("econnrefused") || msg.includes("fetch failed")) && config.provider === 'ollama') {
78
+ suggestion = "Is Ollama running? Try 'ollama serve'.";
79
+ }
80
+ else if (msg.includes("model not found") || msg.includes("404")) {
81
+ suggestion = `Model '${config.model}' may not be available. Check provider docs.`;
82
+ }
83
+ else if (msg.includes("unsupported provider")) {
84
+ suggestion = "Edit your config file to use a supported provider (openai, anthropic, openrouter, ollama, gemini).";
85
+ }
86
+ throw new ProviderError(config.provider, error, suggestion);
87
+ }
88
+ /**
89
+ * Creates a ReactAgent with only the provided tools — no internal Oracle tools injected.
90
+ * Used by subagents like Apoc that need a clean, isolated tool context.
91
+ */
92
+ static async createBare(config, tools = []) {
93
+ try {
94
+ const model = ProviderFactory.buildModel(config);
95
+ const middleware = ProviderFactory.buildMonitoringMiddleware();
96
+ return createAgent({ model, tools, middleware: [middleware] });
97
+ }
98
+ catch (error) {
99
+ ProviderFactory.handleProviderError(config, error);
100
+ }
101
+ }
102
+ static async create(config, tools = []) {
35
103
  try {
36
- switch (config.provider) {
37
- case 'openai':
38
- model = new ChatOpenAI({
39
- modelName: config.model,
40
- temperature: config.temperature,
41
- apiKey: process.env.OPENAI_API_KEY || config.api_key, // Check env var first, then config
42
- });
43
- break;
44
- case 'anthropic':
45
- model = new ChatAnthropic({
46
- modelName: config.model,
47
- temperature: config.temperature,
48
- apiKey: process.env.ANTHROPIC_API_KEY || config.api_key, // Check env var first, then config
49
- });
50
- break;
51
- case 'openrouter':
52
- model = new ChatOpenAI({
53
- modelName: config.model,
54
- temperature: config.temperature,
55
- apiKey: process.env.OPENROUTER_API_KEY || config.api_key, // Check env var first, then config
56
- configuration: {
57
- baseURL: config.base_url || 'https://openrouter.ai/api/v1'
58
- }
59
- });
60
- break;
61
- case 'ollama':
62
- // Ollama usually runs locally, api_key optional
63
- model = new ChatOllama({
64
- model: config.model,
65
- temperature: config.temperature,
66
- baseUrl: config.api_key, // Sometimes users might overload api_key for base URL or similar, but simplified here
67
- });
68
- break;
69
- case 'gemini':
70
- model = new ChatGoogleGenerativeAI({
71
- model: config.model,
72
- temperature: config.temperature,
73
- apiKey: process.env.GOOGLE_API_KEY || config.api_key // Check env var first, then config
74
- });
75
- break;
76
- default:
77
- throw new Error(`Unsupported provider: ${config.provider}`);
78
- }
104
+ const model = ProviderFactory.buildModel(config);
105
+ const middleware = ProviderFactory.buildMonitoringMiddleware();
79
106
  const toolsForAgent = [
80
107
  ...tools,
81
108
  ConfigQueryTool,
@@ -83,35 +110,13 @@ export class ProviderFactory {
83
110
  DiagnosticTool,
84
111
  MessageCountTool,
85
112
  TokenUsageTool,
86
- ProviderModelUsageTool
113
+ ProviderModelUsageTool,
114
+ ApocDelegateTool
87
115
  ];
88
- return createAgent({
89
- model: model,
90
- tools: toolsForAgent,
91
- middleware: [toolMonitoringMiddleware]
92
- });
116
+ return createAgent({ model, tools: toolsForAgent, middleware: [middleware] });
93
117
  }
94
118
  catch (error) {
95
- let suggestion = "Check your configuration and API keys.";
96
- const msg = error.message?.toLowerCase() || '';
97
- // Constructor validation errors (Missing Keys)
98
- if (msg.includes("api key") && (msg.includes("missing") || msg.includes("not found"))) {
99
- suggestion = `API Key is missing for ${config.provider}. Run 'morpheus config' or set it in .env.`;
100
- }
101
- // Network/Auth errors (unlikely in constructor, but possible if pre-validation exists)
102
- else if (msg.includes("401") || msg.includes("unauthorized")) {
103
- suggestion = `Run 'morpheus config' to update your ${config.provider} API key.`;
104
- }
105
- else if ((msg.includes("econnrefused") || msg.includes("fetch failed")) && config.provider === 'ollama') {
106
- suggestion = "Is Ollama running? Try 'ollama serve'.";
107
- }
108
- else if (msg.includes("model not found") || msg.includes("404")) {
109
- suggestion = `Model '${config.model}' may not be available. Check provider docs.`;
110
- }
111
- else if (msg.includes("unsupported provider")) {
112
- suggestion = "Edit your config file to use a supported provider (openai, anthropic, openrouter, ollama, gemini).";
113
- }
114
- throw new ProviderError(config.provider, error, suggestion);
119
+ ProviderFactory.handleProviderError(config, error);
115
120
  }
116
121
  }
117
122
  }
@@ -2,6 +2,21 @@ import { GoogleGenAI } from '@google/genai';
2
2
  import OpenAI from 'openai';
3
3
  import { OpenRouter } from '@openrouter/sdk';
4
4
  import fs from 'fs';
5
+ /**
6
+ * Estimates audio duration in seconds based on file size and a typical bitrate.
7
+ * Uses 32 kbps (4000 bytes/sec) as a conservative baseline for compressed audio (OGG, MP3, etc.).
8
+ * This is an approximation — actual duration depends on encoding settings.
9
+ */
10
+ function estimateAudioDurationSeconds(filePath) {
11
+ try {
12
+ const stats = fs.statSync(filePath);
13
+ const bytesPerSecond = 4000; // ~32 kbps
14
+ return Math.round(stats.size / bytesPerSecond);
15
+ }
16
+ catch {
17
+ return 0;
18
+ }
19
+ }
5
20
  class GeminiTelephonist {
6
21
  model;
7
22
  constructor(model) {
@@ -41,7 +56,8 @@ class GeminiTelephonist {
41
56
  total_tokens: usage?.totalTokenCount ?? 0,
42
57
  input_token_details: {
43
58
  cache_read: usage?.cachedContentTokenCount ?? 0
44
- }
59
+ },
60
+ audio_duration_seconds: estimateAudioDurationSeconds(filePath)
45
61
  };
46
62
  return { text, usage: usageMetadata };
47
63
  }
@@ -75,6 +91,7 @@ class WhisperTelephonist {
75
91
  input_tokens: 0,
76
92
  output_tokens: 0,
77
93
  total_tokens: 0,
94
+ audio_duration_seconds: estimateAudioDurationSeconds(filePath)
78
95
  };
79
96
  return { text, usage: usageMetadata };
80
97
  }
@@ -131,6 +148,7 @@ class OpenRouterTelephonist {
131
148
  input_tokens: usage?.prompt_tokens ?? 0,
132
149
  output_tokens: usage?.completion_tokens ?? 0,
133
150
  total_tokens: usage?.total_tokens ?? 0,
151
+ audio_duration_seconds: estimateAudioDurationSeconds(filePath)
134
152
  };
135
153
  return { text, usage: usageMetadata };
136
154
  }
@@ -0,0 +1,43 @@
1
+ import { tool } from "@langchain/core/tools";
2
+ import { z } from "zod";
3
+ import { Apoc } from "../apoc.js";
4
+ /**
5
+ * Tool that Oracle uses to delegate devtools tasks to Apoc.
6
+ * Oracle should call this whenever the user requests operations like:
7
+ * - Reading/writing/listing files
8
+ * - Running shell commands or scripts
9
+ * - Git operations (status, log, commit, push, etc.)
10
+ * - Package management (npm install, etc.)
11
+ * - Process inspection or management
12
+ * - Network diagnostics (ping, curl, DNS)
13
+ * - System information queries
14
+ */
15
+ export const ApocDelegateTool = tool(async ({ task, context }) => {
16
+ try {
17
+ const apoc = Apoc.getInstance();
18
+ const result = await apoc.execute(task, context);
19
+ return result;
20
+ }
21
+ catch (err) {
22
+ return `Apoc execution failed: ${err.message}`;
23
+ }
24
+ }, {
25
+ name: "apoc_delegate",
26
+ description: `Delegate a devtools task to Apoc, the specialized development subagent.
27
+
28
+ Use this tool when the user asks for ANY of the following:
29
+ - File operations: read, write, create, delete files or directories
30
+ - Shell commands: run scripts, execute commands, check output
31
+ - Git: status, log, diff, commit, push, pull, clone, branch
32
+ - Package management: npm install/update/audit, yarn, package.json inspection
33
+ - Process management: list processes, kill processes, check ports
34
+ - Network: ping hosts, curl URLs, DNS lookups
35
+ - System info: environment variables, OS info, disk space, memory
36
+
37
+ Provide a clear natural language task description. Optionally provide context
38
+ from the current conversation to help Apoc understand the broader goal.`,
39
+ schema: z.object({
40
+ task: z.string().describe("Clear description of the devtools task to execute"),
41
+ context: z.string().optional().describe("Optional context from the conversation to help Apoc understand the goal"),
42
+ }),
43
+ });
@@ -2,3 +2,4 @@
2
2
  export * from './config-tools.js';
3
3
  export * from './diagnostic-tools.js';
4
4
  export * from './analytics-tools.js';
5
+ export * from './apoc-tool.js';
@@ -39,5 +39,11 @@ export const DEFAULT_CONFIG = {
39
39
  context_window: 100,
40
40
  memory_limit: 100,
41
41
  enabled_archived_sessions: true,
42
+ },
43
+ apoc: {
44
+ provider: 'openai',
45
+ model: 'gpt-4',
46
+ temperature: 0.2,
47
+ timeout_ms: 30000,
42
48
  }
43
49
  };