chatgpt-websocket 1.0.0
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/bin/llmbackend.js +153 -0
- package/package.json +13 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { WebSocketServer } = require('ws');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
// Parse args: llmbackend token=<token> [port=<port>]
|
|
7
|
+
const args = {};
|
|
8
|
+
for (const arg of process.argv.slice(2)) {
|
|
9
|
+
const eq = arg.indexOf('=');
|
|
10
|
+
if (eq !== -1) {
|
|
11
|
+
args[arg.slice(0, eq)] = arg.slice(eq + 1);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const token = args.token;
|
|
16
|
+
if (!token) {
|
|
17
|
+
console.error('Usage: llmbackend token=<your_access_token> [port=8080]');
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const PORT = parseInt(args.port, 10) || 8080;
|
|
22
|
+
|
|
23
|
+
// Decode JWT to extract chatgpt_account_id
|
|
24
|
+
let accountId;
|
|
25
|
+
try {
|
|
26
|
+
const payload = JSON.parse(
|
|
27
|
+
Buffer.from(token.split('.')[1], 'base64url').toString()
|
|
28
|
+
);
|
|
29
|
+
accountId = payload['https://api.openai.com/auth']?.chatgpt_account_id;
|
|
30
|
+
} catch (e) {
|
|
31
|
+
// ignore
|
|
32
|
+
}
|
|
33
|
+
if (!accountId) {
|
|
34
|
+
console.error('Could not extract chatgpt_account_id from token');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const INSTRUCTIONS = `You are a coding assistant. You help the user with software engineering tasks including writing code, debugging, and explaining code. Be concise and direct.`;
|
|
39
|
+
|
|
40
|
+
async function streamChat(message, history, ws) {
|
|
41
|
+
// Add user message to history
|
|
42
|
+
history.push({
|
|
43
|
+
type: 'message',
|
|
44
|
+
role: 'user',
|
|
45
|
+
content: [{ type: 'input_text', text: message }]
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const body = JSON.stringify({
|
|
49
|
+
model: 'gpt-5',
|
|
50
|
+
instructions: INSTRUCTIONS,
|
|
51
|
+
input: history,
|
|
52
|
+
store: false,
|
|
53
|
+
stream: true,
|
|
54
|
+
include: ['reasoning.encrypted_content'],
|
|
55
|
+
tools: [],
|
|
56
|
+
tool_choice: 'none'
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const res = await fetch('https://chatgpt.com/backend-api/codex/responses', {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Type': 'application/json',
|
|
63
|
+
'Authorization': `Bearer ${token}`,
|
|
64
|
+
'Accept': 'text/event-stream',
|
|
65
|
+
'OpenAI-Beta': 'responses=experimental',
|
|
66
|
+
'session_id': crypto.randomUUID(),
|
|
67
|
+
'originator': 'codex_cli_rs',
|
|
68
|
+
'chatgpt-account-id': accountId
|
|
69
|
+
},
|
|
70
|
+
body
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
const errText = await res.text();
|
|
75
|
+
ws.send(JSON.stringify({ type: 'error', error: `HTTP ${res.status}: ${errText}` }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const reader = res.body.getReader();
|
|
80
|
+
const decoder = new TextDecoder();
|
|
81
|
+
let buffer = '';
|
|
82
|
+
let assistantText = '';
|
|
83
|
+
|
|
84
|
+
while (true) {
|
|
85
|
+
const { done, value } = await reader.read();
|
|
86
|
+
if (done) break;
|
|
87
|
+
|
|
88
|
+
buffer += decoder.decode(value, { stream: true });
|
|
89
|
+
const lines = buffer.split('\n');
|
|
90
|
+
buffer = lines.pop();
|
|
91
|
+
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
if (!line.startsWith('data: ')) continue;
|
|
94
|
+
const data = line.slice(6).trim();
|
|
95
|
+
if (data === '[DONE]') {
|
|
96
|
+
history.push({ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: assistantText }] });
|
|
97
|
+
ws.send(JSON.stringify({ type: 'done' }));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const event = JSON.parse(data);
|
|
103
|
+
if (event.type === 'response.output_text.delta' && event.delta) {
|
|
104
|
+
assistantText += event.delta;
|
|
105
|
+
ws.send(JSON.stringify({ type: 'chunk', content: event.delta }));
|
|
106
|
+
}
|
|
107
|
+
if (event.type === 'response.completed') {
|
|
108
|
+
history.push({ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: assistantText }] });
|
|
109
|
+
ws.send(JSON.stringify({ type: 'done' }));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
} catch (e) {
|
|
113
|
+
// skip non-JSON lines
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
history.push({ type: 'message', role: 'assistant', content: [{ type: 'output_text', text: assistantText }] });
|
|
119
|
+
ws.send(JSON.stringify({ type: 'done' }));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const wss = new WebSocketServer({ port: PORT });
|
|
123
|
+
|
|
124
|
+
wss.on('connection', (ws) => {
|
|
125
|
+
console.log('Client connected');
|
|
126
|
+
const history = [];
|
|
127
|
+
|
|
128
|
+
ws.on('message', async (raw) => {
|
|
129
|
+
let msg;
|
|
130
|
+
try {
|
|
131
|
+
msg = JSON.parse(raw.toString());
|
|
132
|
+
} catch (e) {
|
|
133
|
+
ws.send(JSON.stringify({ type: 'error', error: 'Invalid JSON' }));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (msg.type !== 'chat' || !msg.message) {
|
|
138
|
+
ws.send(JSON.stringify({ type: 'error', error: 'Expected {"type":"chat","message":"..."}' }));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
await streamChat(msg.message, history, ws);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error('Stream error:', err);
|
|
146
|
+
ws.send(JSON.stringify({ type: 'error', error: err.message }));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
ws.on('close', () => console.log('Client disconnected'));
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
console.log(`llmbackend running on ws://localhost:${PORT}`);
|
package/package.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chatgpt-websocket",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "WebSocket server wrapping ChatGPT backend API for GPT-5 access",
|
|
5
|
+
"bin": {
|
|
6
|
+
"chatgpt-websocket": "bin/llmbackend.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": ["chatgpt", "gpt-5", "websocket", "openai", "streaming"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"ws": "^8.18.0"
|
|
12
|
+
}
|
|
13
|
+
}
|