Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
aea4665
feat: Add Ollama provider support
Shriiii01 Feb 7, 2026
90be7c1
feat: Add Responses API support for Ollama
Shriiii01 Feb 9, 2026
b22d91d
refactor: Remove comments from Ollama provider files
Shriiii01 Feb 9, 2026
6b37790
Merge pull request #71 from Shriiii01/feature/add-ollama-provider
sm86 Feb 10, 2026
ac9c092
fix the broken link
sm86 Feb 12, 2026
8d92668
Merge branch 'agent-memory'
sm86 Feb 14, 2026
6e82165
open router integration simple version
sm86 Feb 14, 2026
39e0b3f
save realtime memory
sm86 Feb 14, 2026
32ac480
npm start only services you need
sm86 Feb 14, 2026
3558a8a
updated build/run scripts
sm86 Feb 14, 2026
886b3ed
updated env file
sm86 Feb 14, 2026
65bdcdc
fixed config
sm86 Feb 14, 2026
c325ccf
add scripts/launcher.js (was untracked due to *.js gitignore)
sm86 Feb 14, 2026
caafe2d
open router allow user keys
sm86 Feb 14, 2026
9592d76
fixed docker file and build issues
sm86 Feb 16, 2026
9ab6080
fixed docker image setup
sm86 Feb 16, 2026
557d43f
agent centric memory
sm86 Feb 16, 2026
ab9434f
clean up and openrouter integration
sm86 Feb 16, 2026
d06ebc0
added more memory tests - single user mode
sm86 Feb 16, 2026
dc207b8
updated rofl links
sm86 Feb 16, 2026
2029d86
updated rofl links
sm86 Feb 16, 2026
c6073d8
Merge pull request #75 from ekailabs/feat/simple-openrouter-integration
sm86 Feb 16, 2026
26b8b3a
updated docker scripts
sm86 Feb 17, 2026
a74a87e
Fix docker container exit issue in fullstack entrypoint
sm86 Feb 17, 2026
c1d456f
Update README with Docker service configuration and fixes
sm86 Feb 17, 2026
692d64a
Merge pull request #76 from ekailabs/feat/simple-openrouter-integration
sm86 Feb 17, 2026
cbea53a
Update YouTube video URL to new demo
sm86 Feb 17, 2026
d75b19c
Merge pull request #77 from ekailabs/feat/simple-openrouter-integration
sm86 Feb 17, 2026
cb71890
Merge main into agent-memory
sm86 Feb 18, 2026
52790d2
fix: add missing reflective sector to dashboard to prevent crash
sm86 Feb 18, 2026
efeb58f
Merge pull request #78 from ekailabs/agent-memory
sm86 Feb 18, 2026
551d581
docs: update READMEs for embedded memory architecture
sm86 Feb 18, 2026
fb4a34c
Merge pull request #79 from ekailabs/agent-memory
sm86 Feb 18, 2026
5ff7957
added memory as a standalone package
sm86 Feb 18, 2026
1c8fa62
Merge pull request #80 from ekailabs/agent-memory
sm86 Feb 18, 2026
da88077
feat: embed Memory Vault UI into OpenRouter for single-container Clou…
sm86 Feb 18, 2026
20af41f
fix: resolve @ekai/memory workspace dep in Docker runtime stages
sm86 Feb 18, 2026
751401d
feat(memory): add OpenRouter as a memory provider
sm86 Feb 18, 2026
e45bf02
fix(ci): remove npm ci from gateway build script
sm86 Feb 18, 2026
65d4497
quick start instructions
sm86 Feb 18, 2026
d845d19
updated instructions to add proxy
sm86 Feb 18, 2026
45406d1
feat(ci): add GHCR publish workflow for ekai-cloudrun image
sm86 Feb 18, 2026
b873007
Merge pull request #81 from ekailabs/standalone-cloud-run-deployment
sm86 Feb 18, 2026
471ed20
agent commits line number from instruction
sm86 Feb 18, 2026
038414c
remove reflective memory from extraction, ingestion, retrieval, and d…
sm86 Feb 18, 2026
2c69bb0
default dashboard to first agent profile instead of 'default'
sm86 Feb 18, 2026
3dfac23
fix build issues
sm86 Feb 18, 2026
67d0a3e
Merge pull request #82 from ekailabs/memory-dashboard-updates
sm86 Feb 18, 2026
d95bcd4
fixed docker build
sm86 Feb 18, 2026
6714f02
Merge pull request #83 from ekailabs/memory-dashboard-updates
sm86 Feb 18, 2026
e5b8425
make ekai-cloudrun the default Docker build target
sm86 Feb 18, 2026
81e7537
Merge remote-tracking branch 'origin/main' into memory-dashboard-updates
sm86 Feb 18, 2026
dacea95
Merge pull request #84 from ekailabs/memory-dashboard-updates
sm86 Feb 18, 2026
27490a6
Remove model catalogue feature
sm86 Feb 18, 2026
b2d7721
Merge pull request #85 from ekailabs/remove-model-catalogue
sm86 Feb 18, 2026
302313a
Remove gateway service — route everything through integrations/openro…
sm86 Feb 19, 2026
832d2a4
Update README to reflect gateway removal
sm86 Feb 19, 2026
fbe488d
Merge pull request #86 from ekailabs/remove-model-catalogue
sm86 Feb 19, 2026
b46502d
Remove unused shared/types directory
sm86 Feb 19, 2026
e7a6fd7
Strip dead dashboard code, keep memory UI
sm86 Feb 19, 2026
9578d4f
Remove integration guides from README
sm86 Feb 19, 2026
cdd5f06
Merge pull request #87 from ekailabs/cleanup
sm86 Feb 19, 2026
8bdabf9
fix build issues
sm86 Feb 20, 2026
1a7571e
Merge pull request #88 from ekailabs/cleanup
sm86 Feb 21, 2026
2db6ef7
Fix graph endpoints leaking user-scoped memories
sm86 Feb 21, 2026
bc98723
Add user-scope filtering to dashboard
sm86 Feb 21, 2026
33bc042
updated package.json
sm86 Feb 22, 2026
371df4b
Agent-centric Memory SDK with provider config
sm86 Feb 23, 2026
db8facd
Separate provider config from agent scoping
sm86 Feb 23, 2026
5fd9910
Remove /v1/ingest/documents endpoint and documents.ts
sm86 Feb 23, 2026
41a46b3
Remove unused graph endpoints and SDK graph methods
sm86 Feb 23, 2026
2f866e2
Dashboard overhaul: agent params, source display, userScope editing
sm86 Feb 23, 2026
b2d0098
Add .nvmrc (Node 22) and gitignore .next/ build cache
sm86 Feb 23, 2026
b75dc63
Disable live ingestion to stop runaway memory growth
sm86 Feb 24, 2026
c5fe181
Merge pull request #89 from living-ip/main
sm86 Feb 24, 2026
82fc8ad
Add user scope filtering for semantic memories on dashboard
sm86 Feb 24, 2026
ec36171
Merge origin/main into user-specific-memory
sm86 Feb 24, 2026
220512c
Fix graph userId filter, strict user scoping, and remove back arrow
sm86 Feb 25, 2026
9c6428d
Merge pull request #91 from ekailabs/user-specific-memory
sm86 Feb 25, 2026
e29d477
Make deduplication always-on, remove unused deduplicate flag
sm86 Feb 25, 2026
0a4b5df
Remove dead strength field and strengthenFact()
sm86 Feb 25, 2026
3652f9f
Add relevance gate for per-agent content filtering
sm86 Feb 25, 2026
2eccb20
Fix ensureAgentExists auto-recreate bug
sm86 Feb 25, 2026
3f9f55e
Add /agents dashboard page with full agent management
sm86 Feb 25, 2026
0ba52f2
Order memory logs: episodic, procedural, then semantic
sm86 Feb 25, 2026
5f314b2
Merge pull request #92 from ekailabs/dedup-and-filters
sm86 Feb 25, 2026
86eea49
Use npm ci with lockfiles in Dockerfile runtime stages
sm86 Feb 25, 2026
a192d85
Replace linear scan with sqlite-vec ANN vector search
sm86 Feb 26, 2026
853fbf1
Update README retrieval pipeline and data model for sqlite-vec
sm86 Feb 26, 2026
be7a49c
Keep embedding required on types, narrow graph return types
sm86 Feb 26, 2026
b6e2741
Merge pull request #93 from ekailabs/vector-database-integration
sm86 Feb 26, 2026
c489df4
Regenerate memory lockfile to include sqlite-vec dependency
sm86 Feb 28, 2026
f8f5cec
Merge pull request #94 from ekailabs/vector-database-integration
sm86 Mar 2, 2026
05d007d
Add @ekai/contexto OpenClaw plugin (v0.1.0)
sm86 Mar 3, 2026
79c67b7
Add @ekai/store JSONL event storage, migrate plugin from inline store
sm86 Mar 3, 2026
fbc7d4e
Fix durationMs overwrite, config schema sync, build order
sm86 Mar 3, 2026
1cb3200
Clean up raw ID conditional and add _error to StoreEvent type
sm86 Mar 3, 2026
fc0fa0a
Remove _error from AppendInput (writer-internal only)
sm86 Mar 3, 2026
069f436
Merge pull request #95 from ekailabs/open-claw-plugin
sm86 Mar 3, 2026
3ce5317
feat: scheduled ingest with deduplication
Shriiii01 Mar 3, 2026
7b2d69b
Update README.md
sm86 Mar 6, 2026
7c471be
Update README.md
sm86 Mar 6, 2026
cf09345
Merge upstream/main into feature/scheduled-ingest-dedup
Shriiii01 Mar 7, 2026
dd08087
Merge upstream/main into feature/scheduled-ingest-dedup
Shriiii01 Mar 7, 2026
ecb58ab
Merge origin/main into feature/scheduled-ingest-dedup, resolve conflicts
Shriiii01 Mar 7, 2026
a2c546a
Merge upstream/main into main
Shriiii01 Mar 7, 2026
292f9f6
Merge main into feature/scheduled-ingest-dedup
Shriiii01 Mar 7, 2026
71ad4dd
Resolve README merge conflict with base branch
Shriiii01 Mar 7, 2026
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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@ GOOGLE_API_KEY=your_key_here
# DATABASE_PATH=data/proxy.db # SQLite file path (default: data/proxy.db relative to gateway cwd)

