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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,8 @@
/application.yml
/.remember/
/.m2repo/
/mcp-filesystem/

# Local Serena run memories
/.serena/memories/run/application-start-command.md
/.serena/memories/run/deploy_command_convention.md
13 changes: 11 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app

ARG APP_VERSION=1.1.0-SNAPSHOT
ARG APP_VERSION=1.1.1-SNAPSHOT

# Copy pom.xml and all modules
COPY pom.xml .
COPY opendaimon-common/pom.xml ./opendaimon-common/
COPY opendaimon-spring-ai/pom.xml ./opendaimon-spring-ai/
COPY opendaimon-mcp/pom.xml ./opendaimon-mcp/
COPY opendaimon-spring-boot-starter/pom.xml ./opendaimon-spring-boot-starter/
COPY opendaimon-ui/pom.xml ./opendaimon-ui/
COPY opendaimon-rest/pom.xml ./opendaimon-rest/
Expand All @@ -18,6 +19,7 @@ COPY opendaimon-app/pom.xml ./opendaimon-app/
# Copy source code
COPY opendaimon-common/src ./opendaimon-common/src
COPY opendaimon-spring-ai/src ./opendaimon-spring-ai/src
COPY opendaimon-mcp/src ./opendaimon-mcp/src
COPY opendaimon-ui/src ./opendaimon-ui/src
COPY opendaimon-rest/src ./opendaimon-rest/src
COPY opendaimon-telegram/src ./opendaimon-telegram/src
Expand All @@ -31,11 +33,18 @@ RUN mvn -Drevision=${APP_VERSION} clean package -DskipTests -B
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app

ARG APP_VERSION=1.1.0-SNAPSHOT
RUN apk add --no-cache nodejs npm \
&& npm install -g @modelcontextprotocol/server-filesystem@0.2.0 \
&& npm install -g zod-to-json-schema@3.23.5 \
&& npm cache clean --force

ARG APP_VERSION=1.1.1-SNAPSHOT

# Copy JAR from build stage
COPY --from=build /app/opendaimon-app/target/opendaimon-app-${APP_VERSION}.jar app.jar

RUN mkdir -p /app/mcp-filesystem

# Expose port
EXPOSE 8080

Expand Down
7 changes: 6 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
- .env
container_name: open-daimon-app
ports:
- "8080:8080"
- "8081:8080"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
Expand Down Expand Up @@ -42,12 +42,17 @@ services:
- MINIO_ENDPOINT=http://minio:9000
# Redis (use service name from docker-compose)
- REDIS_HOST=redis
# MCP filesystem server (admin-only in OpenDaimon tool exposure). Enabled by default.
- MCP_CLIENT_ENABLED=${MCP_CLIENT_ENABLED:-true}
- MCP_FILESYSTEM_COMMAND=mcp-server-filesystem
- MCP_FILESYSTEM_ROOT=/app/mcp-filesystem
# Config override: place application.yml next to docker-compose.yml.
# optional: prefix = if file is absent, uses bundled defaults.
- SPRING_CONFIG_ADDITIONAL_LOCATION=optional:file:/app/config/application.yml
# Mount project root so Spring Boot can find application.yml if it exists
volumes:
- ./:/app/config/:ro
- ./mcp-filesystem:/app/mcp-filesystem
depends_on:
postgres:
condition: service_healthy
Expand Down
2 changes: 1 addition & 1 deletion docs/codex/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ service_tier = "fast"
trust_level = "trusted"

[features]
codex_hooks = true
hooks = true
memories = true
terminal_resize_reflow = true

