Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions example/nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"dependencies": {
"@eko-ai/eko": "workspace:*",
"@eko-ai/eko-nodejs": "workspace:*",
"exa-js": "^2.11.0",
"canvas": "^3.2.0",
"glob": "^11.0.2",
"keytar": "^7.9.0",
Expand Down
91 changes: 91 additions & 0 deletions example/nodejs/src/exa-chat-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Exa from 'exa-js';
import { ChatService, uuidv4 } from '@eko-ai/eko';
import { EkoMessage, WebSearchResult } from '@eko-ai/eko/types';

/**
* ChatService implementation powered by Exa (https://exa.ai) for web search.
*
* Exa is an AI-native search engine that returns clean, structured results
* optimised for agent pipelines. Set the EXA_API_KEY environment variable
* before constructing this service.
*
* Usage:
* import { ExaChatService } from './exa-chat-service';
* global.chatService = new ExaChatService();
*/
export class ExaChatService implements ChatService {
private readonly exa: Exa;

public constructor(apiKey?: string) {
const key = apiKey ?? process.env.EXA_API_KEY;
if (!key) {
throw new Error(
'EXA_API_KEY is required. Get one at https://dashboard.exa.ai/api-keys'
);
}
this.exa = new Exa(key);
// Integration header for Exa usage tracking
const headers = (this.exa as any).headers;
if (headers && typeof headers.set === 'function') {
headers.set('x-exa-integration', 'eko');
}
}

// -- ChatService: message persistence (no-op, override if needed) ----------

public loadMessages(_chatId: string): Promise<EkoMessage[]> {
return Promise.resolve([]);
}

public addMessage(_chatId: string, _messages: EkoMessage[]): Promise<void> {
return Promise.resolve();
}

public memoryRecall(_chatId: string, _prompt: string): Promise<string> {
return Promise.resolve('');
}

public async uploadFile(
file: { base64Data: string; mimeType: string; filename?: string },
_chatId: string,
_taskId?: string | undefined
): Promise<{ fileId: string; url: string }> {
return {
fileId: uuidv4(),
url: file.base64Data.startsWith('data:')
? file.base64Data
: `data:${file.mimeType};base64,${file.base64Data}`,
};
}

// -- ChatService: web search via Exa --------------------------------------

public async websearch(
_chatId: string,
query: string,
site?: string,
_language?: string,
maxResults?: number
): Promise<WebSearchResult[]> {
const numResults = Math.min(maxResults ?? 10, 50);

const response = await this.exa.search(query, {
type: 'auto',
numResults,
contents: {
highlights: true,
text: true,
},
...(site ? { includeDomains: [site] } : {}),
});

return response.results.map((result) => ({
title: result.title ?? '',
url: result.url,
snippet:
result.highlights?.join(' ') ??
(result.text ? result.text.slice(0, 300) : ''),
content: result.text ?? undefined,
}));
}
}
7 changes: 6 additions & 1 deletion example/nodejs/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import dotenv from "dotenv";
import FileAgent from "./file-agent";
import LocalCookiesBrowserAgent from "./browser";
import { ExaChatService } from "./exa-chat-service";
import { BrowserAgent } from "@eko-ai/eko-nodejs";
import { Eko, Log, LLMs, Agent, AgentStreamMessage } from "@eko-ai/eko";
import { Eko, Log, LLMs, Agent, AgentStreamMessage, global } from "@eko-ai/eko";

dotenv.config();

Expand Down Expand Up @@ -44,6 +45,10 @@ function testBrowserLoginStatus() {

async function run() {
Log.setLevel(1);
// Enable Exa-powered web search (requires EXA_API_KEY in .env)
if (process.env.EXA_API_KEY) {
global.chatService = new ExaChatService();
}
// Use local browser cookie login state, will read local Chrome's cookie and localStorage information
// If a password dialog pops up, please enter your computer password and click "Always Allow"
const agents: Agent[] = [
Expand Down