# Service ports
PORT=3001
# OPENROUTER_PORT=4010 # OpenRouter integration (memory API served here)
# DASHBOARD_PORT=3000 # Dashboard UI

# Memory is embedded in the OpenRouter process (no separate service).
# MEMORY_DB_PATH=./memory.db # SQLite path for memory store (used by OpenRouter)

# Scheduled ingest (deduplicated memory)
# CONVERSATION_LOG_PATH=./data/conversation-log.jsonl
# INGEST_CHECKPOINT_PATH=./data/ingest-checkpoint.json
# MEMORY_INGEST_URL=http://localhost:4010
# INGEST_RATE_LIMIT_MS=1000
# Optional x402 passthrough for OpenRouter access
X402_BASE_URL=x402_supported_provider_url
PRIVATE_KEY=
Expand Down
59 changes: 59 additions & 0 deletions gateway/src/costs/ollama.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
provider: "ollama"
currency: "USD"
unit: "MTok"
models:
# Ollama runs models locally — all costs are zero.
# Users may add custom model entries here if needed.
llama3.3:
input: 0.00
output: 0.00
llama3.2:
input: 0.00
output: 0.00
llama3.1:
input: 0.00
output: 0.00
llama3:
input: 0.00
output: 0.00
gemma3:
input: 0.00
output: 0.00
gemma2:
input: 0.00
output: 0.00
qwen3:
input: 0.00
output: 0.00
qwen2.5-coder:
input: 0.00
output: 0.00
deepseek-r1:
input: 0.00
output: 0.00
deepseek-coder-v2:
input: 0.00
output: 0.00
phi4:
input: 0.00
output: 0.00
phi3:
input: 0.00
output: 0.00
mistral:
input: 0.00
output: 0.00
mixtral:
input: 0.00
output: 0.00
codellama:
input: 0.00
output: 0.00
starcoder2:
input: 0.00
output: 0.00
metadata:
last_updated: "2026-02-03"
source: "https://ollama.com"
notes: "Ollama runs models locally. All API costs are zero — hardware costs are borne by the user."
version: "1.0"
92 changes: 92 additions & 0 deletions gateway/src/domain/services/provider-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { AIProvider } from '../types/provider.js';
import { AnthropicProvider } from '../providers/anthropic-provider.js';
import { OpenAIProvider } from '../providers/openai-provider.js';
import { OpenRouterProvider } from '../providers/openrouter-provider.js';
import { XAIProvider } from '../providers/xai-provider.js';
import { ZAIProvider } from '../providers/zai-provider.js';
import { GoogleProvider } from '../providers/google-provider.js';
import { OllamaProvider } from '../providers/ollama-provider.js';

