morpheus-cli 0.3.4 → 0.3.6

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,60 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import request from 'supertest';
3
- import express from 'express';
4
- import bodyParser from 'body-parser';
5
- import { createApiRouter } from '../api.js';
6
- import { ConfigManager } from '../../config/manager.js';
7
- import fs from 'fs-extra';
8
- // Mock dependencies
9
- vi.mock('../../config/manager.js');
10
- vi.mock('fs-extra');
11
- describe('Status API with Server Instance', () => {
12
- let app;
13
- let mockConfigManager;
14
- let mockServerInstance;
15
- beforeEach(() => {
16
- // Reset mocks
17
- vi.clearAllMocks();
18
- // Mock ConfigManager instance
19
- mockConfigManager = {
20
- get: vi.fn(),
21
- };
22
- ConfigManager.getInstance.mockReturnValue(mockConfigManager);
23
- // Mock server instance with getPort method
24
- mockServerInstance = {
25
- getPort: vi.fn().mockReturnValue(4567),
26
- };
27
- // Setup App
28
- app = express();
29
- app.use(bodyParser.json());
30
- // Create router with server instance
31
- app.use('/api', createApiRouter(mockServerInstance));
32
- });
33
- afterEach(() => {
34
- vi.restoreAllMocks();
35
- });
36
- describe('GET /api/status', () => {
37
- it('should return status information with server instance port', async () => {
38
- const mockConfig = {
39
- agent: { name: 'TestAgent' },
40
- llm: { provider: 'openai', model: 'gpt-4' },
41
- ui: { port: 3333 } // This should be overridden by server instance
42
- };
43
- mockConfigManager.get.mockReturnValue(mockConfig);
44
- // Mock fs.readJson to return a version
45
- fs.readJson.mockResolvedValue({ version: '1.0.0' });
46
- const res = await request(app).get('/api/status');
47
- expect(res.status).toBe(200);
48
- expect(res.body).toHaveProperty('status');
49
- expect(res.body).toHaveProperty('uptimeSeconds');
50
- expect(res.body).toHaveProperty('pid');
51
- expect(res.body).toHaveProperty('projectVersion');
52
- expect(res.body).toHaveProperty('nodeVersion');
53
- expect(res.body).toHaveProperty('agentName');
54
- expect(res.body).toHaveProperty('llmProvider');
55
- expect(res.body).toHaveProperty('llmModel');
56
- expect(res.body).toHaveProperty('serverPort');
57
- expect(res.body.serverPort).toBe(4567); // Should come from server instance
58
- });
59
- });
60
- });
@@ -1,95 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { Agent } from '../agent.js';
3
- import { ProviderFactory } from '../providers/factory.js';
4
- import { ToolsFactory } from '../tools/factory.js';
5
- import { DEFAULT_CONFIG } from '../../types/config.js';
6
- import { AIMessage } from '@langchain/core/messages';
7
- import * as fs from 'fs-extra';
8
- import * as path from 'path';
9
- import { homedir } from 'os';
10
- vi.mock('../providers/factory.js');
11
- vi.mock('../tools/factory.js');
12
- describe('Agent', () => {
13
- let agent;
14
- const mockProvider = {
15
- invoke: vi.fn(),
16
- };
17
- beforeEach(async () => {
18
- vi.resetAllMocks();
19
- // Clean up any existing test database
20
- const defaultDbPath = path.join(homedir(), ".morpheus", "memory", "short-memory.db");
21
- if (fs.existsSync(defaultDbPath)) {
22
- try {
23
- const Database = (await import("better-sqlite3")).default;
24
- const db = new Database(defaultDbPath);
25
- db.exec("DELETE FROM messages");
26
- db.close();
27
- }
28
- catch (err) {
29
- // Ignore errors if database doesn't exist or is corrupted
30
- }
31
- }
32
- mockProvider.invoke.mockResolvedValue({ messages: [new AIMessage('Hello world')] });
33
- vi.mocked(ProviderFactory.create).mockResolvedValue(mockProvider);
34
- vi.mocked(ToolsFactory.create).mockResolvedValue([]);
35
- agent = new Agent(DEFAULT_CONFIG);
36
- });
37
- afterEach(async () => {
38
- // Clean up after each test
39
- if (agent) {
40
- try {
41
- await agent.clearMemory();
42
- }
43
- catch (err) {
44
- // Ignore cleanup errors
45
- }
46
- }
47
- });
48
- it('should initialize successfully', async () => {
49
- await agent.initialize();
50
- expect(ToolsFactory.create).toHaveBeenCalled();
51
- expect(ProviderFactory.create).toHaveBeenCalledWith(DEFAULT_CONFIG.llm, []);
52
- });
53
- it('should chat successfully', async () => {
54
- await agent.initialize();
55
- const response = await agent.chat('Hi');
56
- expect(response).toBe('Hello world');
57
- expect(mockProvider.invoke).toHaveBeenCalled();
58
- });
59
- it('should throw if not initialized', async () => {
60
- await expect(agent.chat('Hi')).rejects.toThrow('initialize() first');
61
- });
62
- it('should maintain history', async () => {
63
- await agent.initialize();
64
- // Clear any residual history from previous tests
65
- await agent.clearMemory();
66
- // First turn
67
- await agent.chat('Hi');
68
- const history1 = await agent.getHistory();
69
- expect(history1).toHaveLength(2);
70
- expect(history1[0].content).toBe('Hi'); // User
71
- expect(history1[1].content).toBe('Hello world'); // AI
72
- // Second turn
73
- // Update mock return value for next call
74
- mockProvider.invoke.mockResolvedValue({ messages: [new AIMessage('I am fine')] });
75
- await agent.chat('How are you?');
76
- const history2 = await agent.getHistory();
77
- expect(history2).toHaveLength(4);
78
- expect(history2[2].content).toBe('How are you?');
79
- expect(history2[3].content).toBe('I am fine');
80
- });
81
- describe('Configuration Validation', () => {
82
- it('should throw if llm provider is missing', async () => {
83
- const invalidConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
84
- delete invalidConfig.llm.provider; // Invalid
85
- const badAgent = new Agent(invalidConfig);
86
- await expect(badAgent.initialize()).rejects.toThrow('LLM provider not specified');
87
- });
88
- it('should propagate ProviderError during initialization', async () => {
89
- const mockError = new Error("Mock Factory Error");
90
- vi.mocked(ProviderFactory.create).mockImplementation(() => { throw mockError; });
91
- // ProviderError constructs message as: "Provider {provider} failed: {originalError.message}"
92
- await expect(agent.initialize()).rejects.toThrow('Provider openai failed: Mock Factory Error');
93
- });
94
- });
95
- });
@@ -1,61 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { Agent } from '../agent.js';
3
- import { ProviderFactory } from '../providers/factory.js';
4
- import { DEFAULT_CONFIG } from '../../types/config.js';
5
- import { AIMessage } from '@langchain/core/messages';
6
- import * as fs from 'fs-extra';
7
- import * as path from 'path';
8
- import { homedir } from 'os';
9
- vi.mock('../providers/factory.js');
10
- describe('Agent Memory Limit', () => {
11
- let agent;
12
- const mockProvider = {
13
- invoke: vi.fn(),
14
- };
15
- const dbPath = path.join(homedir(), ".morpheus", "memory", "short-memory.db");
16
- beforeEach(async () => {
17
- vi.resetAllMocks();
18
- // Clean up DB
19
- if (fs.existsSync(dbPath)) {
20
- try {
21
- const Database = (await import("better-sqlite3")).default;
22
- const db = new Database(dbPath);
23
- db.exec("DELETE FROM messages");
24
- db.close();
25
- }
26
- catch (err) { }
27
- }
28
- mockProvider.invoke.mockResolvedValue({
29
- messages: [new AIMessage('Response')]
30
- });
31
- vi.mocked(ProviderFactory.create).mockResolvedValue(mockProvider);
32
- });
33
- afterEach(async () => {
34
- if (agent) {
35
- try {
36
- await agent.clearMemory();
37
- }
38
- catch (err) { }
39
- }
40
- });
41
- it('should respect configured memory limit', async () => {
42
- const limitedConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
43
- limitedConfig.memory.limit = 2; // Only last 2 messages (1 exchange)
44
- agent = new Agent(limitedConfig);
45
- await agent.initialize();
46
- // Turn 1
47
- await agent.chat('Msg 1');
48
- // Turn 2
49
- await agent.chat('Msg 2');
50
- // Turn 3
51
- await agent.chat('Msg 3');
52
- // DB should have 6 messages (3 User + 3 AI)
53
- // getHistory() should return only 2 (User Msg 3 + AI Response)
54
- // Wait, SQLiteChatMessageHistory limit might be total messages? Or pairs?
55
- // LangChain's limit usually means "last N messages".
56
- const history = await agent.getHistory();
57
- // Assuming limit=2 means 2 messages.
58
- expect(history.length).toBeLessThanOrEqual(2);
59
- expect(history[history.length - 1].content).toBe('Response');
60
- });
61
- });
@@ -1,154 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
- import { Agent } from "../agent.js";
3
- import { ProviderFactory } from "../providers/factory.js";
4
- import { DEFAULT_CONFIG } from "../../types/config.js";
5
- import { AIMessage } from "@langchain/core/messages";
6
- import * as fs from "fs-extra";
7
- import * as path from "path";
8
- import { tmpdir } from "os";
9
- import { ToolsFactory } from "../tools/factory.js";
10
- vi.mock("../providers/factory.js");
11
- vi.mock("../tools/factory.js");
12
- describe("Agent Persistence Integration", () => {
13
- let agent;
14
- let testDbPath;
15
- let tempDir;
16
- const mockProvider = {
17
- invoke: vi.fn(),
18
- };
19
- beforeEach(async () => {
20
- vi.resetAllMocks();
21
- // Create a unique temporary test database path for each test to avoid interference
22
- tempDir = path.join(tmpdir(), "morpheus-test-agent", Date.now().toString() + Math.random().toString(36).substring(7));
23
- testDbPath = path.join(tempDir, "short-memory.db");
24
- mockProvider.invoke.mockImplementation(async ({ messages }) => {
25
- return {
26
- messages: [...messages, new AIMessage("Test response")]
27
- };
28
- });
29
- vi.mocked(ProviderFactory.create).mockResolvedValue(mockProvider);
30
- vi.mocked(ToolsFactory.create).mockResolvedValue([]);
31
- agent = new Agent(DEFAULT_CONFIG, { databasePath: testDbPath });
32
- });
33
- afterEach(async () => {
34
- // Clean up temporary test directory
35
- if (fs.existsSync(tempDir)) {
36
- try {
37
- fs.removeSync(tempDir);
38
- }
39
- catch (err) {
40
- // Ignore removal errors if file is locked
41
- }
42
- }
43
- });
44
- describe("Database File Creation", () => {
45
- it("should create database file on initialization", async () => {
46
- await agent.initialize();
47
- expect(fs.existsSync(testDbPath)).toBe(true);
48
- });
49
- });
50
- describe("Message Persistence", () => {
51
- it("should persist messages to database", async () => {
52
- await agent.initialize();
53
- // Send a message
54
- await agent.chat("Hello, Agent!");
55
- // Verify history contains the message
56
- const history = await agent.getHistory();
57
- expect(history).toHaveLength(2); // User message + AI response
58
- expect(history[0].content).toBe("Hello, Agent!");
59
- expect(history[1].content).toBe("Test response");
60
- });
61
- it("should restore conversation history on restart", async () => {
62
- // First session: send a message
63
- await agent.initialize();
64
- await agent.chat("Remember this message");
65
- const firstHistory = await agent.getHistory();
66
- expect(firstHistory).toHaveLength(2);
67
- // Simulate restart: create new agent instance with SAME database path
68
- const agent2 = new Agent(DEFAULT_CONFIG, { databasePath: testDbPath });
69
- await agent2.initialize();
70
- // Verify history was restored
71
- const restoredHistory = await agent2.getHistory();
72
- expect(restoredHistory.length).toBeGreaterThanOrEqual(2);
73
- // Check that the old messages are present
74
- const contents = restoredHistory.map(m => m.content);
75
- expect(contents).toContain("Remember this message");
76
- expect(contents).toContain("Test response");
77
- });
78
- it("should accumulate messages across multiple conversations", async () => {
79
- await agent.initialize();
80
- // First conversation
81
- await agent.chat("First message");
82
- // Second conversation
83
- mockProvider.invoke.mockImplementation(async ({ messages }) => {
84
- return {
85
- messages: [...messages, new AIMessage("Second response")]
86
- };
87
- });
88
- await agent.chat("Second message");
89
- // Verify all messages are persisted
90
- const history = await agent.getHistory();
91
- expect(history).toHaveLength(4);
92
- expect(history[0].content).toBe("First message");
93
- expect(history[1].content).toBe("Test response");
94
- expect(history[2].content).toBe("Second message");
95
- expect(history[3].content).toBe("Second response");
96
- });
97
- });
98
- describe("Memory Clearing", () => {
99
- it("should clear all persisted messages", async () => {
100
- await agent.initialize();
101
- // Add some messages
102
- await agent.chat("Message 1");
103
- await agent.chat("Message 2");
104
- // Verify messages exist
105
- let history = await agent.getHistory();
106
- expect(history.length).toBeGreaterThan(0);
107
- // Clear memory
108
- await agent.clearMemory();
109
- // Verify messages are cleared
110
- history = await agent.getHistory();
111
- expect(history).toEqual([]);
112
- });
113
- it("should start fresh after clearing memory", async () => {
114
- await agent.initialize();
115
- // Add and clear messages
116
- await agent.chat("Old message");
117
- await agent.clearMemory();
118
- // Add new message
119
- mockProvider.invoke.mockImplementation(async ({ messages }) => {
120
- return {
121
- messages: [...messages, new AIMessage("New response")]
122
- };
123
- });
124
- await agent.chat("New message");
125
- // Verify only new messages exist
126
- const history = await agent.getHistory();
127
- expect(history).toHaveLength(2);
128
- expect(history[0].content).toBe("New message");
129
- expect(history[1].content).toBe("New response");
130
- });
131
- });
132
- describe("Context Preservation", () => {
133
- it("should include history in conversation context", async () => {
134
- await agent.initialize();
135
- // First message
136
- await agent.chat("My name is Alice");
137
- // Second message (should have context)
138
- mockProvider.invoke.mockImplementation(async ({ messages }) => {
139
- return {
140
- messages: [...messages, new AIMessage("Hello Alice!")]
141
- };
142
- });
143
- await agent.chat("What is my name?");
144
- // Verify the provider was called with full history
145
- const lastCall = mockProvider.invoke.mock.calls[1];
146
- const messagesPassedToLLM = lastCall[0].messages;
147
- // Should include: system message, previous user message, previous AI response, new user message
148
- expect(messagesPassedToLLM.length).toBeGreaterThanOrEqual(4);
149
- // Check that context includes the original message
150
- const contents = messagesPassedToLLM.map((m) => m.content);
151
- expect(contents).toContain("My name is Alice");
152
- });
153
- });
154
- });
@@ -1,55 +0,0 @@
1
- import { Santi } from "../santi/santi.js";
2
- import { ConfigManager } from "../../config/manager.js";
3
- import * as path from "path";
4
- import { homedir } from "os";
5
- async function main() {
6
- console.log("Starting Santi Manual Verification...");
7
- // 1. Check DB isolation
8
- const santiDbPath = path.join(homedir(), ".morpheus", "memory", "santi-memory.db");
9
- const shortDbPath = path.join(homedir(), ".morpheus", "memory", "short-memory.db");
10
- console.log(`Checking DB paths: \n- ${santiDbPath}\n- ${shortDbPath}`);
11
- // 2. Initialize Santi
12
- const santi = new Santi();
13
- console.log("Santi initialized.");
14
- // 3. Test Recovery (should be empty or have data if prev run)
15
- let memories = await santi.recover("test");
16
- console.log(`Initial recovery "test": ${memories.length} items`);
17
- // 4. Manually add memory via internal store (reflection hack for test)
18
- console.log("Injecting test memory...");
19
- // @ts-ignore
20
- santi.store.addMemory({
21
- category: 'preference',
22
- importance: 'high',
23
- summary: 'The user loves manual testing scripts.',
24
- hash: 'manual-test-hash',
25
- source: 'manual_test'
26
- });
27
- // 5. Test Recovery again
28
- memories = await santi.recover("loves manual testing");
29
- console.log(`Recovery after injection "loves manual testing": ${memories.length} items`);
30
- if (memories.length > 0 && memories[0].summary.includes("loves manual testing")) {
31
- console.log("✅ Recovery SUCCESS");
32
- }
33
- else {
34
- console.error("❌ Recovery FAILED");
35
- }
36
- // 6. Test Evaluate (Mocking messages)
37
- // This requires LLM config to be present.
38
- // We skip this in manual test if no config, but logging will show warning.
39
- if (ConfigManager.getInstance().get().llm) {
40
- console.log("Testing Evaluate (dry run with LLM)...");
41
- // This will assume valid LLM config
42
- /*
43
- await santi.evaluate([
44
- new HumanMessage("My name is Morpheus Tester."),
45
- new AIMessage("Nice to meet you.")
46
- ]);
47
- */
48
- console.log("Evaluate test skipped to avoid api cost, but code is in place.");
49
- }
50
- else {
51
- console.log("Skipping Evaluate test (no LLM config).");
52
- }
53
- console.log("Verification Complete.");
54
- }
55
- main().catch(console.error);
@@ -1,172 +0,0 @@
1
- import { HumanMessage, SystemMessage } from "@langchain/core/messages";
2
- import { ProviderFactory } from "./providers/factory.js";
3
- import { ToolsFactory } from "./tools/factory.js";
4
- import { ConfigManager } from "../config/manager.js";
5
- import { ProviderError } from "./errors.js";
6
- import { DisplayManager } from "./display.js";
7
- import { SQLiteChatMessageHistory } from "./memory/sqlite.js";
8
- export class Agent {
9
- provider;
10
- config;
11
- history;
12
- display = DisplayManager.getInstance();
13
- databasePath;
14
- constructor(config, overrides) {
15
- this.config = config || ConfigManager.getInstance().get();
16
- this.databasePath = overrides?.databasePath;
17
- }
18
- async initialize() {
19
- if (!this.config.llm) {
20
- throw new Error("LLM configuration missing in config object.");
21
- }
22
- // Basic validation before provider creation
23
- if (!this.config.llm.provider) {
24
- throw new Error("LLM provider not specified in configuration.");
25
- }
26
- // Note: API Key validation is delegated to ProviderFactory or the Provider itself
27
- // to allow for Environment Variable fallback supported by LangChain.
28
- try {
29
- const tools = await ToolsFactory.create();
30
- this.provider = await ProviderFactory.create(this.config.llm, tools);
31
- if (!this.provider) {
32
- throw new Error("Provider factory returned undefined");
33
- }
34
- // Initialize persistent memory with SQLite
35
- this.history = new SQLiteChatMessageHistory({
36
- sessionId: "default",
37
- databasePath: this.databasePath,
38
- limit: this.config.memory?.limit || 100, // Fallback purely defensive if config type allows optional
39
- });
40
- }
41
- catch (err) {
42
- if (err instanceof ProviderError)
43
- throw err; // Re-throw known errors
44
- // Wrap unknown errors
45
- throw new ProviderError(this.config.llm.provider || 'unknown', err, "Agent initialization failed");
46
- }
47
- }
48
- async chat(message, extraUsage) {
49
- if (!this.provider) {
50
- throw new Error("Agent not initialized. Call initialize() first.");
51
- }
52
- if (!this.history) {
53
- throw new Error("Message history not initialized. Call initialize() first.");
54
- }
55
- try {
56
- this.display.log('Processing message...', { source: 'Agent' });
57
- const userMessage = new HumanMessage(message);
58
- // Inject provider/model metadata for persistence
59
- userMessage.provider_metadata = {
60
- provider: this.config.llm.provider,
61
- model: this.config.llm.model
62
- };
63
- // Attach extra usage (e.g. from Audio) to the user message to be persisted
64
- if (extraUsage) {
65
- userMessage.usage_metadata = extraUsage;
66
- }
67
- const systemMessage = new SystemMessage(`You are ${this.config.agent.name}, ${this.config.agent.personality},a local AI operator responsible for orchestrating tools, MCPs, and language models to solve the user’s request accurately and reliably.
68
-
69
- Your primary responsibility is NOT to answer from memory when external tools are available.
70
-
71
- You must follow these rules strictly:
72
-
73
- 1. Tool Evaluation First
74
- Before generating a final answer, always evaluate whether any available tool or MCP is capable of providing a more accurate, up-to-date, or authoritative response.
75
-
76
- If a tool can provide the answer, you MUST call the tool.
77
-
78
- 2. No Historical Assumptions for Dynamic Data
79
- If the user asks something that:
80
- - may change over time
81
- - depends on system state
82
- - depends on filesystem
83
- - depends on external APIs
84
- - was previously asked in the conversation
85
-
86
- You MUST NOT reuse previous outputs as final truth.
87
-
88
- Instead:
89
- - Re-evaluate available tools
90
- - Re-execute the relevant tool
91
- - Provide a fresh result
92
-
93
- Even if the user already asked the same question before, you must treat the request as requiring a new verification.
94
-
95
- 3. History Is Context, Not Source of Truth
96
- Conversation history may help with context, but it must not replace real-time verification via tools when tools are available.
97
-
98
- Never assume:
99
- - System state
100
- - File contents
101
- - Database values
102
- - API responses
103
- based only on previous messages.
104
-
105
- 4. Tool Priority Over Language Guessing
106
- If a tool can compute, fetch, inspect, or verify something, prefer tool usage over generating a speculative answer.
107
-
108
- Never hallucinate values that could be retrieved through a tool.
109
-
110
- 5. Freshness Principle
111
- Repeated user queries require fresh validation.
112
- Do not respond with:
113
- "As I said before..."
114
- Instead, perform a new tool check if applicable.
115
-
116
- 6. Final Answer Policy
117
- Only provide a direct natural language answer if:
118
- - No tool is relevant
119
- - Tools are unavailable
120
- - The question is conceptual or explanatory
121
-
122
- Otherwise, use tools first.
123
-
124
- You are an operator, not a guesser.
125
- Accuracy is more important than speed.
126
- `);
127
- // Load existing history from database
128
- const previousMessages = await this.history.getMessages();
129
- const messages = [
130
- systemMessage,
131
- ...previousMessages,
132
- userMessage
133
- ];
134
- const response = await this.provider.invoke({ messages });
135
- // Identify new messages generated during the interaction
136
- // The `messages` array passed to invoke had length `messages.length`
137
- // The `response.messages` contains the full state.
138
- // New messages start after the inputs.
139
- const startNewMessagesIndex = messages.length;
140
- const newGeneratedMessages = response.messages.slice(startNewMessagesIndex);
141
- // Persist User Message first
142
- await this.history.addMessage(userMessage);
143
- // Persist all new intermediate tool calls and responses
144
- for (const msg of newGeneratedMessages) {
145
- // Inject provider/model metadata search interactors
146
- msg.provider_metadata = {
147
- provider: this.config.llm.provider,
148
- model: this.config.llm.model
149
- };
150
- await this.history.addMessage(msg);
151
- }
152
- this.display.log('Response generated.', { source: 'Agent' });
153
- const lastMessage = response.messages[response.messages.length - 1];
154
- return (typeof lastMessage.content === 'string') ? lastMessage.content : JSON.stringify(lastMessage.content);
155
- }
156
- catch (err) {
157
- throw new ProviderError(this.config.llm.provider, err, "Chat request failed");
158
- }
159
- }
160
- async getHistory() {
161
- if (!this.history) {
162
- throw new Error("Message history not initialized. Call initialize() first.");
163
- }
164
- return await this.history.getMessages();
165
- }
166
- async clearMemory() {
167
- if (!this.history) {
168
- throw new Error("Message history not initialized. Call initialize() first.");
169
- }
170
- await this.history.clear();
171
- }
172
- }
@@ -1,55 +0,0 @@
1
- import { GoogleGenAI } from '@google/genai';
2
- export class AudioAgent {
3
- async transcribe(filePath, mimeType, apiKey) {
4
- try {
5
- const ai = new GoogleGenAI({ apiKey });
6
- // Upload the file
7
- const uploadResult = await ai.files.upload({
8
- file: filePath,
9
- config: { mimeType }
10
- });
11
- // Generate content (transcription)
12
- // using gemini-1.5-flash as it is fast and supports audio
13
- const response = await ai.models.generateContent({
14
- model: 'gemini-2.5-flash-lite',
15
- contents: [
16
- {
17
- role: 'user',
18
- parts: [
19
- {
20
- fileData: {
21
- fileUri: uploadResult.uri,
22
- mimeType: uploadResult.mimeType
23
- }
24
- },
25
- { text: "Transcribe this audio message accurately. Return only the transcribed text without any additional commentary." }
26
- ]
27
- }
28
- ]
29
- });
30
- // The new SDK returns text directly on the response object
31
- const text = response.text;
32
- if (!text) {
33
- throw new Error('No transcription generated');
34
- }
35
- // Extract usage metadata
36
- const usage = response.usageMetadata;
37
- const usageMetadata = {
38
- input_tokens: usage?.promptTokenCount ?? 0,
39
- output_tokens: usage?.candidatesTokenCount ?? 0,
40
- total_tokens: usage?.totalTokenCount ?? 0,
41
- input_token_details: {
42
- cache_read: usage?.cachedContentTokenCount ?? 0
43
- }
44
- };
45
- return { text, usage: usageMetadata };
46
- }
47
- catch (error) {
48
- // Wrap error for clarity
49
- if (error instanceof Error) {
50
- throw new Error(`Audio transcription failed: ${error.message}`);
51
- }
52
- throw error;
53
- }
54
- }
55
- }
@@ -1 +0,0 @@
1
- export {};