Expand Down
2 changes: 2 additions & 0 deletions docs/tariffs-and-models.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ Only free models from the API whose id is in this list (and passes other filters
| openrouter/free | CHAT, TOOL_CALLING, SUMMARIZATION, FREE (proxy) |
| qwen/qwen3-4b:free | CHAT, SUMMARIZATION, FREE |
| qwen/qwen3-coder:free | CHAT, SUMMARIZATION, FREE |
| google/gemma-4-31b-it | CHAT, TOOL_CALLING, WEB, VISION, STRUCTURED_OUTPUT, THINKING (paid, ADMIN/VIP) |
| deepseek/deepseek-v4-flash | CHAT, TOOL_CALLING, WEB, STRUCTURED_OUTPUT, THINKING (paid, ADMIN/VIP) |
| qwen/qwen3-vl-235b-a22b-thinking | CHAT, VISION, SUMMARIZATION (paid) |
| qwen/qwen3-vl-30b-a3b-thinking | CHAT, VISION, SUMMARIZATION (paid) |
| stepfun/step-3.5-flash:free | CHAT, TOOL_CALLING, SUMMARIZATION, FREE |
Expand Down
Empty file added mcp-filesystem/.gitkeep
Empty file.
6 changes: 6 additions & 0 deletions opendaimon-app/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.github.ngirchev</groupId>
<artifactId>opendaimon-mcp</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.github.ngirchev</groupId>
<artifactId>opendaimon-gateway-mock</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.github.ngirchev.opendaimon.it.telegram;

import io.github.ngirchev.opendaimon.common.SupportedLanguages;
import io.github.ngirchev.opendaimon.common.config.CoreCommonProperties;
import io.github.ngirchev.opendaimon.common.config.CoreFlywayConfig;
import io.github.ngirchev.opendaimon.common.config.CoreJpaConfig;
Expand Down Expand Up @@ -171,8 +170,8 @@ void shouldLazilyCreateGroupOnFirstInteraction() {
assertEquals(chatId, groupOwner.getTelegramId());
assertEquals("Fresh team", groupOwner.getTitle());
assertEquals("supergroup", groupOwner.getType());
assertEquals(SupportedLanguages.DEFAULT_LANGUAGE, groupOwner.getLanguageCode(),
"language defaults to DEFAULT_LANGUAGE on creation; /language can override later");
assertNull(groupOwner.getLanguageCode(),
"language stays unset on creation so command mapping can fall back to the invoker until /language runs");
assertNull(groupOwner.getPreferredModelId(), "model is unset until /model runs");

Optional<TelegramGroup> found = telegramGroupRepository.findByTelegramId(chatId);
Expand Down
31 changes: 31 additions & 0 deletions opendaimon-app/src/main/resources/application-max.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ open-daimon:
- openai/gpt-5.4
- openai/gpt-5-nano
- z-ai/glm-5-turbo
- google/gemma-4-31b-it
- deepseek/deepseek-v4-flash
- google/gemma-3-4b
- qwen/qwen3-4b
- qwen/qwen3-vl-235b-a22b-thinking
Expand Down Expand Up @@ -107,6 +109,35 @@ open-daimon:
allowed-roles:
- ADMIN
- VIP
- name: "google/gemma-4-31b-it"
capabilities:
- CHAT
- TOOL_CALLING
- WEB
- VISION
- STRUCTURED_OUTPUT
- THINKING
provider-type: OPENAI
priority: 2
max-output-tokens: 16000
max-reasoning-tokens: 8000
allowed-roles:
- ADMIN
- VIP
- name: "deepseek/deepseek-v4-flash"
capabilities:
- CHAT
- TOOL_CALLING
- WEB
- STRUCTURED_OUTPUT
- THINKING
provider-type: OPENAI
priority: 2
max-output-tokens: 16000
max-reasoning-tokens: 8000
allowed-roles:
- ADMIN
- VIP
# qwen3.5 with thinking enabled (max profile only)
- name: "qwen3.5"
capabilities:
Expand Down
1 change: 1 addition & 0 deletions opendaimon-app/src/main/resources/application-mock.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ open-daimon:
newthread-enabled: true
history-enabled: true
threads-enabled: true
tools-enabled: true
message-coalescing:
enabled: true
wait-window-ms: 1200
Expand Down
67 changes: 67 additions & 0 deletions opendaimon-app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ open-daimon:
language-enabled: true
model-enabled: true
mode-enabled: true
tools-enabled: true
cache:
redis-enabled: false # FEATURE FLAG - enable distributed Redis cache for session data
message-coalescing:
Expand All @@ -149,6 +150,17 @@ open-daimon:
emails: ${REST_ACCESS_REGULAR_EMAILS:} # e.g. "user@example.com"
ui:
enabled: true
mcp:
enabled: true
source-display-names:
filesystem: "@modelcontextprotocol/server-filesystem@0.2.0"
tool-access:
# External MCP tools not matched by a rule are available to all allowed tiers.
# Filesystem MCP tools are restricted to ADMIN by default.
default-roles: [ ADMIN, VIP, REGULAR ]
rules:
- name-pattern: "^(?:[A-Za-z0-9_]+_)?(read_file|read_text_file|read_media_file|read_multiple_files|write_file|edit_file|create_directory|list_directory|list_directory_with_sizes|directory_tree|move_file|search_files|get_file_info|list_allowed_directories)$"
roles: [ ADMIN ]
ai:
spring-ai:
enabled: true
Expand All @@ -168,6 +180,8 @@ open-daimon:
- roles: [ ADMIN, VIP ]
include-model-ids:
- google/gemini-2.5-flash
- google/gemma-4-31b-it
- deepseek/deepseek-v4-flash
- openai/gpt-5-nano
- z-ai/glm-5-turbo
- qwen/qwen3-vl-235b-a22b-thinking
Expand Down Expand Up @@ -246,6 +260,33 @@ open-daimon:
allowed-roles:
- ADMIN
- VIP
- name: "google/gemma-4-31b-it"
capabilities:
- CHAT
- TOOL_CALLING
- WEB
- VISION
- STRUCTURED_OUTPUT
- THINKING
provider-type: OPENAI
priority: 1
max-reasoning-tokens: 4000
allowed-roles:
- ADMIN
- VIP
- name: "deepseek/deepseek-v4-flash"
capabilities:
- CHAT
- TOOL_CALLING
- WEB
- STRUCTURED_OUTPUT
- THINKING
provider-type: OPENAI
priority: 1
max-reasoning-tokens: 4000
allowed-roles:
- ADMIN
- VIP
# Top 3 OpenRouter free models (explicit capabilities; others from include-model-ids get capabilities from API).
- name: "openrouter/free"
capabilities:
Expand Down Expand Up @@ -361,6 +402,32 @@ spring:
timeout: 2s

ai:
mcp:
client:
enabled: ${MCP_CLIENT_ENABLED:false}
name: open-daimon
version: ${OPEN_DAIMON_VERSION:dev}
type: SYNC
request-timeout: 30s
# Filesystem MCP runs inside the OpenDaimon container/process and sees only
# that filesystem plus explicitly mounted volumes. OpenDaimon exposes external
# MCP tools to ADMIN users only.
stdio:
connections:
filesystem:
command: sh
args:
- -c
- exec ${MCP_FILESYSTEM_COMMAND:npx -y @modelcontextprotocol/server-filesystem} ${MCP_FILESYSTEM_ROOT:/app/mcp-filesystem}
# sse:
# connections:
# remote:
# url: http://localhost:9000
# streamable-http:
# connections:
# remote-http:
# url: http://localhost:9001
# endpoint: /mcp
ollama:
base-url: ${OLLAMA_BASE_URL:http://localhost:11434}
request-timeout: 600s
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.github.ngirchev.opendaimon.common.ai.tool;

import io.github.ngirchev.opendaimon.bulkhead.model.UserPriority;

public record ExternalToolAccessContext(
Long userId,
UserPriority userPriority
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.github.ngirchev.opendaimon.common.ai.tool;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public interface ExternalToolCatalogService {

List<ExternalToolDescriptor> listAvailableTools(ExternalToolAccessContext context);

default List<ExternalToolSourceDescriptor> listAvailableToolSources(ExternalToolAccessContext context) {
Map<String, List<ExternalToolDescriptor>> toolsBySource = listAvailableTools(context).stream()
.collect(Collectors.groupingBy(
ExternalToolCatalogService::sourceKey,
java.util.LinkedHashMap::new,
Collectors.toList()));
return toolsBySource.entrySet().stream()
.map(entry -> new ExternalToolSourceDescriptor(
sourceName(entry.getValue().getFirst()),
entry.getValue().getFirst().sourceType(),
entry.getValue()))
.toList();
}

private static String sourceKey(ExternalToolDescriptor tool) {
return tool.sourceType().name() + "\u0000" + sourceName(tool);
}

private static String sourceName(ExternalToolDescriptor tool) {
return tool.sourceName() != null && !tool.sourceName().isBlank()
? tool.sourceName()
: "external";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.ngirchev.opendaimon.common.ai.tool;

public record ExternalToolDescriptor(
String name,
String description,
String sourceName,
ExternalToolSourceType sourceType
) {

public ExternalToolDescriptor(String name, String description) {
this(name, description, null, ExternalToolSourceType.EXTERNAL);
}

public ExternalToolDescriptor(String name, String description, String sourceName) {
this(name, description, sourceName, ExternalToolSourceType.EXTERNAL);
}

public ExternalToolDescriptor {
sourceType = sourceType != null ? sourceType : ExternalToolSourceType.EXTERNAL;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.github.ngirchev.opendaimon.common.ai.tool;

import java.util.List;

public record ExternalToolSourceDescriptor(
String name,
ExternalToolSourceType sourceType,
List<ExternalToolDescriptor> tools
) {

public ExternalToolSourceDescriptor(String name, List<ExternalToolDescriptor> tools) {
this(name, ExternalToolSourceType.EXTERNAL, tools);
}

public ExternalToolSourceDescriptor {
sourceType = sourceType != null ? sourceType : ExternalToolSourceType.EXTERNAL;
tools = tools != null ? List.copyOf(tools) : List.of();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.github.ngirchev.opendaimon.common.ai.tool;

public enum ExternalToolSourceType {
BUILT_IN,
MCP,
EXTERNAL
}
Loading
Loading