dhti-cli 0.1.1 → 0.3.1

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.
package/README.md CHANGED
@@ -234,6 +234,8 @@ You can remove the services by: `dhti-cli docker -d`
234
234
  ## Give us a star ⭐️
235
235
  If you find this project useful, give us a star. It helps others discover the project.
236
236
 
237
+ ## [Details of CLI Commands](/notes/README.md)
238
+
237
239
  ## Contributors
238
240
 
239
241
  * [Bell Eapen](https://nuchange.ca) | [![Twitter Follow](https://img.shields.io/twitter/follow/beapen?style=social)](https://twitter.com/beapen)
@@ -2,18 +2,26 @@ import { Args, Command, Flags } from '@oclif/core';
2
2
  import yaml from 'js-yaml';
3
3
  import fs from 'node:fs';
4
4
  import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
5
7
  export default class Compose extends Command {
6
8
  static args = {
7
9
  op: Args.string({ description: 'Operation to perform (add, delete, read or reset)' }),
8
10
  };
9
11
  static description = 'Generates a docker-compose.yml file from a list of modules';
10
- static examples = [
11
- '<%= config.bin %> <%= command.id %>',
12
- ];
12
+ static examples = ['<%= config.bin %> <%= command.id %>'];
13
13
  static flags = {
14
- file: Flags.string({ char: 'f', default: `${os.homedir()}/dhti/docker-compose.yml`, description: 'Full path to the docker compose file to read from. Creates if it does not exist' }),
14
+ file: Flags.string({
15
+ char: 'f',
16
+ default: `${os.homedir()}/dhti/docker-compose.yml`,
17
+ description: 'Full path to the docker compose file to read from. Creates if it does not exist',
18
+ }),
15
19
  // flag with a value (-n, --name=VALUE)
16
- module: Flags.string({ char: 'm', description: 'Modules to add from ( langserve, openmrs, ollama, langfuse, cqlFhir, redis, neo4j and mcpFhir)', multiple: true }),
20
+ module: Flags.string({
21
+ char: 'm',
22
+ description: 'Modules to add from ( langserve, openmrs, ollama, langfuse, cqlFhir, redis, neo4j and mcpFhir)',
23
+ multiple: true,
24
+ }),
17
25
  };
18
26
  static init = () => {
19
27
  // Create ${os.homedir()}/dhti if it does not exist
@@ -27,6 +35,10 @@ export default class Compose extends Command {
27
35
  };
28
36
  async run() {
29
37
  const { args, flags } = await this.parse(Compose);
38
+ // Resolve resources directory for both dev (src) and packaged (dist)
39
+ const __filename = fileURLToPath(import.meta.url);
40
+ const __dirname = path.dirname(__filename);
41
+ const RESOURCES_DIR = path.resolve(__dirname, '../resources');
30
42
  // console.log('args', args) //args { op: 'add' }
31
43
  // console.log('flags', flags) //flags { module: [ 'default', 'langserve', 'redis' ] }
32
44
  const openmrs = ['gateway', 'frontend', 'backend', 'openmrs-db'];
@@ -51,10 +63,10 @@ export default class Compose extends Command {
51
63
  ollama,
52
64
  openmrs,
53
65
  redis,
54
- webui
66
+ webui,
55
67
  };
56
68
  try {
57
- const masterData = yaml.load(fs.readFileSync('src/resources/docker-compose-master.yml', 'utf8'));
69
+ const masterData = yaml.load(fs.readFileSync(path.join(RESOURCES_DIR, 'docker-compose-master.yml'), 'utf8'));
58
70
  let existingData = { services: {}, version: '3.8' };
59
71
  if (fs.existsSync(flags.file)) {
60
72
  existingData = yaml.load(fs.readFileSync(flags.file, 'utf8'));
@@ -2,28 +2,44 @@ import { Args, Command, Flags } from '@oclif/core';
2
2
  import { exec } from 'node:child_process';
3
3
  import fs from 'node:fs';
4
4
  import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
5
7
  export default class Conch extends Command {
6
8
  static args = {
7
9
  op: Args.string({ description: 'Operation to perform (install, uninstall or dev)' }),
8
10
  };
9
11
  static description = 'Install or uninstall conchs to create a Docker image';
10
- static examples = [
11
- '<%= config.bin %> <%= command.id %>',
12
- ];
12
+ static examples = ['<%= config.bin %> <%= command.id %>'];
13
13
  static flags = {
14
- branch: Flags.string({ char: 'b', default: "develop", description: 'Branch to install from' }),
15
- container: Flags.string({ char: 'c', default: "dhti-frontend-1", description: 'Name of the container to copy the conch to while in dev mode' }),
16
- dev: Flags.string({ char: 'd', default: "none", description: 'Dev folder to install' }),
17
- git: Flags.string({ char: 'g', default: "none", description: 'Github repository to install' }),
18
- image: Flags.string({ char: 'i', default: "openmrs/openmrs-reference-application-3-frontend:3.0.0-beta.17", description: 'Base image to use for the conch' }),
14
+ branch: Flags.string({ char: 'b', default: 'develop', description: 'Branch to install from' }),
15
+ container: Flags.string({
16
+ char: 'c',
17
+ default: 'dhti-frontend-1',
18
+ description: 'Name of the container to copy the conch to while in dev mode',
19
+ }),
20
+ dev: Flags.string({ char: 'd', default: 'none', description: 'Dev folder to install' }),
21
+ git: Flags.string({ char: 'g', default: 'none', description: 'Github repository to install' }),
22
+ image: Flags.string({
23
+ char: 'i',
24
+ default: 'openmrs/openmrs-reference-application-3-frontend:3.0.0-beta.17',
25
+ description: 'Base image to use for the conch',
26
+ }),
19
27
  name: Flags.string({ char: 'n', description: 'Name of the elixir' }),
20
- repoVersion: Flags.string({ char: 'v', default: "1.0.0", description: 'Version of the conch' }),
21
- workdir: Flags.string({ char: 'w', default: `${os.homedir()}/dhti`, description: 'Working directory to install the conch' }),
28
+ repoVersion: Flags.string({ char: 'v', default: '1.0.0', description: 'Version of the conch' }),
29
+ workdir: Flags.string({
30
+ char: 'w',
31
+ default: `${os.homedir()}/dhti`,
32
+ description: 'Working directory to install the conch',
33
+ }),
22
34
  };
23
35
  async run() {
24
36
  const { args, flags } = await this.parse(Conch);
37
+ // Resolve resources directory for both dev (src) and packaged (dist)
38
+ const __filename = fileURLToPath(import.meta.url);
39
+ const __dirname = path.dirname(__filename);
40
+ const RESOURCES_DIR = path.resolve(__dirname, '../resources');
25
41
  if (!flags.name) {
26
- console.log("Please provide a name for the conch");
42
+ console.log('Please provide a name for the conch');
27
43
  this.exit(1);
28
44
  }
29
45
  // if arg is dev then copy to docker as below
@@ -50,14 +66,14 @@ export default class Conch extends Command {
50
66
  });
51
67
  }
52
68
  catch (error) {
53
- console.log("Error copying conch to container", error);
69
+ console.log('Error copying conch to container', error);
54
70
  }
55
71
  }
56
72
  // Create a directory to install the elixir
57
73
  if (!fs.existsSync(`${flags.workdir}/conch`)) {
58
74
  fs.mkdirSync(`${flags.workdir}/conch`);
59
75
  }
60
- fs.cpSync('src/resources/spa', `${flags.workdir}/conch`, { recursive: true });
76
+ fs.cpSync(path.join(RESOURCES_DIR, 'spa'), `${flags.workdir}/conch`, { recursive: true });
61
77
  // Rewrite files
62
78
  const rewrite = () => {
63
79
  flags.name = flags.name ?? 'openmrs-esm-genai';
@@ -77,16 +93,19 @@ export default class Conch extends Command {
77
93
  fs.writeFileSync(`${flags.workdir}/conch/def/spa-assemble-config.json`, JSON.stringify(spaAssembleConfig, null, 2));
78
94
  // Read and process Dockerfile
79
95
  let dockerfile = fs.readFileSync(`${flags.workdir}/conch/Dockerfile`, 'utf8');
80
- dockerfile = dockerfile.replaceAll('conch', flags.name).replaceAll('version', flags.repoVersion).replaceAll('server-image', flags.image);
96
+ dockerfile = dockerfile
97
+ .replaceAll('conch', flags.name)
98
+ .replaceAll('version', flags.repoVersion)
99
+ .replaceAll('server-image', flags.image);
81
100
  fs.writeFileSync(`${flags.workdir}/conch/Dockerfile`, dockerfile);
82
101
  // Read routes.json
83
102
  const routes = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/${flags.name}/src/routes.json`, 'utf8'));
84
103
  // Add to routes.registry.json
85
104
  const registry = JSON.parse(fs.readFileSync(`${flags.workdir}/conch/def/routes.registry.json`, 'utf8'));
86
105
  if (args.op === 'install')
87
- registry[(flags.name).replace('openmrs-', '@openmrs/')] = routes;
106
+ registry[flags.name.replace('openmrs-', '@openmrs/')] = routes;
88
107
  if (args.op === 'uninstall')
89
- delete registry[(flags.name).replace('openmrs-', '@openmrs/')];
108
+ delete registry[flags.name.replace('openmrs-', '@openmrs/')];
90
109
  fs.writeFileSync(`${flags.workdir}/conch/def/routes.registry.json`, JSON.stringify(registry, null, 2));
91
110
  };
92
111
  if (flags.git !== 'none') {
@@ -3,6 +3,7 @@ import { exec } from 'node:child_process';
3
3
  import fs from 'node:fs';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
6
7
  export default class Elixir extends Command {
7
8
  static args = {
8
9
  op: Args.string({ description: 'Operation to perform (install, uninstall or dev)' }),
@@ -34,6 +35,10 @@ export default class Elixir extends Command {
34
35
  };
35
36
  async run() {
36
37
  const { args, flags } = await this.parse(Elixir);
38
+ // Resolve resources directory for both dev (src) and packaged (dist)
39
+ const __filename = fileURLToPath(import.meta.url);
40
+ const __dirname = path.dirname(__filename);
41
+ const RESOURCES_DIR = path.resolve(__dirname, '../resources');
37
42
  if (!flags.name) {
38
43
  console.log('Please provide a name for the elixir');
39
44
  this.exit(1);
@@ -69,7 +74,7 @@ export default class Elixir extends Command {
69
74
  if (!fs.existsSync(`${flags.workdir}/elixir`)) {
70
75
  fs.mkdirSync(`${flags.workdir}/elixir`);
71
76
  }
72
- fs.cpSync('src/resources/genai', `${flags.workdir}/elixir`, { recursive: true });
77
+ fs.cpSync(path.join(RESOURCES_DIR, 'genai'), `${flags.workdir}/elixir`, { recursive: true });
73
78
  // if whl is not none, copy the whl file to thee whl directory
74
79
  if (flags.whl !== 'none') {
75
80
  if (!fs.existsSync(`${flags.workdir}/elixir/whl/`)) {
@@ -105,13 +110,13 @@ mcp_server.add_tool(${expoName}_mcp_tool) # type: ignore
105
110
  `;
106
111
  const newCliImport = fs
107
112
  .readFileSync(`${flags.workdir}/elixir/app/server.py`, 'utf8')
108
- .replace('#DHTI_CLI_IMPORT', `#DHTI_CLI_IMPORT\n${CliImport}`);
113
+ .replace('# DHTI_CLI_IMPORT', `#DHTI_CLI_IMPORT\n${CliImport}`);
109
114
  const langfuseRoute = `add_routes(app, ${expoName}_chain.with_config(config), path="/langserve/${expoName}")`;
110
- const newLangfuseRoute = newCliImport.replace('#DHTI_LANGFUSE_ROUTE', `#DHTI_LANGFUSE_ROUTE\n ${langfuseRoute}`);
115
+ const newLangfuseRoute = newCliImport.replace('# DHTI_LANGFUSE_ROUTE', `#DHTI_LANGFUSE_ROUTE\n ${langfuseRoute}`);
111
116
  const normalRoute = `add_routes(app, ${expoName}_chain, path="/langserve/${expoName}")`;
112
- const newNormalRoute = newLangfuseRoute.replace('#DHTI_NORMAL_ROUTE', `#DHTI_NORMAL_ROUTE\n ${normalRoute}`);
117
+ const newNormalRoute = newLangfuseRoute.replace('# DHTI_NORMAL_ROUTE', `#DHTI_NORMAL_ROUTE\n ${normalRoute}`);
113
118
  const commonRoutes = `\nadd_invokes(app, path="/langserve/${expoName}")\nadd_services(app, path="/langserve/${expoName}")`;
114
- const finalRoute = newNormalRoute.replace('#DHTI_COMMON_ROUTE', `#DHTI_COMMON_ROUTES${commonRoutes}`);
119
+ const finalRoute = newNormalRoute.replace('# DHTI_COMMON_ROUTE', `#DHTI_COMMON_ROUTES${commonRoutes}`);
115
120
  // if args.op === install, add the line to the pyproject.toml file
116
121
  if (args.op === 'install') {
117
122
  fs.writeFileSync(`${flags.workdir}/elixir/pyproject.toml`, newPyproject);
@@ -145,12 +145,13 @@ export default class Mimic extends Command {
145
145
  body: mimic_request,
146
146
  headers: {
147
147
  'Content-Type': 'application/fhir+json',
148
- 'Prefer': 'respond-async'
148
+ Prefer: 'respond-async',
149
149
  },
150
150
  method: 'POST',
151
151
  });
152
152
  if (!response.ok) {
153
153
  console.error(`Error: ${response.status} ${response.statusText}`);
154
+ return;
154
155
  }
155
156
  }
156
157
  }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from './utils/index.js';
1
+ export { Cards, handleBundle } from './utils/index.js';
2
2
  export { run } from '@oclif/core';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export * from './utils/index.js';
1
+ export { Cards, handleBundle } from './utils/index.js';
2
2
  export { run } from '@oclif/core';
@@ -0,0 +1,238 @@
1
+ version: "3.7"
2
+
3
+ services:
4
+ gateway:
5
+ image: beapen/dhti-gateway:latest
6
+ restart: "unless-stopped"
7
+ pull_policy: always
8
+ depends_on:
9
+ - frontend
10
+ - backend
11
+ ports:
12
+ - "80:80"
13
+ - "9000:80"
14
+
15
+
16
+ frontend:
17
+ image: openmrs/openmrs-reference-application-3-frontend:3.0.0-beta.17
18
+ # image: openmrs/openmrs-reference-application-3-frontend:${TAG:-3.0.0-beta.17} # dev3, qa, demo, 3.0.0-beta.18
19
+ ports:
20
+ - "8003:80"
21
+ restart: "unless-stopped"
22
+ pull_policy: always
23
+ environment:
24
+ SPA_PATH: /openmrs/spa
25
+ API_URL: /openmrs
26
+ SPA_CONFIG_URLS: /openmrs/spa/config-core_demo.json
27
+ SPA_DEFAULT_LOCALE:
28
+ healthcheck:
29
+ test: ["CMD", "curl", "-f", "http://localhost/"]
30
+ timeout: 5s
31
+ depends_on:
32
+ - backend
33
+ # volumes:
34
+ # - ./spa:/usr/share/nginx/html
35
+
36
+ backend:
37
+ image: openmrs/openmrs-reference-application-3-backend:${TAG:-3.0.0-beta.17} # dev3, qa, demo, 3.0.0-beta.18
38
+ ports:
39
+ - "8002:8080"
40
+ restart: "unless-stopped"
41
+ depends_on:
42
+ - openmrs-db
43
+ environment:
44
+ OMRS_CONFIG_MODULE_WEB_ADMIN: "true"
45
+ OMRS_CONFIG_AUTO_UPDATE_DATABASE: "true"
46
+ OMRS_CONFIG_CREATE_TABLES: "true"
47
+ OMRS_CONFIG_CONNECTION_SERVER: openmrs-db
48
+ OMRS_CONFIG_CONNECTION_DATABASE: openmrs
49
+ OMRS_CONFIG_CONNECTION_USERNAME: ${OPENMRS_DB_USER:-openmrs}
50
+ OMRS_CONFIG_CONNECTION_PASSWORD: ${OPENMRS_DB_PASSWORD:-openmrs}
51
+ healthcheck:
52
+ test: ["CMD", "curl", "-f", "http://localhost:8080/openmrs"]
53
+ timeout: 5s
54
+ volumes:
55
+ - openmrs-data:/openmrs/data
56
+
57
+ openmrs-db:
58
+ image: mariadb:10.11.7
59
+ restart: "unless-stopped"
60
+ command: "mysqld --character-set-server=utf8 --collation-server=utf8_general_ci"
61
+ healthcheck:
62
+ test: "mysql --user=${OMRS_DB_USER:-openmrs} --password=${OMRS_DB_PASSWORD:-openmrs} --execute \"SHOW DATABASES;\""
63
+ interval: 3s
64
+ timeout: 1s
65
+ retries: 5
66
+ environment:
67
+ MYSQL_DATABASE: openmrs
68
+ MYSQL_USER: ${OMRS_DB_USER:-openmrs}
69
+ MYSQL_PASSWORD: ${OMRS_DB_PASSWORD:-openmrs}
70
+ MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-openmrs}
71
+ volumes:
72
+ - openmrs-db:/var/lib/mysql
73
+
74
+ langserve:
75
+ image: beapen/genai:latest
76
+ pull_policy: always
77
+ ports:
78
+ - "8001:8001"
79
+ restart: "unless-stopped"
80
+ environment:
81
+ - OLLAMA_SERVER_URL==http://ollama:11434
82
+ - OLLAMA_WEBUI=http://ollama-webui:8080
83
+ - LANGFUSE_HOST=http://langfuse:3000
84
+ - LANGFUSE_PUBLIC_KEY=pk-lf-abcd
85
+ - LANGFUSE_SECRET_KEY=sk-lf-abcd
86
+
87
+ ollama:
88
+ image: ollama/ollama:latest
89
+ ports:
90
+ - 11434:11434
91
+ volumes:
92
+ - ollama-code:/code
93
+ - ollama-root:/root/.ollama
94
+ tty: true
95
+ restart: "unless-stopped"
96
+ environment:
97
+ - OLLAMA_ORIGINS=*
98
+
99
+ ollama-webui:
100
+ image: ghcr.io/open-webui/open-webui:main
101
+ volumes:
102
+ - ollama-webui:/app/backend/data
103
+ depends_on:
104
+ - ollama
105
+ ports:
106
+ - 8080:8080
107
+ environment:
108
+ - '/ollama/api=http://ollama:11434/api'
109
+ extra_hosts:
110
+ - host.docker.internal:host-gateway
111
+ restart: unless-stopped
112
+
113
+ fhir:
114
+ image: alphora/cqf-ruler:0.14.0 # includes cql
115
+ ports:
116
+ - 8005:8080
117
+ restart: "unless-stopped"
118
+ depends_on:
119
+ - postgres-db
120
+ environment:
121
+ - "spring.datasource.url=jdbc:postgresql://postgres-db:5432/postgres"
122
+ - "spring.datasource.username=postgres"
123
+ - "spring.datasource.password=postgres"
124
+ - "spring.datasource.driverClassName=org.postgresql.Driver"
125
+ - "spring.jpa.properties.hibernate.dialect=ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect"
126
+ - "hapi.fhir.fhir_version=R4"
127
+ - "hapi.fhir.cors.allowed-origins=*"
128
+ - "hapi.fhir.cors.allowCredentials=true"
129
+ - "hapi.fhir.bulkdata.enabled=true"
130
+ - "hapi.fhir.bulk_export_enabled=true"
131
+ - "hapi.fhir.bulk_import_enabled=true"
132
+ - "hapi.fhir.enforce_referential_integrity_on_write=false"
133
+ - "hapi.fhir.enforce_referential_integrity_on_delete=false"
134
+
135
+ mcp-fhir:
136
+ image: beapen/fhir-mcp-server:1.0
137
+ ports:
138
+ - 8006:8000
139
+ restart: "unless-stopped"
140
+ depends_on:
141
+ - fhir
142
+ environment:
143
+ - FHIR_SERVER_BASE_URL="http://fhir:8005/baseR4"
144
+ - FHIR_SERVER_SCOPES="*"
145
+ - FHIR_SERVER_ACCESS_TOKEN="none"
146
+
147
+ cql-elm:
148
+ image: cqframework/cql-translation-service:latest
149
+ ports:
150
+ - 8091:8080
151
+ restart: "unless-stopped"
152
+
153
+ cql-web:
154
+ image: beapen/cql_runner:latest
155
+ ports:
156
+ - 8092:80
157
+ restart: "unless-stopped"
158
+ depends_on:
159
+ - fhir
160
+
161
+ langfuse:
162
+ image: ghcr.io/langfuse/langfuse:latest
163
+ depends_on:
164
+ - postgres-db
165
+ ports:
166
+ - "3000:3000"
167
+ environment:
168
+ - DATABASE_URL=postgresql://postgres:postgres@postgres-db:5432/postgres
169
+ - NEXTAUTH_SECRET=mysecret
170
+ - SALT=mysalt
171
+ - NEXTAUTH_URL=http://langfuse:3000
172
+ - TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false}
173
+ - LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=${LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES:-false}
174
+
175
+ postgres-db:
176
+ image: postgres
177
+ restart: "unless-stopped"
178
+ environment:
179
+ - POSTGRES_USER=postgres
180
+ - POSTGRES_PASSWORD=postgres
181
+ - POSTGRES_DB=postgres
182
+ ports:
183
+ - 5432:5432
184
+ volumes:
185
+ - postgres-db:/var/lib/postgresql/data
186
+
187
+ redis:
188
+ image: redislabs/redisearch:2.8.8
189
+ ports:
190
+ - 6379:6379
191
+ restart: "unless-stopped"
192
+ volumes:
193
+ - redis-db:/data
194
+
195
+ redis-commander:
196
+ image: rediscommander/redis-commander:latest
197
+ restart: "unless-stopped"
198
+ environment:
199
+ - REDIS_HOSTS=local:redis:6379
200
+ ports:
201
+ - "8081:8081"
202
+
203
+ neo4j:
204
+ image: neo4j:5.1-enterprise
205
+ ports:
206
+ - 7474:7474
207
+ - 7687:7687
208
+ environment:
209
+ - NEO4J_AUTH=neo4j/password
210
+ - NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
211
+ - NEO4J_PLUGINS=["apoc", "graph-data-science", "n10s"]
212
+ - NEO4J_dbms_security_procedures_unrestricted=apoc.*,gds.*,n10s.*
213
+ - NEO4J_dbms_security_procedures_whitelist=apoc.*,gds.*,n10s.*
214
+ restart: "unless-stopped"
215
+ volumes:
216
+ - neo4j-db:/data
217
+
218
+ fhirg:
219
+ image: beapen/fhirg:latest
220
+ ports:
221
+ - 8004:8080
222
+ restart: "unless-stopped"
223
+ environment:
224
+ - spring.neo4j.uri=bolt://neo4j:7687
225
+ - spring.neo4j.authentication.username=neo4j
226
+ - spring.neo4j.authentication.password=password
227
+
228
+
229
+ volumes:
230
+ openmrs-data: ~
231
+ openmrs-db: ~
232
+ fhir-db: ~
233
+ postgres-db: ~
234
+ redis-db: ~
235
+ neo4j-db: ~
236
+ ollama-code: ~
237
+ ollama-root: ~
238
+ ollama-webui: ~
@@ -0,0 +1,27 @@
1
+ # Use Python 3.12 slim as base image
2
+ FROM python:3.12-slim AS base
3
+
4
+ # Update package lists and install Git
5
+ RUN apt-get update && \
6
+ apt-get install -y --no-install-recommends git && \
7
+ rm -rf /var/lib/apt/lists/*
8
+
9
+ # Install uv
10
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
11
+
12
+ # Set working directory
13
+ WORKDIR /app
14
+
15
+ # Copy pyproject.toml into the image
16
+ COPY pyproject.toml /app/pyproject.toml
17
+ COPY README.md /app/README.md
18
+
19
+ # Install dependencies and generate uv.lock
20
+ RUN uv sync --no-dev
21
+
22
+ # Copy the project into the image
23
+ COPY . /app
24
+
25
+
26
+ # Run the server
27
+ CMD ["uv", "run", "python", "app/server.py"]
@@ -0,0 +1 @@
1
+ # README
File without changes
@@ -0,0 +1,43 @@
1
+ from kink import di
2
+ from os import getenv
3
+ from dotenv import load_dotenv
4
+ from langchain_core.prompts import PromptTemplate
5
+ from langchain_core.language_models.fake import FakeListLLM
6
+
7
+ ## Override the default configuration of elixirs here if needed
8
+
9
+
10
+ def bootstrap():
11
+ load_dotenv()
12
+ fake_llm = FakeListLLM(responses=["Paris", "I don't know"])
13
+ di["main_prompt"] = PromptTemplate.from_template(
14
+ "Summarize the following in 100 words: {input}"
15
+ )
16
+ di["main_llm"] = fake_llm
17
+ di["cds_hook_discovery"] = {
18
+ "services": [
19
+ {
20
+ "id": "dhti-service",
21
+ "hook": "order-select",
22
+ "title": "MyOrg Order Assistant",
23
+ "description": "Provides suggestions and actions for selected draft orders, including handling CommunicationRequest resources.",
24
+ "prefetch": {
25
+ "patient": "Patient/{{context.patientId}}",
26
+ "draftOrders": "Bundle?patient={{context.patientId}}&status=draft",
27
+ },
28
+ "scopes": [
29
+ "launch",
30
+ "patient/Patient.read",
31
+ "user/Practitioner.read",
32
+ "patient/CommunicationRequest.read",
33
+ ],
34
+ "metadata": {
35
+ "author": "MyOrg CDS Team",
36
+ "version": "1.0.0",
37
+ "supportedResources": [
38
+ "CommunicationRequest",
39
+ ],
40
+ },
41
+ }
42
+ ]
43
+ }
@@ -0,0 +1,57 @@
1
+ from fastapi import FastAPI
2
+ from langserve import add_routes
3
+ from langchain_core.runnables.config import RunnableConfig
4
+ from dhti_elixir_base.cds_hook.routes import add_services, add_invokes
5
+ from fastapi.middleware.cors import CORSMiddleware
6
+ from mcp.server.fastmcp import FastMCP
7
+ mcp_server = FastMCP(name="dhti-mcp-server")
8
+
9
+ # ! DO NOT REMOVE THE COMMENT BELOW
10
+ # DHTI_CLI_IMPORT
11
+ import uvicorn
12
+
13
+ # Comes after elixir bootstraps, so can override elixir configurations
14
+ from bootstrap import bootstrap
15
+ bootstrap()
16
+
17
+ app = FastAPI(title="dhti-elixir-server")
18
+ # Mount the MCP server's ASGI application at a specific path (Exposes /messages and /sse endpoints)
19
+ app.mount("/langserve/mcp", mcp_server.sse_app())
20
+
21
+ origins = [
22
+ "*",
23
+ ]
24
+
25
+ app.add_middleware(
26
+ CORSMiddleware,
27
+ allow_origins=origins,
28
+ allow_credentials=True,
29
+ allow_methods=["*"],
30
+ allow_headers=["*"],
31
+ )
32
+
33
+ # Define a root endpoint
34
+ @app.get("/langserve")
35
+ async def read_root():
36
+ return {"message": "Hello from DHTI!"}
37
+
38
+ try:
39
+ from langfuse import Langfuse
40
+ from langfuse.callback import CallbackHandler
41
+ langfuse_handler = CallbackHandler()
42
+ langfuse_handler.auth_check()
43
+ config = RunnableConfig(callbacks=[langfuse_handler])
44
+ # ! DO NOT REMOVE THE COMMENT BELOW
45
+ # DHTI_LANGFUSE_ROUTE
46
+
47
+ except:
48
+ # ! DO NOT REMOVE THE COMMENT BELOW
49
+ # DHTI_NORMAL_ROUTE
50
+ x = True
51
+
52
+ # ! DO NOT REMOVE THE COMMENT BELOW
53
+ # DHTI_COMMON_ROUTE
54
+
55
+
56
+ if __name__ == "__main__":
57
+ uvicorn.run(app, host="0.0.0.0", port=8001)