export enum Provider {
ANTHROPIC = 'anthropic',
OPENAI = 'openai',
OPENROUTER = 'openrouter',
XAI = 'xAI',
ZAI = 'zai',
GOOGLE = 'google',
OLLAMA = 'ollama'
}

export interface ProviderSelectionRule {
match: (modelName: string) => boolean;
}

export interface ProviderPlugin {
id: Provider;
create: () => AIProvider;
selectionRules?: ProviderSelectionRule[];
}

/**
* Central registry for provider creation and selection hints.
* Keeps wiring in one place and reduces per-provider boilerplate.
*/
export class ProviderRegistry {
private readonly instances = new Map<Provider, AIProvider>();

constructor(private readonly plugins: ProviderPlugin[]) {}

listProviders(): Provider[] {
return this.plugins.map(p => p.id);
}

getOrCreateProvider(id: Provider): AIProvider {
if (!this.instances.has(id)) {
const plugin = this.plugins.find(p => p.id === id);
if (!plugin) {
throw new Error(`Unknown provider: ${id}`);
}
this.instances.set(id, plugin.create());
}

const provider = this.instances.get(id);
if (!provider) {
throw new Error(`Failed to create provider: ${id}`);
}
return provider;
}

getAvailableProviders(): Provider[] {
return this.listProviders().filter(id => {
const provider = this.getOrCreateProvider(id);
return provider.isConfigured();
});
}

/**
* Return the first preferred provider whose rule matches the model name and is available.
*/
findPreferredProvider(modelName: string, available: Provider[]): Provider | undefined {
for (const plugin of this.plugins) {
if (!plugin.selectionRules || !available.includes(plugin.id)) continue;
if (plugin.selectionRules.some(rule => rule.match(modelName))) {
return plugin.id;
}
}
return undefined;
}
}

