mirror of
https://github.com/hoshikawa2/agent_platform_oci.git
synced 2026-07-09 14:04:19 +00:00
First commit
This commit is contained in:
17
apps/channel_gateway/.env.example
Normal file
17
apps/channel_gateway/.env.example
Normal file
@@ -0,0 +1,17 @@
|
||||
APP_NAME=external-channel-gateway
|
||||
LOG_LEVEL=INFO
|
||||
API_HOST=0.0.0.0
|
||||
API_PORT=7000
|
||||
CORS_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
|
||||
|
||||
# adapter = receive channel payloads at /channels/* and convert to GatewayRequest.
|
||||
# proxy = receive only GatewayRequest at /gateway/message and forward it.
|
||||
CHANNEL_GATEWAY_RUNTIME_MODE=adapter
|
||||
|
||||
AGENT_FRAMEWORK_BASE_URL=http://localhost:8000
|
||||
AGENT_FRAMEWORK_GATEWAY_PATH=/gateway/message
|
||||
DEFAULT_TENANT_ID=default
|
||||
DEFAULT_AGENT_ID=telecom_contas
|
||||
REQUEST_TIMEOUT_SECONDS=120
|
||||
# Optional: shared token added to calls from channel_gateway to backend.
|
||||
INTERNAL_GATEWAY_TOKEN=
|
||||
9
apps/channel_gateway/Dockerfile
Normal file
9
apps/channel_gateway/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY app ./app
|
||||
COPY config ./config
|
||||
ENV PYTHONPATH=/app
|
||||
EXPOSE 7000
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7000"]
|
||||
112
apps/channel_gateway/README.md
Normal file
112
apps/channel_gateway/README.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# External Channel Gateway
|
||||
|
||||
This service is a separate Channel Gateway that sits in front of the Agent Framework backend.
|
||||
|
||||
It has its own runtime mode, independent from the backend input mode.
|
||||
|
||||
## Runtime modes
|
||||
|
||||
```env
|
||||
CHANNEL_GATEWAY_RUNTIME_MODE=adapter
|
||||
```
|
||||
|
||||
`adapter` means this service receives channel-specific payloads and translates them into `GatewayRequest` before calling the Agent Framework backend.
|
||||
|
||||
```env
|
||||
CHANNEL_GATEWAY_RUNTIME_MODE=proxy
|
||||
```
|
||||
|
||||
`proxy` means this service accepts only an already-built `GatewayRequest` at `/gateway/message` and forwards it after validation.
|
||||
|
||||
## Recommended enterprise setup
|
||||
|
||||
In `channel_gateway/.env`:
|
||||
|
||||
```env
|
||||
CHANNEL_GATEWAY_RUNTIME_MODE=adapter
|
||||
AGENT_FRAMEWORK_BASE_URL=http://localhost:8000
|
||||
DEFAULT_TENANT_ID=default
|
||||
DEFAULT_AGENT_ID=telecom_contas
|
||||
```
|
||||
|
||||
In `agent_template_backend/.env`:
|
||||
|
||||
```env
|
||||
FRAMEWORK_CHANNEL_INPUT_MODE=external
|
||||
```
|
||||
|
||||
This means:
|
||||
|
||||
```text
|
||||
channel_gateway:7000 = understands channel payloads and builds GatewayRequest
|
||||
backend:8000 = accepts only GatewayRequest and does not parse native channel payloads
|
||||
```
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
cd channel_gateway
|
||||
cp .env.example .env
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 7000
|
||||
```
|
||||
|
||||
## Test web adapter endpoint
|
||||
|
||||
```bash
|
||||
curl -s -X POST "http://localhost:7000/channels/web/message" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"message": "Quero consultar minha fatura",
|
||||
"session_id": "external-gw-test-001",
|
||||
"user_id": "user-external-001",
|
||||
"message_id": "msg-external-001",
|
||||
"customer_key": "11999999999",
|
||||
"contract_key": "3000131180",
|
||||
"interaction_key": "301953872",
|
||||
"session_key": "external-gw-test-001"
|
||||
}' | jq
|
||||
```
|
||||
|
||||
## Test proxy mode
|
||||
|
||||
Set:
|
||||
|
||||
```env
|
||||
CHANNEL_GATEWAY_RUNTIME_MODE=proxy
|
||||
```
|
||||
|
||||
Then call:
|
||||
|
||||
```bash
|
||||
curl -s -X POST "http://localhost:7000/gateway/message" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"channel": "web",
|
||||
"tenant_id": "default",
|
||||
"agent_id": "telecom_contas",
|
||||
"payload": {
|
||||
"message": "Quero consultar minha fatura",
|
||||
"session_id": "proxy-test-001"
|
||||
}
|
||||
}' | jq
|
||||
```
|
||||
|
||||
## Important distinction
|
||||
|
||||
Do not use `CHANNEL_GATEWAY_MODE=external` to mean “this service is external”.
|
||||
|
||||
Use:
|
||||
|
||||
```env
|
||||
CHANNEL_GATEWAY_RUNTIME_MODE=adapter
|
||||
```
|
||||
|
||||
for the external gateway service that owns adapters.
|
||||
|
||||
Use:
|
||||
|
||||
```env
|
||||
FRAMEWORK_CHANNEL_INPUT_MODE=external
|
||||
```
|
||||
|
||||
in the Agent Framework backend when the backend must accept only normalized `GatewayRequest` payloads.
|
||||
0
apps/channel_gateway/app/__init__.py
Normal file
0
apps/channel_gateway/app/__init__.py
Normal file
0
apps/channel_gateway/app/adapters/__init__.py
Normal file
0
apps/channel_gateway/app/adapters/__init__.py
Normal file
9
apps/channel_gateway/app/adapters/base.py
Normal file
9
apps/channel_gateway/app/adapters/base.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol
|
||||
from app.schemas import GatewayRequest
|
||||
|
||||
|
||||
class ChannelAdapter(Protocol):
|
||||
name: str
|
||||
async def to_gateway_request(self, payload) -> GatewayRequest: ...
|
||||
45
apps/channel_gateway/app/adapters/voice.py
Normal file
45
apps/channel_gateway/app/adapters/voice.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.schemas import BusinessContext, GatewayRequest, VoiceTranscript
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
class VoiceAdapter:
|
||||
name = 'voice'
|
||||
|
||||
async def to_gateway_request(self, payload: VoiceTranscript) -> GatewayRequest:
|
||||
session_id = payload.session_id or payload.call_id
|
||||
message_id = payload.message_id or (f'{payload.call_id}-turn-1' if payload.call_id else None)
|
||||
bc = BusinessContext(
|
||||
customer_key=payload.customer_key or payload.caller,
|
||||
contract_key=payload.contract_key,
|
||||
interaction_key=payload.interaction_key or payload.call_id,
|
||||
session_key=session_id,
|
||||
metadata={
|
||||
'source_channel': 'voice',
|
||||
'confidence': payload.confidence,
|
||||
'language': payload.language,
|
||||
**(payload.metadata or {}),
|
||||
},
|
||||
)
|
||||
data = {
|
||||
'message': payload.transcript,
|
||||
'session_id': session_id,
|
||||
'user_id': payload.caller,
|
||||
'message_id': message_id,
|
||||
'customer_key': bc.customer_key,
|
||||
'contract_key': bc.contract_key,
|
||||
'interaction_key': bc.interaction_key,
|
||||
'session_key': bc.session_key,
|
||||
'business_context': bc.model_dump(exclude_none=True),
|
||||
'metadata': {
|
||||
**(payload.metadata or {}),
|
||||
'external_gateway': settings.APP_NAME,
|
||||
'source_channel': 'voice',
|
||||
'call_id': payload.call_id,
|
||||
'confidence': payload.confidence,
|
||||
'language': payload.language,
|
||||
'contract_version': 'gateway-request-v1',
|
||||
},
|
||||
}
|
||||
return GatewayRequest(channel='voice', tenant_id=settings.DEFAULT_TENANT_ID, agent_id=settings.DEFAULT_AGENT_ID, payload=data)
|
||||
35
apps/channel_gateway/app/adapters/web.py
Normal file
35
apps/channel_gateway/app/adapters/web.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.schemas import BusinessContext, GatewayRequest, WebMessage
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
class WebAdapter:
|
||||
name = 'web'
|
||||
|
||||
async def to_gateway_request(self, payload: WebMessage) -> GatewayRequest:
|
||||
bc = payload.business_context or BusinessContext(
|
||||
customer_key=payload.customer_key,
|
||||
contract_key=payload.contract_key,
|
||||
interaction_key=payload.interaction_key,
|
||||
account_key=payload.account_key,
|
||||
resource_key=payload.resource_key,
|
||||
session_key=payload.session_key or payload.session_id,
|
||||
metadata={'source_channel': 'web', **(payload.metadata or {})},
|
||||
)
|
||||
data = payload.model_dump(exclude_none=True)
|
||||
data['business_context'] = bc.model_dump(exclude_none=True)
|
||||
data.setdefault('session_key', payload.session_key or payload.session_id)
|
||||
data.setdefault('metadata', {})
|
||||
data['metadata'] = {
|
||||
**(data.get('metadata') or {}),
|
||||
'external_gateway': settings.APP_NAME,
|
||||
'source_channel': 'web',
|
||||
'contract_version': 'gateway-request-v1',
|
||||
}
|
||||
return GatewayRequest(
|
||||
channel='web',
|
||||
tenant_id=settings.DEFAULT_TENANT_ID,
|
||||
agent_id=settings.DEFAULT_AGENT_ID,
|
||||
payload=data,
|
||||
)
|
||||
42
apps/channel_gateway/app/adapters/whatsapp.py
Normal file
42
apps/channel_gateway/app/adapters/whatsapp.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.schemas import BusinessContext, GatewayRequest, WhatsAppWebhook
|
||||
from app.settings import settings
|
||||
|
||||
|
||||
class WhatsAppAdapter:
|
||||
name = 'whatsapp'
|
||||
|
||||
async def to_gateway_request(self, payload: WhatsAppWebhook) -> GatewayRequest:
|
||||
user_id = payload.wa_id or payload.from_
|
||||
text = payload.message or payload.text or payload.interactive_title or payload.interactive_id
|
||||
if not text:
|
||||
raise ValueError('INVALID_WHATSAPP_PAYLOAD: message/text/interactive_title is required')
|
||||
session_id = payload.session_id or user_id
|
||||
message_id = payload.message_id or payload.interaction_key
|
||||
bc = BusinessContext(
|
||||
customer_key=payload.customer_key or user_id,
|
||||
contract_key=payload.contract_key,
|
||||
interaction_key=payload.interaction_key or message_id,
|
||||
session_key=session_id,
|
||||
metadata={'source_channel': 'whatsapp', **(payload.metadata or {})},
|
||||
)
|
||||
data = {
|
||||
'message': text,
|
||||
'session_id': session_id,
|
||||
'user_id': user_id,
|
||||
'message_id': message_id,
|
||||
'customer_key': bc.customer_key,
|
||||
'contract_key': bc.contract_key,
|
||||
'interaction_key': bc.interaction_key,
|
||||
'session_key': bc.session_key,
|
||||
'business_context': bc.model_dump(exclude_none=True),
|
||||
'metadata': {
|
||||
**(payload.metadata or {}),
|
||||
'external_gateway': settings.APP_NAME,
|
||||
'source_channel': 'whatsapp',
|
||||
'interactive_id': payload.interactive_id,
|
||||
'contract_version': 'gateway-request-v1',
|
||||
},
|
||||
}
|
||||
return GatewayRequest(channel='whatsapp', tenant_id=settings.DEFAULT_TENANT_ID, agent_id=settings.DEFAULT_AGENT_ID, payload=data)
|
||||
33
apps/channel_gateway/app/client.py
Normal file
33
apps/channel_gateway/app/client.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import httpx
|
||||
from .settings import settings
|
||||
from .schemas import GatewayRequest
|
||||
|
||||
|
||||
class AgentFrameworkClient:
|
||||
def __init__(self):
|
||||
self.url = settings.AGENT_FRAMEWORK_BASE_URL.rstrip('/') + settings.AGENT_FRAMEWORK_GATEWAY_PATH
|
||||
|
||||
async def send(self, request: GatewayRequest) -> dict:
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
if settings.INTERNAL_GATEWAY_TOKEN:
|
||||
headers['X-Channel-Gateway-Token'] = settings.INTERNAL_GATEWAY_TOKEN
|
||||
async with httpx.AsyncClient(timeout=settings.REQUEST_TIMEOUT_SECONDS) as client:
|
||||
resp = await client.post(self.url, json=request.model_dump(exclude_none=True), headers=headers)
|
||||
try:
|
||||
data = resp.json()
|
||||
except Exception:
|
||||
data = {'text': resp.text}
|
||||
if resp.status_code >= 400:
|
||||
return {
|
||||
'ok': False,
|
||||
'status_code': resp.status_code,
|
||||
'error': data,
|
||||
'forwarded_to': self.url,
|
||||
}
|
||||
if isinstance(data, dict):
|
||||
data.setdefault('ok', True)
|
||||
data.setdefault('forwarded_to', self.url)
|
||||
return data
|
||||
return {'ok': True, 'data': data, 'forwarded_to': self.url}
|
||||
112
apps/channel_gateway/app/main.py
Normal file
112
apps/channel_gateway/app/main.py
Normal file
@@ -0,0 +1,112 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from app.adapters.web import WebAdapter
|
||||
from app.adapters.whatsapp import WhatsAppAdapter
|
||||
from app.adapters.voice import VoiceAdapter
|
||||
from app.client import AgentFrameworkClient
|
||||
from app.schemas import GatewayRequest, VoiceTranscript, WebMessage, WhatsAppWebhook
|
||||
from app.settings import settings
|
||||
|
||||
logging.basicConfig(level=settings.LOG_LEVEL)
|
||||
logger = logging.getLogger('external_channel_gateway')
|
||||
|
||||
app = FastAPI(title='External Channel Gateway')
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[o.strip() for o in settings.CORS_ORIGINS.split(',')],
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
)
|
||||
|
||||
client = AgentFrameworkClient()
|
||||
web_adapter = WebAdapter()
|
||||
whatsapp_adapter = WhatsAppAdapter()
|
||||
voice_adapter = VoiceAdapter()
|
||||
|
||||
|
||||
def _require_mode(expected: str, endpoint_type: str):
|
||||
if settings.runtime_mode != expected:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail={
|
||||
'error_code': 'CHANNEL_GATEWAY_MODE_MISMATCH',
|
||||
'message': f'{endpoint_type} endpoints require CHANNEL_GATEWAY_RUNTIME_MODE={expected}',
|
||||
'current_runtime_mode': settings.runtime_mode,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def _forward(request: GatewayRequest) -> dict:
|
||||
return await client.send(request)
|
||||
|
||||
|
||||
@app.get('/health')
|
||||
async def health():
|
||||
return {
|
||||
'status': 'ok',
|
||||
'app_name': settings.APP_NAME,
|
||||
'runtime_mode': settings.runtime_mode,
|
||||
'configured_runtime_mode': settings.CHANNEL_GATEWAY_RUNTIME_MODE,
|
||||
'legacy_channel_gateway_mode': settings.CHANNEL_GATEWAY_MODE,
|
||||
'backend_url': client.url,
|
||||
'default_tenant_id': settings.DEFAULT_TENANT_ID,
|
||||
'default_agent_id': settings.DEFAULT_AGENT_ID,
|
||||
}
|
||||
|
||||
|
||||
@app.post('/channels/web/message')
|
||||
async def web_message(payload: WebMessage, request: Request):
|
||||
_require_mode('adapter', '/channels/*')
|
||||
gateway_request = await web_adapter.to_gateway_request(payload)
|
||||
gateway_request.payload.setdefault('metadata', {})
|
||||
gateway_request.payload['metadata'].update({
|
||||
'channel_gateway_request_id': request.headers.get('x-request-id') or str(uuid4()),
|
||||
'channel_gateway_endpoint': '/channels/web/message',
|
||||
})
|
||||
return await _forward(gateway_request)
|
||||
|
||||
|
||||
@app.post('/channels/whatsapp/webhook')
|
||||
async def whatsapp_webhook(payload: WhatsAppWebhook, request: Request):
|
||||
_require_mode('adapter', '/channels/*')
|
||||
try:
|
||||
gateway_request = await whatsapp_adapter.to_gateway_request(payload)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
gateway_request.payload.setdefault('metadata', {})
|
||||
gateway_request.payload['metadata'].update({
|
||||
'channel_gateway_request_id': request.headers.get('x-request-id') or str(uuid4()),
|
||||
'channel_gateway_endpoint': '/channels/whatsapp/webhook',
|
||||
})
|
||||
return await _forward(gateway_request)
|
||||
|
||||
|
||||
@app.post('/channels/voice/transcript')
|
||||
async def voice_transcript(payload: VoiceTranscript, request: Request):
|
||||
_require_mode('adapter', '/channels/*')
|
||||
gateway_request = await voice_adapter.to_gateway_request(payload)
|
||||
gateway_request.payload.setdefault('metadata', {})
|
||||
gateway_request.payload['metadata'].update({
|
||||
'channel_gateway_request_id': request.headers.get('x-request-id') or str(uuid4()),
|
||||
'channel_gateway_endpoint': '/channels/voice/transcript',
|
||||
})
|
||||
return await _forward(gateway_request)
|
||||
|
||||
|
||||
@app.post('/gateway/message')
|
||||
async def proxy_gateway_message(payload: GatewayRequest, request: Request):
|
||||
_require_mode('proxy', '/gateway/message')
|
||||
payload.payload.setdefault('metadata', {})
|
||||
payload.payload['metadata'].update({
|
||||
'channel_gateway_request_id': request.headers.get('x-request-id') or str(uuid4()),
|
||||
'channel_gateway_endpoint': '/gateway/message',
|
||||
'proxied_by': settings.APP_NAME,
|
||||
})
|
||||
return await _forward(payload)
|
||||
74
apps/channel_gateway/app/schemas.py
Normal file
74
apps/channel_gateway/app/schemas.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class BusinessContext(BaseModel):
|
||||
customer_key: str | None = None
|
||||
contract_key: str | None = None
|
||||
interaction_key: str | None = None
|
||||
account_key: str | None = None
|
||||
resource_key: str | None = None
|
||||
session_key: str | None = None
|
||||
protocol_key: str | None = None
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class WebMessage(BaseModel):
|
||||
message: str
|
||||
session_id: str | None = None
|
||||
user_id: str | None = None
|
||||
message_id: str | None = None
|
||||
customer_key: str | None = None
|
||||
contract_key: str | None = None
|
||||
interaction_key: str | None = None
|
||||
account_key: str | None = None
|
||||
resource_key: str | None = None
|
||||
session_key: str | None = None
|
||||
business_context: BusinessContext | None = None
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class WhatsAppWebhook(BaseModel):
|
||||
wa_id: str | None = None
|
||||
from_: str | None = Field(default=None, alias="from")
|
||||
message: str | None = None
|
||||
text: str | None = None
|
||||
session_id: str | None = None
|
||||
message_id: str | None = None
|
||||
interactive_id: str | None = None
|
||||
interactive_title: str | None = None
|
||||
raw: dict[str, Any] = Field(default_factory=dict)
|
||||
customer_key: str | None = None
|
||||
contract_key: str | None = None
|
||||
interaction_key: str | None = None
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class VoiceTranscript(BaseModel):
|
||||
transcript: str
|
||||
call_id: str | None = None
|
||||
caller: str | None = None
|
||||
session_id: str | None = None
|
||||
message_id: str | None = None
|
||||
confidence: float | None = None
|
||||
language: str | None = None
|
||||
customer_key: str | None = None
|
||||
contract_key: str | None = None
|
||||
interaction_key: str | None = None
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class GatewayRequest(BaseModel):
|
||||
channel: str
|
||||
payload: dict[str, Any]
|
||||
agent_id: str | None = None
|
||||
tenant_id: str | None = None
|
||||
|
||||
|
||||
class GatewayResponse(BaseModel):
|
||||
channel: str | None = None
|
||||
session_id: str | None = None
|
||||
text: str | None = None
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
46
apps/channel_gateway/app/settings.py
Normal file
46
apps/channel_gateway/app/settings.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
from dotenv import load_dotenv
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
load_dotenv(override=False)
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8', extra='ignore')
|
||||
|
||||
APP_NAME: str = 'external-channel-gateway'
|
||||
LOG_LEVEL: str = 'INFO'
|
||||
API_HOST: str = '0.0.0.0'
|
||||
API_PORT: int = 7000
|
||||
CORS_ORIGINS: str = 'http://localhost:5173,http://127.0.0.1:5173'
|
||||
|
||||
# adapter = receive native/simple channel payloads and translate them to GatewayRequest.
|
||||
# proxy = receive only GatewayRequest and forward it after validation.
|
||||
CHANNEL_GATEWAY_RUNTIME_MODE: Literal['adapter','proxy'] = 'adapter'
|
||||
# Legacy alias accepted only for compatibility. Prefer CHANNEL_GATEWAY_RUNTIME_MODE.
|
||||
CHANNEL_GATEWAY_MODE: str | None = None
|
||||
|
||||
AGENT_FRAMEWORK_BASE_URL: str = 'http://localhost:8000'
|
||||
AGENT_FRAMEWORK_GATEWAY_PATH: str = '/gateway/message'
|
||||
DEFAULT_TENANT_ID: str = 'default'
|
||||
DEFAULT_AGENT_ID: str = 'telecom_contas'
|
||||
REQUEST_TIMEOUT_SECONDS: float = 120.0
|
||||
|
||||
INTERNAL_GATEWAY_TOKEN: str | None = None
|
||||
|
||||
@property
|
||||
def runtime_mode(self) -> str:
|
||||
legacy = (self.CHANNEL_GATEWAY_MODE or '').strip().lower()
|
||||
if legacy in {'adapter', 'proxy'}:
|
||||
return legacy
|
||||
# Legacy mapping for old deployments: embedded meant adapters on.
|
||||
if legacy == 'embedded':
|
||||
return 'adapter'
|
||||
if legacy == 'external':
|
||||
return 'proxy'
|
||||
return self.CHANNEL_GATEWAY_RUNTIME_MODE
|
||||
|
||||
|
||||
settings = Settings()
|
||||
13
apps/channel_gateway/config/channels.yaml
Normal file
13
apps/channel_gateway/config/channels.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
channels:
|
||||
web:
|
||||
enabled: true
|
||||
endpoint: /channels/web/message
|
||||
adapter: web
|
||||
whatsapp:
|
||||
enabled: true
|
||||
endpoint: /channels/whatsapp/webhook
|
||||
adapter: whatsapp
|
||||
voice:
|
||||
enabled: true
|
||||
endpoint: /channels/voice/transcript
|
||||
adapter: voice
|
||||
7
apps/channel_gateway/docker-compose.yml
Normal file
7
apps/channel_gateway/docker-compose.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
channel_gateway:
|
||||
build: .
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "7000:7000"
|
||||
6
apps/channel_gateway/requirements.txt
Normal file
6
apps/channel_gateway/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
fastapi>=0.111.0
|
||||
uvicorn[standard]>=0.30.0
|
||||
httpx>=0.27.0
|
||||
pydantic>=2.7.0
|
||||
pydantic-settings>=2.2.0
|
||||
python-dotenv>=1.0.0
|
||||
Reference in New Issue
Block a user