forked from goyaljai/jaika
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgrpc_server.py
More file actions
168 lines (139 loc) · 5.59 KB
/
Copy pathgrpc_server.py
File metadata and controls
168 lines (139 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
"""Jaika gRPC server — bidirectional streaming chat for admin users.
Runs alongside Flask on port 5245. Admin-only access.
"""
import grpc
from concurrent import futures
import logging
import os
import sys
import time
# Add parent dir to path for imports
sys.path.insert(0, os.path.dirname(__file__))
import chat_pb2
import chat_pb2_grpc
from auth import load_token, is_admin
from gemini import generate
from sessions import create_session, add_message, get_conversation_history
logging.basicConfig(level=logging.INFO)
log = logging.getLogger(__name__)
# Load env
from dotenv import load_dotenv
load_dotenv()
class JaikaChatServicer(chat_pb2_grpc.JaikaChatServicer):
"""Bidirectional streaming chat service.
Client sends ChatMessage stream, server responds with ChatResponse stream.
Each message from the client triggers an AI response from the server.
Admin-only: non-admin users get an error response.
"""
def Chat(self, request_iterator, context):
"""Handle bidirectional streaming chat.
For each incoming ChatMessage:
1. Verify user is admin
2. Get or create session
3. Run Gemini CLI with conversation history
4. Stream back the response
"""
session_id = None
user_id = None
for message in request_iterator:
# First message sets the user context
if user_id is None:
user_id = message.user_id
if not user_id or not load_token(user_id):
yield chat_pb2.ChatResponse(
text="Authentication failed. Invalid user_id.",
status="error"
)
return
if not is_admin(user_id):
yield chat_pb2.ChatResponse(
text="gRPC chat is admin-only.",
status="error"
)
return
log.info("gRPC chat started for admin user %s", user_id)
# Use provided session_id or create one
if message.session_id:
session_id = message.session_id
elif session_id is None:
sess = create_session(user_id, title="gRPC Chat")
session_id = sess["id"]
# Save user message
add_message(user_id, session_id, "user", message.text)
# Signal start
yield chat_pb2.ChatResponse(
session_id=session_id,
status="start"
)
# Get conversation history and build prompt
history = get_conversation_history(user_id, session_id)
parts = []
for msg in history:
if msg.get("text"):
role = "User" if msg["role"] == "user" else "Assistant"
parts.append(f"{role}: {msg['text']}")
prompt = "\n".join(parts)
# Call AI API
api_messages = [{"role": "user", "content": prompt}]
try:
result = generate(user_id, api_messages)
if isinstance(result, dict) and result.get("error"):
yield chat_pb2.ChatResponse(
text=result["error"],
session_id=session_id,
status="error"
)
continue
text = (result.get("text", "") if isinstance(result, dict) else str(result)).strip()
if text:
# Save AI response
add_message(user_id, session_id, "model", text)
# Stream response in chunks (simulate streaming)
words = text.split()
chunk = []
for i, word in enumerate(words):
chunk.append(word)
if len(chunk) >= 10 or i == len(words) - 1:
yield chat_pb2.ChatResponse(
text=" ".join(chunk),
session_id=session_id,
status="chunk"
)
chunk = []
time.sleep(0.05) # Small delay for streaming feel
yield chat_pb2.ChatResponse(
session_id=session_id,
status="done"
)
else:
yield chat_pb2.ChatResponse(
text="Empty response from AI.",
session_id=session_id,
status="error"
)
except TimeoutError:
yield chat_pb2.ChatResponse(
text="Request timed out.",
session_id=session_id,
status="error"
)
except Exception as e:
yield chat_pb2.ChatResponse(
text=f"Error: {str(e)}",
session_id=session_id,
status="error"
)
def serve():
"""Start the gRPC server on port 5245 with TLS."""
server = grpc.server(futures.ThreadPoolExecutor(max_workers=5))
chat_pb2_grpc.add_JaikaChatServicer_to_server(JaikaChatServicer(), server)
# Insecure port — Tailscale Funnel handles TLS termination
server.add_insecure_port("[::]:5245")
log.info("Jaika gRPC server started on port 5245 (Tailscale handles TLS)")
server.start()
try:
server.wait_for_termination()
except KeyboardInterrupt:
server.stop(0)
if __name__ == "__main__":
serve()