export function createDefaultProviderRegistry(): ProviderRegistry {
const plugins: ProviderPlugin[] = [
{ id: Provider.ANTHROPIC, create: () => new AnthropicProvider() },
{ id: Provider.OPENAI, create: () => new OpenAIProvider() },
{ id: Provider.OPENROUTER, create: () => new OpenRouterProvider() },
{ id: Provider.XAI, create: () => new XAIProvider(), selectionRules: [{ match: model => model.includes('grok-') || model.includes('grok_beta') }] },
{ id: Provider.ZAI, create: () => new ZAIProvider() },
{ id: Provider.GOOGLE, create: () => new GoogleProvider(), selectionRules: [{ match: model => model.toLowerCase().includes('gemini') }] },
{ id: Provider.OLLAMA, create: () => new OllamaProvider(), selectionRules: [{ match: model => model.startsWith('ollama/') }] },
];

return new ProviderRegistry(plugins);
}
167 changes: 167 additions & 0 deletions gateway/src/infrastructure/config/app-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* Centralized application configuration
* All environment variables are validated and accessed through this class
*/

export class AppConfig {
// Server configuration
readonly server = {
port: this.getNumber('PORT', 3001),
environment: this.getString('NODE_ENV', 'development'),
isDevelopment: this.getString('NODE_ENV', 'development') === 'development',
isProduction: this.getString('NODE_ENV', 'development') === 'production',
version: this.getOptionalString('npm_package_version') || 'dev',
};

// x402 Payment configuration
readonly x402 = {
enabled: this.has('PRIVATE_KEY'),
privateKey: this.getOptionalString('PRIVATE_KEY'),
baseUrl: this.getString('X402_BASE_URL', 'https://x402.ekailabs.xyz'),

// Helper methods
get chatCompletionsUrl() {
return `${this.baseUrl}/v1/chat/completions`;
},
get messagesUrl() {
return `${this.baseUrl}/v1/messages`;
},
};

// Provider API Keys
readonly providers = {
anthropic: {
apiKey: this.getOptionalString('ANTHROPIC_API_KEY'),
enabled: this.has('ANTHROPIC_API_KEY'),
},
openai: {
apiKey: this.getOptionalString('OPENAI_API_KEY'),
enabled: this.has('OPENAI_API_KEY'),
},
openrouter: {
apiKey: this.getOptionalString('OPENROUTER_API_KEY'),
enabled: this.has('OPENROUTER_API_KEY'),
},
xai: {
apiKey: this.getOptionalString('XAI_API_KEY'),
enabled: this.has('XAI_API_KEY'),
},
zai: {
apiKey: this.getOptionalString('ZAI_API_KEY'),
enabled: this.has('ZAI_API_KEY'),
},
google: {
apiKey: this.getOptionalString('GOOGLE_API_KEY'),
enabled: this.has('GOOGLE_API_KEY'),
},
ollama: {
baseUrl: this.getString('OLLAMA_BASE_URL', 'http://localhost:11434/v1'),
apiKey: this.getOptionalString('OLLAMA_API_KEY'),
enabled: this.has('OLLAMA_BASE_URL'),
},
};

// Telemetry configuration
readonly telemetry = {
enabled: this.getBoolean('ENABLE_TELEMETRY', true),
endpoint: this.getOptionalString('TELEMETRY_ENDPOINT'),
};

// OpenRouter-specific configuration
readonly openrouter = {
skipPricingRefresh: this.getBoolean('SKIP_OPENROUTER_PRICING_REFRESH', false),
pricingTimeoutMs: this.getNumber('OPENROUTER_PRICING_TIMEOUT_MS', 4000),
pricingRetries: this.getNumber('OPENROUTER_PRICING_RETRIES', 2),
};

// Feature flags
readonly features = {
usageTracking: this.getBoolean('ENABLE_USAGE_TRACKING', true),
};

// Memory service configuration (FIFO file backend by default)
readonly memory = {
backend: this.getString('MEMORY_BACKEND', 'file'),
maxItems: this.getNumber('MEMORY_MAX_ITEMS', 20),
} as const;

// Helper methods
private has(key: string): boolean {
return !!process.env[key];
}

private getString(key: string, defaultValue: string): string;
private getString(key: string): string;
private getString(key: string, defaultValue?: string): string {
const value = process.env[key] || defaultValue;
if (value === undefined) {
throw new Error(`Missing required environment variable: ${key}`);
}
return value;
}

private getOptionalString(key: string): string | undefined {
return process.env[key];
}

private getNumber(key: string, defaultValue: number): number {
const value = process.env[key];
if (!value) return defaultValue;
const num = parseInt(value, 10);
if (isNaN(num)) {
throw new Error(`Invalid number for environment variable ${key}: ${value}`);
}
return num;
}

private getBoolean(key: string, defaultValue: boolean): boolean {
const value = process.env[key];
if (!value) return defaultValue;
return value.toLowerCase() === 'true' || value === '1';
}

/**
* Validate that at least one authentication method is configured
*/
validate(): void {
const hasApiKeys = Object.values(this.providers).some(p => p.enabled);
const hasX402 = this.x402.enabled;

if (!hasApiKeys && !hasX402) {
throw new Error(
'No authentication configured. Set either:\n' +
' 1. At least one provider API key (ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.)\n' +
' 2. PRIVATE_KEY for x402 payment mode'
);
}
}

/**
* Get human-readable mode description
*/
getMode(): 'x402-only' | 'hybrid' | 'byok' {
const hasApiKeys = Object.values(this.providers).some(p => p.enabled);
const hasX402 = this.x402.enabled;

if (!hasApiKeys && hasX402) return 'x402-only';
if (hasApiKeys && hasX402) return 'hybrid';
return 'byok';
}
}

// Singleton instance
let configInstance: AppConfig | null = null;

export function getConfig(): AppConfig {
if (!configInstance) {
configInstance = new AppConfig();
configInstance.validate();
}
return configInstance;
}

// For testing - reset config
export function resetConfig(): void {
configInstance = null;
}

Loading