ScuttleBot

scuttlebot / skills / openai-relay / scripts / python-openai-relay.py
Blame History Raw 143 lines
1
#!/usr/bin/env python3
2
"""Minimal OpenAI + scuttlebot relay example.
3
4
Env required:
5
SCUTTLEBOT_URL, SCUTTLEBOT_TOKEN, SCUTTLEBOT_CHANNEL
6
Optional:
7
SCUTTLEBOT_NICK, SCUTTLEBOT_SESSION_ID, OPENAI_MODEL (default: gpt-4.1-mini)
8
"""
9
import os
10
import re
11
import sys
12
import time
13
from datetime import datetime
14
import requests
15
from openai import OpenAI
16
17
prompt = sys.argv[1] if len(sys.argv) > 1 else "Hello from openai-relay"
18
19
20
def sanitize(value: str) -> str:
21
return re.sub(r"[^A-Za-z0-9_-]+", "-", value).strip("-") or "session"
22
23
24
base_name = sanitize(os.path.basename(os.getcwd()) or "repo")
25
session_suffix = sanitize(
26
os.environ.get("SCUTTLEBOT_SESSION_ID")
27
or os.environ.get("CODEX_SESSION_ID")
28
or str(os.getppid())
29
)
30
31
cfg = {
32
"url": os.environ.get("SCUTTLEBOT_URL"),
33
"token": os.environ.get("SCUTTLEBOT_TOKEN"),
34
"channel": (os.environ.get("SCUTTLEBOT_CHANNEL", "general")).lstrip("#"),
35
"nick": os.environ.get(
36
"SCUTTLEBOT_NICK", f"codex-{base_name}-{session_suffix}"
37
),
38
"model": os.environ.get("OPENAI_MODEL", "gpt-4.1-mini"),
39
"backend": os.environ.get("SCUTTLEBOT_LLM_BACKEND", "openai"), # default to daemon-stored openai backend
40
}
41
42
missing = [k for k, v in cfg.items() if not v and k != "model"]
43
use_backend = bool(cfg["backend"])
44
if missing:
45
print(f"missing env: {', '.join(missing)}", file=sys.stderr)
46
sys.exit(1)
47
if not use_backend and "OPENAI_API_KEY" not in os.environ:
48
print("missing env: OPENAI_API_KEY (or set SCUTTLEBOT_LLM_BACKEND to use server-side key)", file=sys.stderr)
49
sys.exit(1)
50
51
client = None if use_backend else OpenAI(api_key=os.environ["OPENAI_API_KEY"])
52
last_check = 0.0
53
mention_re = re.compile(
54
rf"(^|[^A-Za-z0-9_./\\-]){re.escape(cfg['nick'])}($|[^A-Za-z0-9_./\\-])",
55
re.IGNORECASE,
56
)
57
58
59
def relay_post(text: str) -> None:
60
res = requests.post(
61
f"{cfg['url']}/v1/channels/{cfg['channel']}/messages",
62
headers={
63
"Authorization": f"Bearer {cfg['token']}",
64
"Content-Type": "application/json",
65
},
66
json={"text": text, "nick": cfg["nick"]},
67
timeout=10,
68
)
69
res.raise_for_status()
70
71
72
def relay_poll():
73
global last_check
74
res = requests.get(
75
f"{cfg['url']}/v1/channels/{cfg['channel']}/messages",
76
headers={"Authorization": f"Bearer {cfg['token']}"},
77
timeout=10,
78
)
79
res.raise_for_status()
80
data = res.json()
81
now = time.time()
82
bots = {
83
cfg["nick"],
84
"bridge",
85
"oracle",
86
"sentinel",
87
"steward",
88
"scribe",
89
"warden",
90
"snitch",
91
"herald",
92
"scroll",
93
"systembot",
94
"auditbot",
95
"claude",
96
}
97
msgs = [
98
m
99
for m in data.get("messages", [])
100
if m["nick"] not in bots
101
and not m["nick"].startswith("claude-")
102
and not m["nick"].startswith("codex-")
103
and not m["nick"].startswith("gemini-")
104
and datetime.fromisoformat(m["at"].replace("Z", "+00:00")).timestamp() > last_check
105
and mention_re.search(m["text"])
106
]
107
last_check = now
108
return msgs
109
110
111
def main():
112
relay_post(f"starting: {prompt}")
113
if use_backend:
114
res = requests.post(
115
f"{cfg['url']}/v1/llm/complete",
116
headers={
117
"Authorization": f"Bearer {cfg['token']}",
118
"Content-Type": "application/json",
119
},
120
json={"backend": cfg["backend"], "prompt": prompt},
121
timeout=20,
122
)
123
res.raise_for_status()
124
reply = res.json()["text"]
125
else:
126
completion = client.chat.completions.create(
127
model=cfg["model"],
128
messages=[{"role": "user", "content": prompt}],
129
)
130
reply = completion.choices[0].message.content
131
print(f"OpenAI: {reply}")
132
relay_post(f"OpenAI reply: {reply}")
133
for m in relay_poll():
134
print(f"[IRC] {m['nick']}: {m['text']}")
135
136
137
if __name__ == "__main__":
138
try:
139
main()
140
except Exception as exc: # broad but fine for CLI sample
141
print(exc, file=sys.stderr)
142
sys.exit(1)
143

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button