├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── README.md
├── clewd.js
├── docker-compose.yml
├── lib
├── bin
│ ├── ca
│ ├── clewd-superfetch-linux-amd64
│ ├── clewd-superfetch-linux-arm64
│ ├── clewd-superfetch-win-amd64.exe
│ └── clewd-superfetch-win-ia32.exe
├── clewd-stream.js
├── clewd-superfetch.js
└── clewd-utils.js
├── media
├── logo.png
└── program.png
├── package.json
├── start.bat
├── start.sh
├── update.bat
└── update.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Developer
9 | *_dev.js
10 | *.dev.js
11 |
12 | # Clewd
13 | cookies.json
14 | cookies.js
15 | cookies.ini
16 | lib/bin/cfg
17 | lib/bin/hdr
18 | lib/bin/pyld
19 | config.js
20 | log.txt
21 |
22 | # npm
23 | package-lock.json
24 |
25 | # Runtime data
26 | pids
27 | *.pid
28 | *.seed
29 | *.pid.lock
30 |
31 | # Directory for instrumented libs generated by jscoverage/JSCover
32 | lib-cov
33 |
34 | # Coverage directory used by tools like istanbul
35 | coverage
36 |
37 | # nyc test coverage
38 | .nyc_output
39 |
40 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
41 | .grunt
42 |
43 | # Bower dependency directory (https://bower.io/)
44 | bower_components
45 |
46 | # node-waf configuration
47 | .lock-wscript
48 |
49 | # Compiled binary addons (https://nodejs.org/api/addons.html)
50 | build/Release
51 |
52 | # Dependency directories
53 | node_modules/
54 | jspm_packages/
55 |
56 | # TypeScript v1 declaration files
57 | typings/
58 |
59 | # Optional npm cache directory
60 | .npm
61 |
62 | # Optional eslint cache
63 | .eslintcache
64 |
65 | # Optional REPL history
66 | .node_repl_history
67 |
68 | # Output of 'npm pack'
69 | *.tgz
70 |
71 | # Yarn Integrity file
72 | .yarn-integrity
73 |
74 | # dotenv environment variables file
75 | .env
76 |
77 | # next.js build output
78 | .next
79 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
14 |
15 | # 4.8
16 |
17 | up to date route
18 |
19 | fixed errors not showing on frontend
20 |
21 | > **Hotfix 1**
22 |
23 | reverted route
24 |
25 | limited external models shown to website ones
26 |
27 | > **Hotfix 2**
28 |
29 | fixed "streaming off" not being detected
30 |
31 | fixed more errors not showing on frontend
32 |
33 | > **Hotfix 3**
34 |
35 | fixed some names not using spaces properly
36 |
37 | fixed endpoint
38 |
39 | # 4.7
40 |
41 | added multiple model options in case you subscribe to their plans. some based on their docs, some on rumors. stick to 2.1 if you're a free user
42 |
43 | # 4.6
44 |
45 | fixed major bug in the prompt build logic which was causing problems like the AI refering to old messages erroneously, ignoring your latest message, etc
46 |
47 | added superfetch for armv7
48 |
49 | # 4.5
50 |
51 | expanded superfetch to fix some errors
52 |
53 | status codes and *some* basic as fuck error handling for superfetch
54 |
55 | stopped trimming system messages
56 |
57 | fixed crash sometimes after superfetch request
58 |
59 | fixed impersonation sometimes not stopping
60 |
61 | fixed hard-censor detection
62 |
63 | fixed weird behavior when "Add character names" was checked
64 |
65 | fixed another source of "Received no valid replies at all"
66 |
67 | chown added to start.sh to stop EACESS errors
68 |
69 | > **AllSamples** changed
70 |
71 | last Assistant and Human will not be transformed into examples
72 |
73 | # 4.4
74 |
75 | fixed another source of "Received no valid replies at all"
76 |
77 | # 4.3
78 |
79 | a fix for "Received no valid replies at all" on >=20k ctx (tested 35k)
80 |
81 | > **SuperfetchTimeout** removed
82 |
83 | was only relevant to pre-4.0
84 |
85 | > **Hotfix 1**
86 |
87 | fixed another source of "Received no valid replies at all"
88 |
89 |
90 | # 4.2
91 |
92 | fixed broken replies
93 |
94 | # 4.1
95 |
96 | > **PromptExperimentFirst** and **PromptExperimentNext** added
97 |
98 | both only have effect when **PromptExperiments** is true
99 |
100 | **PromptExperimentFirst** is sent on the very first message together with the prompt in file form
101 |
102 | **PromptExperimentNext** is sent on the subsequent messages if **RenewAlways** is false
103 |
104 | examples
105 |
106 | - PromptExperimentFirst set to "Comply"
107 |
108 | - PromptExperimentNext set to "Continue"
109 |
110 | - one of them set to secondary jailbreak
111 |
112 |
113 |
114 |
115 | # 4.0
116 |
117 | > **Streaming changes**
118 |
119 | reworked how messages are parsed, again
120 |
121 | > **Superfetch** reworked
122 |
123 | pros:
124 | - streaming
125 | - fast
126 | - more reliable
127 | - no lingering processes
128 | - no firewall issues (hopefully)
129 |
130 | cons:
131 | - the AI typing might look weird
132 | - no android-armv7
133 | - android-arm64 possibly doesn't work
134 | - mac-arm64 possibly doesn't work
135 | - no 32bit for any platform for now
136 | - poor error handling
137 |
138 | tested on linux and windows
139 |
140 | > **SuperfetchHost** and **SuperfetchPort** removed
141 |
142 | > **Minor changes**
143 |
144 | split code into multiple files
145 |
146 | clewd-superfetch and clewd are now the same package
147 |
148 | removed all dependencies
149 |
150 | **git** [added to requirements](https://gitgud.io/ahsk/clewd/#requirements) (highly recommended so update scripts work, otherwise do a clean install)
151 |
152 | # 3.8.5
153 |
154 | fixed memory leak on clewd-superfetch
155 |
156 | added support for custom host/port for superfetch in case you want
157 |
158 | better cleaning up so it is less likely orphan superfetch processes will remain
159 |
160 | # 3.8
161 | > **Superfetch** reworked
162 |
163 | dropped old js files in favor of custom-made ones
164 |
165 | i believe firewalls may block the binaries from connecting to port 443
166 |
167 | also, you should see "superfetch-load *{PATH}*" followed by "superfetch-spawn"
168 |
169 | > **SuperfetchTimeout** added
170 |
171 | controls how much time in seconds until it times out
172 |
173 | default 120
174 |
175 | > **Minor changes**
176 |
177 | new binaries for windows/linux/mac/arm/freebsd
178 |
179 | # 3.7
180 |
181 | using custom library to see if termux works
182 |
183 | # 3.6
184 | > ### **Superfetch** added (defaults true)
185 |
186 | if set to true, will use an alternate method to get past the *"We are unable to serve your request"* error
187 |
188 | if set to false it's the old clewd behavior (if you don't struggle with that error you can keep it on false)
189 |
190 | > **Streaming changes**
191 |
192 | both streaming on/off working
193 |
194 | > **RetryRegen** changed
195 |
196 | now also works with Superfetch
197 |
198 | > **Minor changes**
199 |
200 | error handling for Superfetch (can't do much)
201 |
202 | support name prefixes for group chats when "Add character names" is enabled
203 |
204 | > if you were having trouble with 3.5 but 3.1 or 3.4 were fine, update to this and enable/disable **Superfetch** as you see fit
205 |
206 | # 3.5
207 | > **Streaming changes**
208 |
209 | fixed the dreaded bug, shit is unstable don't expect much
210 |
211 | keep streaming enabled for now
212 |
213 | can try to fix non-stream later, same with error handling and **RetryRegenerate**
214 |
215 | lastly, fuck you rats ;)
216 |
217 | # 3.4
218 | > **Streaming changes**
219 |
220 | turns out I could've fixed the broken formatting long ago, should work great now
221 |
222 | > Prompt conversion and **SystemExperiments** reworked
223 |
224 | > **Prompts** (PromptMain, PromptReminder, PromptContinue) from 3.0 removed
225 |
226 | most frontends are implementing prompt managers so that hack is not needed
227 |
228 | > **PersonalityFormat** and **ScenarioFormat** added
229 |
230 | those are hardcoded on ST and will stay available until they're not
231 |
232 | scenarios and char description are extracted from their hardcoded strings and replaced by the format you set
233 |
234 | - **RealChatPrefix** default is now empty
235 |
236 | > **AllSamples** changed
237 |
238 | no longer excludes the last two messages when transforming
239 |
240 | > **LogMessages** changed
241 |
242 | moved to Settings
243 |
244 | > **Minor changes**
245 |
246 | - Error handling changes
247 |
248 | - RenewAlways is now more stable when set to false. still, regenerate once if you swap characters
249 |
250 | # 3.3
251 | added \[DONE\] to end of streams
252 |
253 | changed more things to support [RisuAI](https://files.catbox.moe/l243nm.png)
254 |
255 | # 3.2
256 | small streaming change to try to fix \n bullshit again
257 |
258 | > **PreserveChats** added (defaults false)
259 |
260 | if set to true, prevents the deletion of chats at any point
261 |
262 | # 3.1
263 | > **Streaming changes**
264 |
265 | if the user did not enable streaming, clewd will **fake** a non-stream response for compatibility
266 |
267 | > **LogMessages** added (defaults false)
268 |
269 | if set to true, will log the prompt and then the reply to a `log.txt` file
270 |
271 | # 3.0
272 | > ### **Clewd requires setting your "Chat Completion source" to OpenAI now. Enable the "External" models option aswell.**
273 |
274 | > ### A config.js file will be generated when first ran, edit it to set your cookie and customize settings
275 |
276 | > **Streaming changes**
277 |
278 | streaming is now enforced by the website so no point supporting the other mode
279 |
280 | Clewd streaming was reworked to behave more like [NBSX](https://gitgud.io/ahsk/nbsx)
281 |
282 | > **AntiStall**, **StallTrigger**, **StallTriggerMax** removed
283 |
284 | no longer needed since streaming is enforced
285 |
286 | > **DeleteChats** removed
287 |
288 | now cleans up by default other than a few cases like when `RenewAlways` is set to false
289 |
290 | > **RecycleChats** removed
291 |
292 | replaced by another system that is activated only if `RenewAlways` is set to false
293 |
294 | > **ReplaceSamples** removed
295 |
296 | replaced by **NoSamples** *or* **AllSamples*
297 |
298 | > **RetryRegenerate** changed
299 |
300 | trigger should be more consistent now
301 |
302 | > **StripAssistant** and **StripHuman** changed
303 |
304 | they remove the Human/Assistant prefixes from the last messages, keeping the message contents
305 |
306 | > **RenewAlways** added (defaults true)
307 |
308 | if set to true, this is the default pre-3.0 clewd behavior
309 |
310 | if set to false, check the `Prompts` section below
311 |
312 | > **NoSamples** added (defaults false)
313 |
314 | if set to true, replaces every previous message with H/A prefix from your frontend into Human/Assistant
315 |
316 | mutually exclusive with `AllSamples`
317 |
318 | (only outgoing)
319 |
320 | > **AllSamples** added (defaults false)
321 |
322 | if set to true, replaces every Human/Assistant prefix from your frontend **except the last two** into H/A
323 |
324 | mutually exclusive with `NoSamples`
325 |
326 | (only outgoing)
327 |
328 | > **SystemExperiments** (defaults true) and **Prompts** added (Main, Reminder, Continue)
329 |
330 | instead of sending a pre-written chunk of text to the AI, now you can customize exactly (almost) how the prompt is formatted before it is sent
331 |
332 | if `RenewAlways` is set to true:
333 | - `Main` is **always** the one being used.
334 |
335 | if `RenewAlways` is set to false:
336 | - `Main` is sent on conversation start
337 | - `Continue` is sent on the next message, as long as no *impersonation* happened
338 | - every `SystemInterval` of messages, `Reminder` is sent once
339 |
340 | ---
341 |
342 | if `SystemExperiments` is set to true:
343 | uses Main then alternates between Reminder and Continue
344 |
345 | if `SystemExperiments` is set to false:
346 | uses Main then Reminder
347 |
348 | sometimes it will renew anyway when detecting some oddity
349 |
350 | ---
351 |
352 | example of custom `Main` prompt with XML tags wrapping around
353 |
354 | - character description
355 | - scenario
356 | - main prompt
357 | - example messages
358 | - real messages
359 |
360 | ```
361 |
362 | {{MAIN_AND_CHARACTER}}
363 | {{CHAT_EXAMPLE}}
364 | {{CHAT_LOG}}
365 |
366 | {{JAILBREAK}}
367 | ```
368 |
369 | > **Minor changes**
370 |
371 | - Clewd errors now should show as such on your frontend
372 |
373 | - flags now show how many days until they expire
374 |
375 | - the message limit error now shows how many hours until the restriction is lifted
376 |
377 | - settings that aren't on their default are now displayed in yellow on startup
378 |
379 | - "[Start a new chat]" is excluded from system prompts
380 |
381 |
382 | # 2.7
383 | > Fixed broken newlines
384 |
385 |
386 | # 2.6
387 | > **AdaptClaude** removed
388 |
389 | > **ReplaceSamples** added (defaults false)
390 |
391 | If set to true, will replace any cases of **outgoing** H/A, to Human/Assistant
392 |
393 | now has **no effect** on incoming, [more info](https://docs.anthropic.com/claude/docs/prompt-troubleshooting-checklist#the-prompt-is-formatted-correctly)
394 |
395 | >**PreventImperson** updated
396 |
397 | now also stops on A: or Assistant:
398 | another check added, *should* impersonate less often
399 |
400 |
401 | # 2.5
402 | > Changed some defaults around
403 |
404 | refer to the [README](https://gitgud.io/ahsk/clewd/#defaults) for the reasoning behind each
405 |
406 | > **DeleteChats** bugfix
407 |
408 |
409 | # 2.4
410 | > **DeleteChats** added (defaults false)
411 |
412 | for privacy, deletes old chats on startup and deletes any new ones after receiving a reply
413 |
414 | > **PreventImperson** bugfix
415 |
416 | should be more accurate now
417 |
418 | > **RetryRegenerate** no longer defaults true
419 |
420 |
421 | # 2.0 to 2.2
422 | > **RetryRegenerate** added (defaults true)
423 |
424 | uses the AI's own retry mechanism when you regenerate on your frontend, if you change anything from your last prompt before regenerating it will default to old behavior
425 |
426 | > **PromptExperiments** added (defaults true)
427 |
428 | an alternative way to send your prompt to the AI, through a file. set to false if it's bad
429 | both enabled: https://files.catbox.moe/io1q53.webm
430 |
431 |
432 | # 1.6 to 2.0
433 | > **AdaptClaude** now should work better with streaming on
434 |
435 | > **AntiStall 2**
436 |
437 | now returns up to the first AI reply instead of the second, **AntiStall 1** sends the whole schizophrenia (good chance of some spicy things being included)
438 |
439 | > **PreventImperson**
440 |
441 | added, stops the bot immediately once it starts pretending to be you (you can use this as kind of a AntiStall, chance of missing some spicy things)
442 |
443 | > **PassParams** added (defaults false)
444 |
445 | now whatever temperature <=1 you set on SillyTavern will be forwarded to claude (if you get errors turn this shit off), also, no idea if this does anything but they accept it
446 |
447 | ---
448 |
449 | > [README](https://gitgud.io/ahsk/clewd/#defaults)
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use the official Node.js image as the base image
2 | FROM node:20.4
3 |
4 | # Set the working directory in the container
5 | WORKDIR /app
6 |
7 | # Copy the package.json and package-lock.json files to the container
8 | COPY package*.json ./
9 |
10 | # Install the dependencies
11 | RUN npm install --no-audit --fund false
12 |
13 | # Copy the rest of the files to the container
14 | COPY . .
15 |
16 | # Change ownership of files in lib/bin and set permissions
17 | RUN chown -R node:node lib/bin/* && \
18 | chmod u+x lib/bin/* && \
19 | chmod -R 777 /app
20 |
21 | # Run as the "node" user for better security practices
22 | USER node
23 |
24 | RUN ls -la
25 |
26 | # Start the application
27 | CMD ["node", "clewd.js"]
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | Clewd
4 |
12 |
13 |
14 |
15 | [原版Clewd](https://gitgud.io/ahsk/clewd)
16 |
17 | [Clewd教程(必读)](https://rentry.org/teralomaniac_clewd)
18 |
19 | Clewd修改版及教程禁止转发任何包含收费项目的群组/论坛或用于收费项目
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ## Requirements
29 |
30 | - [nodejs>=20.4.*](https://nodejs.org/en/download/current)
31 |
32 | - [git>=2.41.*](https://gitforwindows.org/)
33 |
34 | ## Defaults
35 |
36 | ### SettingName: (DEFAULT)/opt1/opt2...
37 |
38 | - `Superfetch`: (true)/false
39 | * true will use an alternate method to get past the `We are unable to serve your request` error
40 | * false won't use this method and you may get the error again
41 |
42 | - `PreventImperson`: (false)/true
43 | * true trims the bot reply immediately if he says "Human:", "Assistant:", "H:" or "A:"
44 | * making it so it doesn't hallucinate speaking as you __(chance of missing some spicy things)__
45 |
46 | - `PromptExperiments`: (true)/false
47 | * true is an alternative way to send your prompt to the AI
48 | * experiment before setting to false
49 |
50 | - `RetryRegenerate`: (false)/true
51 | * true uses the AI's own retry mechanism when you regenerate on your frontend
52 | * instead of a new conversation
53 | * experiment with it
54 |
55 | - `SystemExperiments`: (true)/false
56 | * only has any effect when `RenewAlways` is false
57 | * true alternates between Main+JB+User and JB+User
58 | * false doesn't alternate
59 |
60 | - `RenewAlways`: (true)/false
61 | * true makes a new conversation context each time
62 | * false *tries* to reutilize the same old conversation, sending only your actual last message each time, taking into consideration `SystemExperiments`
63 |
64 | - `StripAssistant`: (false)/true
65 | * true strips the "Assistant:" prefix from the last assistant message
66 | * (check your log.txt to see where it is being stripped, not the same as pre 3.0)
67 |
68 | - `StripHuman`: (false)/true
69 | * true strips the "Human:" prefix from the last human message
70 |
71 | - `AllSamples`: (false)/true
72 | * mutually exclusive with `NoSamples`
73 | * true converts all real dialogue to "sample dialogue" except the last Assistant and Human
74 | * you're "H" and the AI is "A"
75 | * whatever the AI replies with is kept (only outgoing)
76 | * [see this](https://docs.anthropic.com/claude/docs/prompt-troubleshooting-checklist#the-prompt-is-formatted-correctly) for more information
77 | - Human->H
78 | - Assistant->A
79 |
80 | - `NoSamples`: (false)/true
81 | * mutually exclusive with `AllSamples`
82 | * true converts all "sample dialogue" to real dialogue
83 | * you're "Human" and the AI is "Assistant"
84 | * whatever the AI replies with is kept (only outgoing)
85 | * [see this](https://docs.anthropic.com/claude/docs/prompt-troubleshooting-checklist#the-prompt-is-formatted-correctly) for more information
86 | - H->Human
87 | - A->Assistant
88 |
89 | - `LogMessages`: (false)/true
90 | * true logs prompt and reply to `log.txt`
91 |
92 | - `ClearFlags`: (false)/true
93 | * possibly snake-oil
94 | * clears your warnings
95 |
96 | - `PassParams`: (false)/true
97 | * true will send the temperature you set on your frontend
98 | * only values under <=1.0 >= 0.1
99 | * this could get your account banned
100 | * if clewd stops working, set to false
101 |
102 | - `PreserveChats`: (false)/true
103 | * true prevents the deletion of old chats at any point
104 |
105 |
106 |
107 | ## Examples
108 |
109 | **safe setup**
110 | > **PreventImperson**: false (higher chance of spicy stuff)
111 |
112 | > **RenewAlways**: true
113 |
114 | ---
115 |
116 | **experimental setup**
117 | > **PromptExperiments**: true
118 |
119 | > **SystemExperiments**: true
120 |
121 | > **RetryRegenerate**: true
122 |
123 | > **PreventImperson**: true
124 |
125 | > **RenewAlways**: false
126 |
127 | > **AllSamples**: true
--------------------------------------------------------------------------------
/clewd.js:
--------------------------------------------------------------------------------
1 | /*
2 | * https://rentry.org/teralomaniac_clewd
3 | * https://github.com/teralomaniac/clewd
4 | */
5 | 'use strict';
6 |
7 | const {createServer: Server, IncomingMessage, ServerResponse} = require('node:http'), {createHash: Hash, randomUUID, randomInt, randomBytes} = require('node:crypto'), {TransformStream, ReadableStream} = require('node:stream/web'), {Readable, Writable} = require('node:stream'), {Blob} = require('node:buffer'), {existsSync: exists, writeFileSync: write, createWriteStream} = require('node:fs'), {join: joinP} = require('node:path'), {ClewdSuperfetch: Superfetch, SuperfetchAvailable, SuperfetchFoldersMk, SuperfetchFoldersRm} = require('./lib/clewd-superfetch'), {AI, fileName, genericFixes, bytesToSize, setTitle, checkResErr, Replacements, Main} = require('./lib/clewd-utils'), ClewdStream = require('./lib/clewd-stream');
8 |
9 | /******************************************************* */
10 | let currentIndex, Firstlogin = true, changeflag = 0, changing, changetime = 0, totaltime, uuidOrgArray = [], model, cookieModel, tokens, apiKey, timestamp, regexLog, isPro, modelList = [];
11 |
12 | const url = require('url');
13 | const asyncPool = async (poolLimit, array, iteratorFn) => {
14 | const ret = [], executing = [];
15 | for (const item of array) {
16 | const p = Promise.resolve().then(() => iteratorFn(item));
17 | ret.push(p);
18 | if (poolLimit <= array.length) {
19 | const e = p.then(() => executing.splice(executing.indexOf(e), 1));
20 | executing.push(e);
21 | if (executing.length >= poolLimit) await Promise.race(executing);
22 | }
23 | }
24 | return Promise.all(ret);
25 | }, convertToType = value => {
26 | if (value === 'true') return true;
27 | if (value === 'false') return false;
28 | if (/^\d+$/.test(value)) return parseInt(value);
29 | return value;
30 | }, CookieChanger = (resetTimer = true, cleanup = false) => {
31 | if (Config.CookieArray?.length <= 1) {
32 | return changing = false;
33 | } else {
34 | changeflag = 0, changing = true;
35 | if(!cleanup) {
36 | currentIndex = (currentIndex + 1) % Config.CookieArray.length;
37 | console.log(`Changing Cookie...\n`);
38 | }
39 | setTimeout(() => {
40 | onListen();
41 | resetTimer && (timestamp = Date.now());
42 | }, !Config.rProxy || Config.rProxy === AI.end() ? 15000 + timestamp - Date.now() : 0);
43 | }
44 | }, CookieCleaner = (flag, percentage) => {
45 | Config.WastedCookie.push(flag + '@' + Config.CookieArray[currentIndex].split('@').toReversed()[0]);
46 | Config.CookieArray.splice(currentIndex, 1), Config.Cookie = '';
47 | Config.Cookiecounter < 0 && console.log(`[progress]: [32m${percentage.toFixed(2)}%[0m\n[length]: [33m${Config.CookieArray.length}[0m\n`);
48 | console.log(`Cleaning Cookie...\n`);
49 | writeSettings(Config);
50 | return CookieChanger(true, true);
51 | }, padtxt = content => {
52 | const {countTokens} = require('@anthropic-ai/tokenizer');
53 | tokens = countTokens(content);
54 | const padtxt = String(Config.Settings.padtxt).split(',').reverse(), maxtokens = parseInt(padtxt[0]), extralimit = parseInt(padtxt[1]) || 1000, minlimit = parseInt(padtxt[2]);
55 | const placeholder = (tokens > maxtokens - extralimit && minlimit ? Config.placeholder_byte : Config.placeholder_token) || randomBytes(randomInt(5, 15)).toString('hex');
56 | const placeholdertokens = countTokens(placeholder.trim());
57 | for (let match; match = content.match(/<\|padtxt.*?(\d+)t.*?\|>/); content = content.replace(match[0], placeholder.repeat(parseInt(match[1]) / placeholdertokens))) tokens += parseInt(match[1]);
58 | if(/<\|padtxt off.*?\|>/.test(content)) return content.replace(/\s*<\|padtxt.*?\|>\s*/g, '\n\n');
59 | const padding = placeholder.repeat(Math.min(maxtokens, (tokens <= maxtokens - extralimit ? maxtokens - tokens : minlimit ? minlimit : extralimit)) / placeholdertokens);
60 | content = /<\|padtxt.*?\|>/.test(content) ? content.replace(/<\|padtxt.*?\|>/, padding).replace(/\s*<\|padtxt.*?\|>\s*/g, '\n\n') : !apiKey ? padding + '\n\n\n' + content.trim() : content;
61 | return content;
62 | }, xmlPlot_merge = (content, mergeTag, nonsys) => {
63 | if (/(\n\n|^\s*)xmlPlot:\s*/.test(content)) {
64 | content = (nonsys ? content : content.replace(/(\n\n|^\s*)(? {
70 | let matches = content.match(new RegExp(` *"(/?)(.*)\\1(.*?)" *: *"(.*?)" *`, 'gm'));
71 | matches && matches.forEach(match => {
72 | try {
73 | const reg = / *"(\/?)(.*)\1(.*?)" *: *"(.*?)" *<\/regex>/.exec(match);
74 | regexLog += match + '\n';
75 | content = content.replace(new RegExp(reg[2], reg[3]), JSON.parse(`"${reg[4].replace(/\\?"/g, '\\"')}"`));
76 | } catch (err) {
77 | console.log(`[33mRegex error: [0m` + match + '\n' + err);
78 | }
79 | });
80 | return content;
81 | }, xmlPlot = (content, nonsys = false) => {
82 | regexLog = '';
83 | //一次正则
84 | content = xmlPlot_regex(content, 1);
85 | //一次role合并
86 | const mergeTag = {
87 | all: !content.includes('<|Merge Disable|>'),
88 | system: !content.includes('<|Merge System Disable|>'),
89 | human: !content.includes('<|Merge Human Disable|>'),
90 | assistant: !content.includes('<|Merge Assistant Disable|>')
91 | };
92 | content = xmlPlot_merge(content, mergeTag, nonsys);
93 | //自定义插入
94 | let splitContent = content.split(/\n\n(?=Assistant:|Human:)/g), match;
95 | while ((match = /<@(\d+)>(.*?)<\/@\1>/gs.exec(content)) !== null) {
96 | let index = splitContent.length - parseInt(match[1]) - 1;
97 | index >= 0 && (splitContent[index] += '\n\n' + match[2]);
98 | content = content.replace(match[0], '');
99 | }
100 | content = splitContent.join('\n\n').replace(/<@(\d+)>.*?<\/@\1>/gs, '');
101 | //二次正则
102 | content = xmlPlot_regex(content, 2);
103 | //二次role合并
104 | content = xmlPlot_merge(content, mergeTag, nonsys);
105 | //Plain Prompt
106 | let segcontentHuman = content.split('\n\nHuman:');
107 | let segcontentlastIndex = segcontentHuman.length - 1;
108 | if (!apiKey && segcontentlastIndex >= 2 && segcontentHuman[segcontentlastIndex].includes('<|Plain Prompt Enable|>') && !content.includes('\n\nPlainPrompt:')) {
109 | content = segcontentHuman.slice(0, segcontentlastIndex).join('\n\nHuman:') + '\n\nPlainPrompt:' + segcontentHuman.slice(segcontentlastIndex).join('\n\nHuman:').replace(/\n\nHuman: *PlainPrompt:/, '\n\nPlainPrompt:');
110 | }
111 | //三次正则
112 | content = xmlPlot_regex(content, 3);
113 | //消除空XML tags、两端空白符和多余的\n
114 | content = content.replace(/.*?<\/regex>/gm, '')
115 | .replace(/\r\n|\r/gm, '\n')
116 | .replace(/\s*<\|curtail\|>\s*/g, '\n')
117 | .replace(/\s*<\|join\|>\s*/g, '')
118 | .replace(/\s*<\|space\|>\s*/g, ' ')
119 | .replace(/\s*\n\n(H(uman)?|A(ssistant)?): +/g, '\n\n$1: ')
120 | .replace(/<\|(\\.*?)\|>/g, function(match, p1) {
121 | try {
122 | return JSON.parse(`"${p1.replace(/\\?"/g, '\\"')}"`);
123 | } catch { return match }
124 | });
125 | //确保格式正确
126 | if (apiKey) {
127 | content = content.replace(/(\n\nHuman:(?!.*?\n\nAssistant:).*?|(?\s*(.*?)(?:\n\nAssistant:\s*)?$/s, '\n\n$1');
128 | content.includes('<|reverseHA|>') && (content = content.replace(/\s*<\|reverseHA\|>\s*/g, '\n\n').replace(/Assistant|Human/g, function(match) {return match === 'Human' ? 'Assistant' : 'Human'}).replace(/\n(A|H): /g, function(match, p1) {return p1 === 'A' ? '\nH: ' : '\nA: '}));
129 | return content.replace(Config.Settings.padtxt ? /\s*<\|(?!padtxt).*?\|>\s*/g : /\s*<\|.*?\|>\s*/g, '\n\n').trim().replace(/^.+:/, '\n\n$&').replace(/(?<=\n)\n(?=\n)/g, '');
130 | } else {
131 | return content.replace(Config.Settings.padtxt ? /\s*<\|(?!padtxt).*?\|>\s*/g : /\s*<\|.*?\|>\s*/g, '\n\n').trim().replace(/^Human: *|\n\nAssistant: *$/g, '').replace(/(?<=\n)\n(?=\n)/g, '');
132 | }
133 | }, waitForChange = () => {
134 | return new Promise(resolve => {
135 | const interval = setInterval(() => {
136 | if (!changing) {
137 | clearInterval(interval);
138 | resolve();
139 | }
140 | }, 100);
141 | });
142 | };
143 | /******************************************************* */
144 |
145 | let ChangedSettings, UnknownSettings, Logger;
146 |
147 | const ConfigPath = joinP(__dirname, './config.js'), LogPath = joinP(__dirname, './log.txt'), Conversation = {
148 | char: null,
149 | uuid: null,
150 | depth: 0
151 | }, cookies = {};
152 |
153 | let uuidOrg, curPrompt = {}, prevPrompt = {}, prevMessages = [], prevImpersonated = false, Config = {
154 | Cookie: '',
155 | CookieArray: [],
156 | WastedCookie: [],
157 | unknownModels: [],
158 | Cookiecounter: 3,
159 | CookieIndex: 0,
160 | ProxyPassword: '',
161 | Ip: (process.env.Cookie || process.env.CookieArray) ? '0.0.0.0' : '127.0.0.1',
162 | Port: process.env.PORT || 8444,
163 | localtunnel: false,
164 | BufferSize: 1,
165 | SystemInterval: 3,
166 | rProxy: '',
167 | api_rProxy: '',
168 | placeholder_token: '',
169 | placeholder_byte: '',
170 | PromptExperimentFirst: '',
171 | PromptExperimentNext: '',
172 | PersonalityFormat: '{{char}}\'s personality: {{personality}}',
173 | ScenarioFormat: 'Dialogue scenario: {{scenario}}',
174 | Settings: {
175 | RenewAlways: true,
176 | RetryRegenerate: false,
177 | PromptExperiments: true,
178 | SystemExperiments: true,
179 | PreventImperson: true,
180 | AllSamples: false,
181 | NoSamples: false,
182 | StripAssistant: false,
183 | StripHuman: false,
184 | PassParams: true,
185 | ClearFlags: true,
186 | PreserveChats: false,
187 | LogMessages: true,
188 | FullColon: true,
189 | padtxt: "1000,1000,15000",
190 | xmlPlot: true,
191 | SkipRestricted: false,
192 | Artifacts: false,
193 | Superfetch: true
194 | }
195 | };
196 |
197 | ServerResponse.prototype.json = async function(body, statusCode = 200, headers) {
198 | body = body instanceof Promise ? await body : body;
199 | this.headersSent || this.writeHead(statusCode, {
200 | 'Content-Type': 'application/json',
201 | ...headers && headers
202 | });
203 | this.end('object' == typeof body ? JSON.stringify(body) : body);
204 | return this;
205 | };
206 |
207 | Array.prototype.sample = function() {
208 | return this[Math.floor(Math.random() * this.length)];
209 | };
210 |
211 | const updateParams = res => {
212 | updateCookies(res);
213 | }, updateCookies = res => {
214 | let cookieNew = '';
215 | res instanceof Response ? cookieNew = res.headers?.get('set-cookie') : res?.superfetch ? cookieNew = res.headers?.['set-cookie'] : 'string' == typeof res && (cookieNew = res.split('\n').join(''));
216 | if (!cookieNew) {
217 | return;
218 | }
219 | let cookieArr = cookieNew.split(/;\s?/gi).filter((prop => false === /^(path|expires|domain|HttpOnly|Secure|SameSite)[=;]*/i.test(prop)));
220 | for (const cookie of cookieArr) {
221 | const divide = cookie.split(/^(.*?)=\s*(.*)/), cookieName = divide[1], cookieVal = divide[2];
222 | cookies[cookieName] = cookieVal;
223 | }
224 | }, getCookies = () => {
225 | const cookieNames = Object.keys(cookies);
226 | return cookieNames.map(((name, idx) => `${name}=${cookies[name]}${idx === cookieNames.length - 1 ? '' : ';'}`)).join(' ').replace(/(\s+)$/gi, '');
227 | }, deleteChat = async uuid => {
228 | if (!uuid) {
229 | return;
230 | }
231 | if (uuid === Conversation.uuid) {
232 | Conversation.uuid = null;
233 | Conversation.depth = 0;
234 | }
235 | if (Config.Settings.PreserveChats) {
236 | return;
237 | }
238 | const res = await (Config.Settings.Superfetch ? Superfetch : fetch)(`${Config.rProxy || AI.end()}/api/organizations/${uuidOrg}/chat_conversations/${uuid}`, {
239 | headers: {
240 | ...AI.hdr(),
241 | Cookie: getCookies()
242 | },
243 | method: 'DELETE'
244 | });
245 | updateParams(res);
246 | }, onListen = async () => {
247 | /***************************** */
248 | if (Firstlogin) {
249 | Firstlogin = false, timestamp = Date.now(), totaltime = Config.CookieArray.length;
250 | console.log(`[2m${Main}[0m\n[33mhttp://${Config.Ip}:${Config.Port}/v1[0m\n\n${Object.keys(Config.Settings).map((setting => UnknownSettings?.includes(setting) ? `??? [31m${setting}: ${Config.Settings[setting]}[0m` : `[1m${setting}:[0m ${ChangedSettings?.includes(setting) ? '[33m' : '[36m'}${Config.Settings[setting]}[0m`)).sort().join('\n')}\n`); //↓
251 | if (Config.Settings.Superfetch) {
252 | SuperfetchAvailable(true);
253 | SuperfetchFoldersMk();
254 | }
255 | if (Config.localtunnel) {
256 | const localtunnel = require('localtunnel');
257 | localtunnel({ port: Config.Port }).then((tunnel) => {
258 | console.log(`\nTunnel URL for outer websites: ${tunnel.url}/v1\n`);
259 | })
260 | }
261 | }
262 | if (Config.CookieArray?.length > 0) {
263 | const cookieInfo = /(?:(claude[-_][a-z0-9-_]*?)@)?(?:sessionKey=)?(sk-ant-sid01-[\w-]{86}-[\w-]{6}AA)/.exec(Config.CookieArray[currentIndex]);
264 | cookieInfo?.[2] && (Config.Cookie = 'sessionKey=' + cookieInfo[2]);
265 | changetime++;
266 | if (model && cookieInfo?.[1] && !/claude[\w]*?_pro/.test(cookieInfo?.[1]) && cookieInfo?.[1] != model) return CookieChanger(false);
267 | }
268 | let percentage = ((changetime + Math.max(Config.CookieIndex - 1, 0)) / totaltime) * 100
269 | if (Config.Cookiecounter < 0 && percentage > 100) {
270 | console.log(`\n※※※Cookie cleanup completed※※※\n\n`);
271 | return process.exit();
272 | }
273 | try {
274 | /***************************** */
275 | if ('SET YOUR COOKIE HERE' === Config.Cookie || Config.Cookie?.length < 1) {
276 | return changing = false, console.log(`[33mNo cookie available, enter apiKey-only mode.[0m\n`); //throw Error('Set your cookie inside config.js');
277 | }
278 | updateCookies(Config.Cookie);
279 | /**************************** */
280 | const bootstrapRes = await (Config.Settings.Superfetch ? Superfetch : fetch)((Config.rProxy || AI.end()) + `/api/bootstrap`, {
281 | method: 'GET',
282 | headers: {
283 | ...AI.hdr(),
284 | Cookie: getCookies()
285 | }
286 | });
287 | await checkResErr(bootstrapRes);
288 | const bootstrap = await bootstrapRes.json();
289 | if (bootstrap.account === null) {
290 | console.log(`[35mNull![0m`);
291 | return CookieCleaner('Null', percentage);
292 | }
293 | const bootAccInfo = bootstrap.account.memberships.find(item => item.organization.capabilities.includes('chat')).organization;
294 | cookieModel = bootstrap.statsig.values.layer_configs["HPOHwBLNLQLxkj5Yn4bfSkgCQnBX28kPR7h/BNKdVLw="]?.value?.console_default_model_override?.model || bootstrap.statsig.values.dynamic_configs["6zA9wvTedwkzjLxWy9PVe7yydI00XDQ6L5Fejjq/2o8="]?.value?.model;
295 | isPro = bootAccInfo.capabilities.includes('claude_pro') && 'claude_pro' || bootAccInfo.capabilities.includes('raven') && 'claude_team_pro';
296 | const unknown = cookieModel && !(AI.mdl().includes(cookieModel) || Config.unknownModels.includes(cookieModel));
297 | if (Config.CookieArray?.length > 0 && (isPro || cookieModel) != Config.CookieArray[currentIndex].split('@')[0] || unknown) {
298 | Config.CookieArray[currentIndex] = (isPro || cookieModel) + '@' + Config.Cookie;
299 | unknown && Config.unknownModels.push(cookieModel);
300 | writeSettings(Config);
301 | }
302 | if (!isPro && model && model != cookieModel) return CookieChanger();
303 | console.log(Config.CookieArray?.length > 0 ? `(index: [36m${currentIndex + 1 || Config.CookieArray.length}[0m) Logged in %o` : 'Logged in %o', { //console.log('Logged in %o', { ↓
304 | name: bootAccInfo.name?.split('@')?.[0],
305 | mail: bootstrap.account.email_address, //
306 | cookieModel, //
307 | capabilities: bootAccInfo.capabilities
308 | }); //↓
309 | if (uuidOrgArray.includes(bootAccInfo.uuid) && percentage <= 100 && Config.CookieArray?.length > 0 || bootAccInfo.api_disabled_reason && !bootAccInfo.api_disabled_until || !bootstrap.account.completed_verification_at) {
310 | const flag = bootAccInfo.api_disabled_reason ? 'Disabled' : !bootstrap.account.completed_verification_at ? 'Unverified' : 'Overlap';
311 | console.log(`[31m${flag}![0m`);
312 | return CookieCleaner(flag, percentage);
313 | } else uuidOrgArray.push(bootAccInfo.uuid);
314 | if (Config.Cookiecounter < 0) {
315 | console.log(`[progress]: [32m${percentage.toFixed(2)}%[0m\n[length]: [33m${Config.CookieArray.length}[0m\n`);
316 | return CookieChanger();
317 | }
318 | /**************************** */
319 | const accRes = await (Config.Settings.Superfetch ? Superfetch : fetch)((Config.rProxy || AI.end()) + '/api/organizations', {
320 | method: 'GET',
321 | headers: {
322 | ...AI.hdr(),
323 | Cookie: getCookies()
324 | }
325 | });
326 | await checkResErr(accRes);
327 | const accInfo = (await accRes.json())?.find(item => item.capabilities.includes('chat')); //const accInfo = (await accRes.json())?.[0];\nif (!accInfo || accInfo.error) {\n throw Error(`Couldn't get account info: "${accInfo?.error?.message || accRes.statusText}"`);\n}\nif (!accInfo?.uuid) {\n throw Error('Invalid account id');\n}
328 | setTitle('ok');
329 | updateParams(accRes);
330 | uuidOrg = accInfo?.uuid;
331 | if (accInfo?.active_flags.length > 0) {
332 | let banned = false; //
333 | const now = new Date, formattedFlags = accInfo.active_flags.map((flag => {
334 | const days = ((new Date(flag.expires_at).getTime() - now.getTime()) / 864e5).toFixed(2);
335 | 'consumer_banned' === flag.type && (banned = true); //
336 | return {
337 | type: flag.type,
338 | remaining_days: days
339 | };
340 | }));
341 | console.warn(`${banned ? '[31m' : '[35m'}Your account has warnings[0m %o`, formattedFlags); //console.warn('[31mYour account has warnings[0m %o', formattedFlags);
342 | await Promise.all(accInfo.active_flags.map((flag => (async type => {
343 | if (!Config.Settings.ClearFlags) {
344 | return;
345 | }
346 | if ('consumer_restricted_mode' === type || 'consumer_banned' === type) { //if ('consumer_restricted_mode' === type) {
347 | return;
348 | }
349 | const req = await (Config.Settings.Superfetch ? Superfetch : fetch)(`${Config.rProxy || AI.end()}/api/organizations/${uuidOrg}/flags/${type}/dismiss`, {
350 | headers: {
351 | ...AI.hdr(),
352 | Cookie: getCookies()
353 | },
354 | method: 'POST'
355 | });
356 | updateParams(req);
357 | const json = await req.json();
358 | console.log(`${type}: ${json.error ? json.error.message || json.error.type || json.detail : 'OK'}`);
359 | })(flag.type))));
360 | console.log(`${banned ? '[31mBanned' : '[35mRestricted'}![0m`); //
361 | if (banned) return CookieCleaner('Banned') //
362 | else if (Config.Settings.SkipRestricted) return CookieChanger(); //
363 | }
364 | if (bootstrap.account.settings.preview_feature_uses_artifacts != Config.Settings.Artifacts) {
365 | const settingsRes = await (Config.Settings.Superfetch ? Superfetch : fetch)((Config.rProxy || AI.end()) + `/api/account`, {
366 | method: 'PUT',
367 | headers: {
368 | ...AI.hdr(),
369 | Cookie: getCookies()
370 | },
371 | body: JSON.stringify({ settings: Object.assign(bootstrap.account.settings, { preview_feature_uses_artifacts: Config.Settings.Artifacts }) }),
372 | });
373 | await checkResErr(settingsRes);
374 | updateParams(settingsRes);
375 | }
376 | changing = false;
377 | const convRes = await (Config.Settings.Superfetch ? Superfetch : fetch)(`${Config.rProxy || AI.end()}/api/organizations/${accInfo.uuid}/chat_conversations`, { //const convRes = await fetch(`${Config.rProxy || AI.end()}/api/organizations/${uuidOrg}/chat_conversations`, {
378 | method: 'GET',
379 | headers: {
380 | ...AI.hdr(),
381 | Cookie: getCookies()
382 | }
383 | }), conversations = await convRes.json();
384 | updateParams(convRes);
385 | conversations.length > 0 && await asyncPool(10, conversations, async (conv) => await deleteChat(conv.uuid)); //await Promise.all(conversations.map((conv => deleteChat(conv.uuid))));
386 | /***************************** */
387 | } catch (err) {
388 | if (err.message === 'Invalid authorization') {
389 | console.log(`[31mInvalid![0m`);
390 | return CookieCleaner('Invalid', percentage);
391 | }
392 | console.error('[33mClewd:[0m\n%o', err);
393 | CookieChanger();
394 | }
395 | /***************************** */
396 | }, writeSettings = async (config, firstRun = false) => {
397 | if (process.env.Cookie || process.env.CookieArray) return; //
398 | write(ConfigPath, `/*\n* https://rentry.org/teralomaniac_clewd\n* https://github.com/teralomaniac/clewd\n*/\n\n// SET YOUR COOKIE BELOW\n\nmodule.exports = ${JSON.stringify(config, null, 4)}\n\n/*\n BufferSize\n * How many characters will be buffered before the AI types once\n * lower = less chance of \`PreventImperson\` working properly\n\n ---\n\n SystemInterval\n * How many messages until \`SystemExperiments alternates\`\n\n ---\n\n Other settings\n * https://gitgud.io/ahsk/clewd/#defaults\n * and\n * https://gitgud.io/ahsk/clewd/-/blob/master/CHANGELOG.md\n */`.trim().replace(/((? {
404 | if ('OPTIONS' === req.method) {
405 | return ((req, res) => {
406 | res.writeHead(200, {
407 | 'Access-Control-Allow-Origin': '*',
408 | 'Access-Control-Allow-Headers': 'Authorization, Content-Type',
409 | 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS'
410 | }).end();
411 | })(0, res);
412 | }
413 | const URL = url.parse(req.url.replace(/\/v1(\?.*)\$(\/.*)$/, '/v1$2$1'), true);
414 | const api_rProxy = URL.query?.api_rProxy || Config.api_rProxy;
415 | req.url = URL.pathname;
416 | switch (req.url) {
417 | case '/v1/models':
418 | /***************************** */
419 | (async (req, res) => {
420 | let models;
421 | if (/oaiKey:/.test(req.headers.authorization)) {
422 | try {
423 | const modelsRes = await fetch(api_rProxy.replace(/(\/v1)?\/? *$/, '') + '/v1/models', {
424 | method: 'GET',
425 | headers: { authorization: req.headers.authorization.match(/(?<=oaiKey:).*/)?.[0].split(',')[0].trim() }
426 | });
427 | models = await modelsRes.json();
428 | } catch(err) {}
429 | }
430 | res.json({
431 | data: [
432 | ...AI.mdl().concat(Config.unknownModels).map((name => ({ id: name })))
433 | ].concat(models?.data).reduce((acc, current, index) => {
434 | index === 0 && modelList.splice(0);
435 | if (current?.id && acc.every(model => model.id != current.id)) {
436 | acc.push(current);
437 | modelList.push(current.id);
438 | }
439 | return acc;
440 | }, [])
441 | });
442 | })(req, res); //res.json({\n data: AI.mdl().map((name => ({\n id: name\n })))\n});
443 | /***************************** */
444 | break;
445 |
446 | case '/v1/chat/completions':
447 | ((req, res) => {
448 | setTitle('recv...');
449 | let fetchAPI;
450 | const abortControl = new AbortController, {signal} = abortControl;
451 | res.socket.on('close', (async () => {
452 | abortControl.signal.aborted || abortControl.abort();
453 | }));
454 | const buffer = [];
455 | req.on('data', (chunk => {
456 | buffer.push(chunk);
457 | }));
458 | req.on('end', (async () => {
459 | let clewdStream, titleTimer, samePrompt = false, shouldRenew = true, retryRegen = false, exceeded_limit = false, nochange = false; //let clewdStream, titleTimer, samePrompt = false, shouldRenew = true, retryRegen = false;
460 | try {
461 | const body = JSON.parse(Buffer.concat(buffer).toString());
462 | let {temperature} = body;
463 | temperature = typeof temperature === 'number' ? Math.max(.1, Math.min(1, temperature)) : undefined; //temperature = Math.max(.1, Math.min(1, temperature));
464 | let {messages} = body;
465 | /************************* */
466 | const thirdKey = req.headers.authorization?.match(/(?<=(3rd|oai)Key:).*/), oaiAPI = /oaiKey:/.test(req.headers.authorization), forceModel = /--force/.test(body.model);
467 | apiKey = thirdKey?.[0].split(',').map(item => item.trim()) || req.headers.authorization?.match(/sk-ant-api\d\d-[\w-]{86}-[\w-]{6}AA/g);
468 | model = apiKey || forceModel || isPro ? body.model.replace(/--force/, '').trim() : cookieModel;
469 | let max_tokens_to_sample = body.max_tokens, stop_sequences = body.stop, top_p = typeof body.top_p === 'number' ? body.top_p : undefined, top_k = typeof body.top_k === 'number' ? body.top_k : undefined;
470 | if (!apiKey && (Config.ProxyPassword != '' && req.headers.authorization != 'Bearer ' + Config.ProxyPassword || !uuidOrg)) {
471 | throw Error(uuidOrg ? 'ProxyPassword Wrong' : 'No cookie available or apiKey format wrong');
472 | } else if (!changing && !apiKey && (!isPro && model != cookieModel)) CookieChanger();
473 | await waitForChange();
474 | /************************* */
475 | if (messages?.length < 1) {
476 | throw Error('Select OpenAI as completion source');
477 | }
478 | if (!body.stream && 1 === messages.length && JSON.stringify(messages.sort() || []) === JSON.stringify([ {
479 | role: 'user',
480 | content: 'Hi'
481 | } ].sort())) {
482 | return res.json({
483 | choices: [ {
484 | message: {
485 | content: Main
486 | }
487 | } ]
488 | });
489 | }
490 | res.setHeader('Access-Control-Allow-Origin', '*');
491 | body.stream && res.setHeader('Content-Type', 'text/event-stream');
492 | if (!body.stream && messages?.[0]?.content?.startsWith('From the list below, choose a word that best represents a character\'s outfit description, action, or emotion in their dialogue')) {
493 | return res.json({
494 | choices: [ {
495 | message: {
496 | content: 'neutral'
497 | }
498 | } ]
499 | });
500 | }
501 | if (Config.Settings.AllSamples && Config.Settings.NoSamples) {
502 | console.log('[33mhaving[0m [1mAllSamples[0m and [1mNoSamples[0m both set to true is not supported');
503 | throw Error('Only one can be used at the same time: AllSamples/NoSamples');
504 | }
505 | //const model = body.model;//if (model === AI.mdl()[0]) {// return;//}
506 | if (!modelList.includes(model) && !/claude-.*/.test(model) && !forceModel) {
507 | throw Error('Invalid model selected: ' + model);
508 | }
509 | curPrompt = {
510 | firstUser: messages.find((message => 'user' === message.role)),
511 | firstSystem: messages.find((message => 'system' === message.role)),
512 | firstAssistant: messages.find((message => 'assistant' === message.role)),
513 | lastUser: messages.findLast((message => 'user' === message.role)),
514 | lastSystem: messages.findLast((message => 'system' === message.role && '[Start a new chat]' !== message.content)),
515 | lastAssistant: messages.findLast((message => 'assistant' === message.role))
516 | };
517 | prevPrompt = {
518 | ...prevMessages.length > 0 && {
519 | firstUser: prevMessages.find((message => 'user' === message.role)),
520 | firstSystem: prevMessages.find((message => 'system' === message.role)),
521 | firstAssistant: prevMessages.find((message => 'assistant' === message.role)),
522 | lastUser: prevMessages.findLast((message => 'user' === message.role)),
523 | lastSystem: prevMessages.find((message => 'system' === message.role && '[Start a new chat]' !== message.content)),
524 | lastAssistant: prevMessages.findLast((message => 'assistant' === message.role))
525 | }
526 | };
527 | samePrompt = JSON.stringify(messages.filter((message => 'system' !== message.role)).sort()) === JSON.stringify(prevMessages.filter((message => 'system' !== message.role)).sort());
528 | const sameCharDiffChat = !samePrompt && curPrompt.firstSystem?.content === prevPrompt.firstSystem?.content && curPrompt.firstUser?.content !== prevPrompt.firstUser?.content;
529 | shouldRenew = Config.Settings.RenewAlways || !Conversation.uuid || prevImpersonated || !Config.Settings.RenewAlways && samePrompt || sameCharDiffChat;
530 | retryRegen = Config.Settings.RetryRegenerate && samePrompt && null != Conversation.uuid;
531 | samePrompt || (prevMessages = JSON.parse(JSON.stringify(messages)));
532 | let type = '';
533 | if (apiKey) { type = 'api'; } else if (retryRegen) { //if (retryRegen) {
534 | type = 'R';
535 | fetchAPI = await (async (signal, model) => {
536 | let res;
537 | const body = {
538 | prompt: '',
539 | parent_message_uuid: '',
540 | timezone: AI.zone(),
541 | attachments: [],
542 | files: [],
543 | rendering_mode: 'raw'
544 | };
545 | let headers = {
546 | ...AI.hdr(Conversation.uuid || ''),
547 | Accept: 'text/event-stream',
548 | Cookie: getCookies()
549 | };
550 | if (Config.Settings.Superfetch) {
551 | const names = Object.keys(headers), values = Object.values(headers);
552 | headers = names.map(((header, idx) => `${header}: ${values[idx]}`));
553 | }
554 | res = await (Config.Settings.Superfetch ? Superfetch : fetch)((Config.rProxy || AI.end()) + `/api/organizations/${uuidOrg || ''}/chat_conversations/${Conversation.uuid || ''}/retry_completion`, {
555 | stream: true,
556 | signal,
557 | method: 'POST',
558 | body: JSON.stringify(body),
559 | headers
560 | });
561 | updateParams(res);
562 | await checkResErr(res);
563 | return res;
564 | })(signal, model);
565 | } else if (shouldRenew) {
566 | Conversation.uuid && await deleteChat(Conversation.uuid);
567 | fetchAPI = await (async signal => {
568 | Conversation.uuid = randomUUID().toString();
569 | Conversation.depth = 0;
570 | const res = await (Config.Settings.Superfetch ? Superfetch : fetch)(`${Config.rProxy || AI.end()}/api/organizations/${uuidOrg}/chat_conversations`, {
571 | signal,
572 | headers: {
573 | ...AI.hdr(),
574 | Cookie: getCookies()
575 | },
576 | method: 'POST',
577 | body: JSON.stringify({
578 | uuid: Conversation.uuid,
579 | name: ''
580 | })
581 | });
582 | updateParams(res);
583 | await checkResErr(res);
584 | return res;
585 | })(signal);
586 | type = 'r';
587 | } else if (samePrompt) {} else {
588 | const systemExperiment = !Config.Settings.RenewAlways && Config.Settings.SystemExperiments;
589 | if (!systemExperiment || systemExperiment && Conversation.depth >= Config.SystemInterval) {
590 | type = 'c-r';
591 | Conversation.depth = 0;
592 | } else {
593 | type = 'c-c';
594 | Conversation.depth++;
595 | }
596 | }
597 | let {prompt, systems} = ((messages, type) => {
598 | const rgxScenario = /^\[Circumstances and context of the dialogue: ([\s\S]+?)\.?\]$/i, rgxPerson = /^\[([\s\S]+?)'s personality: ([\s\S]+?)\]$/i, messagesClone = JSON.parse(JSON.stringify(messages)), realLogs = messagesClone.filter((message => [ 'user', 'assistant' ].includes(message.role))), sampleLogs = messagesClone.filter((message => message.name)), mergedLogs = [ ...sampleLogs, ...realLogs ];
599 | mergedLogs.forEach(((message, idx) => {
600 | const next = mergedLogs[idx + 1];
601 | message.customname = (message => [ 'assistant', 'user' ].includes(message.role) && null != message.name && !(message.name in Replacements))(message);
602 | if (next && !Config.Settings.xmlPlot) { //if (next) {
603 | if ('name' in message && 'name' in next) {
604 | if (message.name === next.name) {
605 | message.content += '\n' + next.content;
606 | next.merged = true;
607 | }
608 | } else if ('system' !== next.role) {
609 | if (next.role === message.role) {
610 | message.content += '\n' + next.content;
611 | next.merged = true;
612 | }
613 | } else {
614 | message.content += '\n' + next.content;
615 | next.merged = true;
616 | }
617 | }
618 | }));
619 | const lastAssistant = realLogs.findLast((message => !message.merged && 'assistant' === message.role));
620 | lastAssistant && Config.Settings.StripAssistant && (lastAssistant.strip = true);
621 | const lastUser = realLogs.findLast((message => !message.merged && 'user' === message.role));
622 | lastUser && Config.Settings.StripHuman && (lastUser.strip = true);
623 | const systemMessages = messagesClone.filter((message => 'system' === message.role && !('name' in message)));
624 | systemMessages.forEach(((message, idx) => {
625 | const scenario = message.content.match(rgxScenario)?.[1], personality = message.content.match(rgxPerson);
626 | if (scenario) {
627 | message.content = Config.ScenarioFormat.replace(/{{scenario}}/gim, scenario);
628 | message.scenario = true;
629 | }
630 | if (3 === personality?.length) {
631 | message.content = Config.PersonalityFormat.replace(/{{char}}/gim, personality[1]).replace(/{{personality}}/gim, personality[2]);
632 | message.personality = true;
633 | }
634 | message.main = 0 === idx;
635 | message.jailbreak = idx === systemMessages.length - 1;
636 | ' ' === message.content && (message.discard = true);
637 | }));
638 | Config.Settings.AllSamples && !Config.Settings.NoSamples && realLogs.forEach((message => {
639 | if (![ lastUser, lastAssistant ].includes(message)) {
640 | if ('user' === message.role) {
641 | message.name = message.customname ? message.name : 'example_user';
642 | message.role = 'system';
643 | } else if ('assistant' === message.role) {
644 | message.name = message.customname ? message.name : 'example_assistant';
645 | message.role = 'system';
646 | } else if (!message.customname) {
647 | throw Error('Invalid role ' + message.name);
648 | }
649 | }
650 | }));
651 | Config.Settings.NoSamples && !Config.Settings.AllSamples && sampleLogs.forEach((message => {
652 | if ('example_user' === message.name) {
653 | message.role = 'user';
654 | } else if ('example_assistant' === message.name) {
655 | message.role = 'assistant';
656 | } else if (!message.customname) {
657 | throw Error('Invalid role ' + message.name);
658 | }
659 | message.customname || delete message.name;
660 | }));
661 | let systems = [];
662 | if (![ 'r', 'R', 'api' ].includes(type)) {
663 | lastUser.strip = true;
664 | systemMessages.forEach((message => message.discard = message.discard || 'c-c' === type ? !message.jailbreak : !message.jailbreak && !message.main));
665 | systems = systemMessages.filter((message => !message.discard)).map((message => `"${message.content.substring(0, 25).replace(/\n/g, '\\n').trim()}..."`));
666 | messagesClone.forEach((message => message.discard = message.discard || mergedLogs.includes(message) && ![ lastUser ].includes(message)));
667 | }
668 | const prompt = messagesClone.map(((message, idx) => {
669 | if (message.merged || message.discard) {
670 | return '';
671 | }
672 | if (message.content.length < 1) {
673 | return message.content;
674 | }
675 | let spacing = '';
676 | /******************************** */
677 | if (Config.Settings.xmlPlot) {
678 | idx > 0 && (spacing = '\n\n');
679 | const prefix = message.customname ? message.role + ': ' + message.name.replaceAll('_', ' ') + ': ' : 'system' !== message.role || message.name ? Replacements[message.name || message.role] + ': ' : 'xmlPlot: ' + Replacements[message.role];
680 | return `${spacing}${message.strip ? '' : prefix}${message.content}`;
681 | } else {
682 | /******************************** */
683 | idx > 0 && (spacing = systemMessages.includes(message) ? '\n' : '\n\n');
684 | const prefix = message.customname ? message.name.replaceAll('_', ' ') + ': ' : 'system' !== message.role || message.name ? Replacements[message.name || message.role] + ': ' : '' + Replacements[message.role];
685 | return `${spacing}${message.strip ? '' : prefix}${'system' === message.role ? message.content : message.content.trim()}`;
686 | } //
687 | }));
688 | return {
689 | prompt: prompt.join(''), //genericFixes(prompt.join('')).trim(),
690 | systems
691 | };
692 | })(messages, type);
693 | /******************************** */
694 | const legacy = /claude-([12]|instant)/i.test(model), messagesAPI = thirdKey || !legacy && !/<\|completeAPI\|>/.test(prompt) || /<\|messagesAPI\|>/.test(prompt), messagesLog = /<\|messagesLog\|>/.test(prompt), fusion = apiKey && messagesAPI && /<\|Fusion Mode\|>/.test(prompt), wedge = '\r';
695 | const stopSet = /<\|stopSet *(\[.*?\]) *\|>/.exec(prompt)?.[1], stopRevoke = /<\|stopRevoke *(\[.*?\]) *\|>/.exec(prompt)?.[1];
696 | if (stop_sequences || stopSet || stopRevoke) stop_sequences = JSON.parse(stopSet || '[]').concat(stop_sequences).concat(['\n\nHuman:', '\n\nAssistant:']).filter(item => !JSON.parse(stopRevoke || '[]').includes(item) && item);
697 | apiKey && (type = oaiAPI ? 'oai_api' : messagesAPI ? 'msg_api' : type);
698 | prompt = Config.Settings.xmlPlot ? xmlPlot(prompt, legacy && !/claude-2\.1/i.test(model)) : apiKey ? `\n\nHuman: ${genericFixes(prompt)}\n\nAssistant:` : genericFixes(prompt).trim();
699 | Config.Settings.FullColon && (prompt = !legacy ?
700 | prompt.replace(fusion ? /\n(?!\nAssistant:\s*$)(?=\n(Human|Assistant):)/gs : apiKey ? /(? 0 ? ' ' + systems.join(' [33m/[0m ') : ''}`);
705 | 'R' !== type || prompt || (prompt = '...regen...');
706 | Logger?.write(`\n\n-------\n[${(new Date).toLocaleString()}]\n${Main}\n####### ${model} (${type})\n${JSON.stringify({FusionMode: fusion, PassParams: Config.Settings.PassParams, stop_sequences, temperature, top_k, top_p}, null, 2)}\n\n####### regex:\n${regexLog}\n####### PROMPT ${tokens}t:\n${prompt}\n--\n####### REPLY:\n`); //Logger?.write(`\n\n-------\n[${(new Date).toLocaleString()}]\n####### MODEL: ${model}\n####### PROMPT (${type}):\n${prompt}\n--\n####### REPLY:\n`);
707 | retryRegen || (fetchAPI = await (async (signal, model, prompt, temperature, type) => {
708 | /******************************** */
709 | if (apiKey) {
710 | let messages, system, key = apiKey[Math.floor(Math.random() * apiKey.length)];
711 | if (messagesAPI) {
712 | const rounds = prompt.replace(/^(?!.*\n\nHuman:)/s, '\n\nHuman:').split('\n\nHuman:');
713 | messages = rounds.slice(1).flatMap(round => {
714 | const turns = round.split('\n\nAssistant:');
715 | return [{role: 'user', content: turns[0].trim()}].concat(turns.slice(1).flatMap(turn => [{role: 'assistant', content: turn.trim()}]));
716 | }).reduce((acc, current) => {
717 | if (Config.Settings.FullColon && acc.length > 0 && (acc[acc.length - 1].role === current.role || !acc[acc.length - 1].content)) {
718 | acc[acc.length - 1].content += (current.role === 'user' ? 'Human' : 'Assistant').replace(/.*/, legacy ? '\n$&﹕ ' : '\n' + wedge + '\n$&: ') + current.content;
719 | } else acc.push(current);
720 | return acc;
721 | }, []).filter(message => message.content), oaiAPI ? messages.unshift({role: 'system', content: rounds[0].trim()}) : system = rounds[0].trim();
722 | messagesLog && console.log({system, messages});
723 | }
724 | const res = await fetch((api_rProxy || 'https://api.anthropic.com').replace(/(\/v1)? *$/, thirdKey ? '$1' : '/v1').trim('/') + (oaiAPI ? '/chat/completions' : messagesAPI ? '/messages' : '/complete'), {
725 | method: 'POST',
726 | signal,
727 | headers: {
728 | 'anthropic-version': '2023-06-01',
729 | 'authorization': 'Bearer ' + key,
730 | 'Content-Type': 'application/json',
731 | 'User-Agent': AI.agent(),
732 | 'x-api-key': key,
733 | },
734 | body: JSON.stringify({
735 | ...oaiAPI || messagesAPI ? {
736 | max_tokens : max_tokens_to_sample,
737 | messages,
738 | system
739 | } : {
740 | max_tokens_to_sample,
741 | prompt
742 | },
743 | model,
744 | stop_sequences,
745 | stream: true,
746 | temperature,
747 | top_k,
748 | top_p
749 | }),
750 | });
751 | await checkResErr(res);
752 | return res;
753 | }
754 | /******************************** */
755 | const attachments = [];
756 | if (Config.Settings.PromptExperiments) {
757 | let splitedprompt = prompt.split('\n\nPlainPrompt:'); //
758 | prompt = splitedprompt[0]; //
759 | attachments.push({
760 | extracted_content: prompt,
761 | file_name: 'paste.txt', //fileName(),
762 | file_type: 'txt', //'text/plain',
763 | file_size: Buffer.from(prompt).byteLength
764 | });
765 | prompt = 'r' === type ? Config.PromptExperimentFirst : Config.PromptExperimentNext;
766 | splitedprompt.length > 1 && (prompt += splitedprompt[1]); //
767 | }
768 | let res;
769 | const body = {
770 | attachments,
771 | files: [],
772 | model: isPro || forceModel ? model : undefined,
773 | rendering_mode: 'raw',
774 | ...Config.Settings.PassParams && {
775 | max_tokens_to_sample, //
776 | //stop_sequences, //
777 | top_k, //
778 | top_p, //
779 | temperature
780 | },
781 | prompt: prompt || '',
782 | timezone: AI.zone()
783 | };
784 | let headers = {
785 | ...AI.hdr(Conversation.uuid || ''),
786 | Accept: 'text/event-stream',
787 | Cookie: getCookies()
788 | };
789 | res = await (Config.Settings.Superfetch ? Superfetch : fetch)(`${Config.rProxy || AI.end()}/api/organizations/${uuidOrg || ''}/chat_conversations/${Conversation.uuid || ''}/completion`, {
790 | stream: true,
791 | signal,
792 | method: 'POST',
793 | body: JSON.stringify(body),
794 | headers
795 | });
796 | updateParams(res);
797 | await checkResErr(res);
798 | return res;
799 | })(signal, model, prompt, temperature, type));
800 | const response = Writable.toWeb(res);
801 | clewdStream = new ClewdStream({
802 | config: {
803 | ...Config,
804 | Settings: {
805 | ...Config.Settings,
806 | Superfetch: apiKey ? false : Config.Settings.Superfetch
807 | }
808 | }, //config: Config,
809 | version: Main,
810 | minSize: Config.BufferSize,
811 | model,
812 | streaming: true === body.stream,
813 | abortControl,
814 | source: fetchAPI
815 | }, Logger);
816 | titleTimer = setInterval((() => setTitle('recv ' + bytesToSize(clewdStream.size))), 300);
817 | (!apiKey && Config.Settings.Superfetch) ? await Readable.toWeb(fetchAPI.body).pipeThrough(clewdStream).pipeTo(response) : await fetchAPI.body.pipeThrough(clewdStream).pipeTo(response); //Config.Settings.Superfetch ? await Readable.toWeb(fetchAPI.body).pipeThrough(clewdStream).pipeTo(response) : await fetchAPI.body.pipeThrough(clewdStream).pipeTo(response);
818 | } catch (err) {
819 | if ('AbortError' === err.name) {
820 | res.end();
821 | } else {
822 | nochange = true, exceeded_limit = err.exceeded_limit; //
823 | err.planned ? console.log(`[33m${err.status || 'Aborted'}![0m\n`) : console.error('[33mClewd:[0m\n%o', err); //err.planned || console.error('[33mClewd:[0m\n%o', err);
824 | res.json({
825 | error: {
826 | message: 'clewd: ' + (err.message || err.name || err.type),
827 | type: err.type || err.name || err.code,
828 | param: null,
829 | code: err.code || 500
830 | }
831 | }, 500);
832 | }
833 | }
834 | clearInterval(titleTimer);
835 | if (clewdStream) {
836 | clewdStream.censored && console.warn('[33mlikely your account is hard-censored[0m');
837 | prevImpersonated = clewdStream.impersonated;
838 | exceeded_limit = clewdStream.error.exceeded_limit; //
839 | clewdStream.error.status < 200 || clewdStream.error.status >= 300 || clewdStream.error.message === 'Overloaded' && (nochange = true); //
840 | setTitle('ok ' + bytesToSize(clewdStream.size));
841 | if (clewdStream.compModel && !(AI.mdl().includes(clewdStream.compModel) || Config.unknownModels.includes(clewdStream.compModel)) && !apiKey) {
842 | Config.unknownModels.push(clewdStream.compModel);
843 | writeSettings(Config);
844 | }
845 | console.log(`${200 == fetchAPI.status ? '[32m' : '[33m'}${fetchAPI.status}![0m\n`);
846 | clewdStream.empty();
847 | }
848 | const shouldChange = exceeded_limit || !nochange && Config.Cookiecounter > 0 && changeflag++ >= Config.Cookiecounter - 1; //
849 | if (!apiKey && (shouldChange || prevImpersonated)) { //if (prevImpersonated) {
850 | try {
851 | await deleteChat(Conversation.uuid);
852 | } catch (err) {}
853 | /******************************** */
854 | if (shouldChange) {
855 | exceeded_limit && console.log(`[35mExceeded limit![0m\n`);
856 | changeflag = 0;
857 | CookieChanger();
858 | }
859 | /******************************** */
860 | }
861 | }));
862 | })(req, res);
863 | break;
864 |
865 | case '/v1/complete':
866 | res.json({
867 | error: {
868 | message: 'clewd: Set "Chat Completion source" to OpenAI instead of Claude. Enable "External" models aswell',
869 | code: 404
870 | }
871 | }, 404);
872 | break;
873 |
874 | default:
875 | !['/', '/v1', '/favicon.ico'].includes(req.url) && (console.log('unknown request: ' + req.url)); //console.log('unknown request: ' + req.url);
876 | res.writeHead(200, {'Content-Type': 'text/html'}); //
877 | res.write(`\n\n\n\n\n\n\n${Main}
完全开源、免费且禁止商用
点击复制反向代理: Copy Link
填入OpenAI API反向代理并选择OpenAI分类中的claude模型(酒馆需打开Show "External" models,仅在api模式有模型选择差异)
教程与FAQ: Rentry | Discord
❗警惕任何高风险cookie/伪api(25k cookie)购买服务,以及破坏中文AI开源共享环境倒卖免费资源抹去署名的群组(🈲黑名单:酒馆小二、AI新服务、浅睡(鲑鱼)、赛博女友制作人(青麈/overloaded/科普晓百生)🈲)\n\n`); //
878 | res.end(); //res.json(// {// error: {// message: '404 Not Found',// type: 404,// param: null,// code: 404// }//}, 404);
879 | }
880 | }));
881 |
882 | !async function() {
883 | await (async () => {
884 | if (exists(ConfigPath)) {
885 | const userConfig = require(ConfigPath), validConfigs = Object.keys(Config), parsedConfigs = Object.keys(userConfig), parsedSettings = Object.keys(userConfig.Settings), invalidConfigs = parsedConfigs.filter((config => !validConfigs.includes(config))), validSettings = Object.keys(Config.Settings);
886 | UnknownSettings = parsedSettings.filter((setting => !validSettings.includes(setting)));
887 | invalidConfigs.forEach((config => {
888 | console.warn(`unknown config in config.js: [33m${config}[0m`);
889 | }));
890 | UnknownSettings.forEach((setting => {
891 | console.warn(`unknown setting in config.js: [33mSettings.${setting}[0m`);
892 | }));
893 | const missingConfigs = validConfigs.filter((config => !parsedConfigs.includes(config))), missingSettings = validSettings.filter((config => !parsedSettings.includes(config)));
894 | missingConfigs.forEach((config => {
895 | console.warn(`adding missing config in config.js: [33m${config}[0m`);
896 | userConfig[config] = Config[config];
897 | }));
898 | missingSettings.forEach((setting => {
899 | console.warn(`adding missing setting in config.js: [33mSettings.${setting}[0m`);
900 | userConfig.Settings[setting] = Config.Settings[setting];
901 | }));
902 | ChangedSettings = parsedSettings.filter((setting => Config.Settings[setting] !== userConfig.Settings[setting]));
903 | (missingConfigs.length > 0 || missingSettings.length > 0) && await writeSettings(userConfig);
904 | userConfig.Settings.LogMessages && (Logger = createWriteStream(LogPath));
905 | Config = {
906 | ...Config,
907 | ...userConfig
908 | };
909 | } else {
910 | Config.Cookie = 'SET YOUR COOKIE HERE';
911 | writeSettings(Config, true);
912 | }
913 | })();
914 | /***************************** */
915 | for (let key in Config) {
916 | if (key === 'Settings') {
917 | for (let setting in Config.Settings) {
918 | Config.Settings[setting] = process.env[setting] ? convertToType(process.env[setting]) : Config.Settings[setting];
919 | }
920 | } else {
921 | Config[key] = process.env[key] ? convertToType(process.env[key]) : Config[key];
922 | }
923 | }
924 | Config.rProxy = Config.rProxy.replace(/\/$/, '');
925 | Config.CookieArray = [...new Set([Config.CookieArray].join(',').match(/(claude[-_][a-z0-9-_]*?@)?(sessionKey=)?sk-ant-sid01-[\w-]{86}-[\w-]{6}AA/g))];
926 | Config.unknownModels = Config.unknownModels.reduce((prev, cur) => !cur || prev.includes(cur) || AI.mdl().includes(cur) ? prev : [...prev, cur], []);
927 | writeSettings(Config);
928 | currentIndex = Config.CookieIndex > 0 ? Config.CookieIndex - 1 : Config.Cookiecounter >= 0 ? Math.floor(Math.random() * Config.CookieArray.length) : 0;
929 | /***************************** */
930 | Proxy.listen(Config.Port, Config.Ip, onListen);
931 | Proxy.on('error', (err => {
932 | console.error('Proxy error\n%o', err);
933 | }));
934 | }();
935 |
936 | const cleanup = async () => {
937 | console.log('cleaning...');
938 | try {
939 | await deleteChat(Conversation.uuid);
940 | SuperfetchFoldersRm();
941 | Logger?.close();
942 | } catch (err) {}
943 | process.exit();
944 | };
945 |
946 | process.on('SIGHUP', cleanup);
947 |
948 | process.on('SIGTERM', cleanup);
949 |
950 | process.on('SIGINT', cleanup);
951 |
952 | process.on('exit', (async () => {
953 | console.log('exiting...');
954 | }));
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | app:
4 | build:
5 | context: .
6 | dockerfile: Dockerfile
7 |
8 | ports:
9 | - 8444:8444
10 | volumes:
11 | - .:/app
12 |
--------------------------------------------------------------------------------
/lib/bin/clewd-superfetch-linux-amd64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teralomaniac/clewd/1d90b063fe2454488e66edb7235f6ab41405fff6/lib/bin/clewd-superfetch-linux-amd64
--------------------------------------------------------------------------------
/lib/bin/clewd-superfetch-linux-arm64:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teralomaniac/clewd/1d90b063fe2454488e66edb7235f6ab41405fff6/lib/bin/clewd-superfetch-linux-arm64
--------------------------------------------------------------------------------
/lib/bin/clewd-superfetch-win-amd64.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teralomaniac/clewd/1d90b063fe2454488e66edb7235f6ab41405fff6/lib/bin/clewd-superfetch-win-amd64.exe
--------------------------------------------------------------------------------
/lib/bin/clewd-superfetch-win-ia32.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teralomaniac/clewd/1d90b063fe2454488e66edb7235f6ab41405fff6/lib/bin/clewd-superfetch-win-ia32.exe
--------------------------------------------------------------------------------
/lib/clewd-stream.js:
--------------------------------------------------------------------------------
1 | /*
2 | * https://rentry.org/teralomaniac_clewd
3 | * https://github.com/teralomaniac/clewd
4 | */
5 | 'use strict';
6 |
7 | const {AI, genericFixes, DangerChars, encodeDataJSON, indexOfAny, cleanJSON, checkResErr} = require('./clewd-utils'), Decoder = new TextDecoder;
8 |
9 | class ClewdStream extends TransformStream {
10 | constructor(opts, logger) {
11 | super({
12 | transform: (chunk, controller) => {
13 | this.#handle(chunk, controller);
14 | },
15 | flush: controller => {
16 | this.#done(controller);
17 | }
18 | });
19 | this.#logger = logger;
20 | this.#version = opts.version;
21 | this.#config = opts.config;
22 | this.#model = opts.model;
23 | this.#streaming = opts.streaming;
24 | this.#minSize = opts.minSize || 8;
25 | this.#abortControl = opts.abortControl;
26 | this.#source = opts.source;
27 | }
28 | #source=void 0;
29 | #ended=false;
30 | #streaming=void 0;
31 | #minSize=void 0;
32 | #compOK='';
33 | #compRaw='';
34 | #logger=void 0;
35 | #version=void 0;
36 | #config=void 0;
37 | #abortControl=void 0;
38 | #model=void 0;
39 | #compAll=[];
40 | #recvLength=0;
41 | #stopLoc=void 0;
42 | #stopReason=void 0;
43 | #hardCensor=false;
44 | #impersonated=false;
45 | #error={}; //
46 | #compModel=''; //
47 | get size() {
48 | return this.#recvLength;
49 | }
50 | get total() {
51 | return this.#compAll.length;
52 | }
53 | get censored() {
54 | return this.#hardCensor;
55 | }
56 | get impersonated() {
57 | return this.#impersonated;
58 | }
59 | /************************ */
60 | get error() {
61 | return this.#error;
62 | }
63 | get compModel() {
64 | return this.#compModel;
65 | }
66 | /************************ */
67 | empty() {
68 | this.#compOK = this.#compRaw = '';
69 | this.#compAll = [];
70 | this.#recvLength = 0;
71 | }
72 | #collectBuf() {
73 | const valid = [ ...this.#compOK ], selection = valid.splice(0, Math.min(this.#minSize, valid.length)).join('');
74 | this.#compOK = valid.join('');
75 | return selection;
76 | }
77 | #err(err, controller) {
78 | this.#logger?.write(JSON.stringify(err, null, 4));
79 | const message = `## ${this.#version}\n**${this.#model} error**:\n${err.status || err.code || err.type}\n\n\`\`\`${err.message}\`\`\`\n\nFAQ: https://rentry.org/teralomaniac_clewd`;
80 | this.#error = err; //
81 | this.#enqueue(this.#build(message), controller);
82 | return this.#endEarly(controller);
83 | }
84 | #build(selection) {
85 | this.#logger?.write(selection);
86 | const completion = this.#streaming ? {
87 | choices: [ {
88 | delta: {
89 | content: genericFixes(selection)
90 | }
91 | } ]
92 | } : {
93 | choices: [ {
94 | message: {
95 | content: genericFixes(selection)
96 | }
97 | } ]
98 | };
99 | return this.#streaming ? encodeDataJSON(completion) : JSON.stringify(completion);
100 | }
101 | #enqueue(selection, controller) {
102 | this.#ended || controller.enqueue(selection);
103 | }
104 | #print() {}
105 | async #done(controller) {
106 | this.#compRaw.length > 0 && await this.#parseBuf(this.#compRaw, controller);
107 | this.#streaming ? this.#compOK.length > 0 && this.#enqueue(this.#build(this.#compOK), controller) : this.#enqueue(this.#build(this.#compAll.join('')), controller);
108 | this.#compAll?.[0] === Buffer.from([ 73, 32, 97, 112, 111, 108, 111, 103, 105, 122, 101, 44, 32, 98, 117, 116, 32, 73, 32, 119, 105, 108, 108, 32, 110, 111, 116, 32, 112, 114, 111, 118, 105, 100, 101, 32, 97, 110, 121, 32, 114, 101, 115, 112, 111, 110, 115, 101, 115, 32, 116, 104, 97, 116, 32, 118, 105, 111, 108, 97, 116, 101, 32, 65, 110, 116, 104, 114, 111, 112, 105, 99, 39, 115, 32, 65, 99, 99, 101, 112, 116, 97, 98, 108, 101, 32, 85, 115, 101, 32, 80, 111, 108, 105, 99, 121, 32, 111, 114, 32, 99, 111, 117, 108, 100, 32, 112, 114, 111, 109, 111, 116, 101, 32, 104, 97, 114, 109, 46 ]).toString() && (this.#hardCensor = true);
109 | if (!this.#ended && 0 === this.total) {
110 | const err = `## ${this.#version}\n**error**:\n\n\`\`\`Received no valid replies at all\`\`\`\n\nFAQ: https://rentry.org/teralomaniac_clewd`;
111 | this.#enqueue(this.#build(err), controller);
112 | }
113 | this.#streaming && this.#enqueue('data: [DONE]\n\n', controller);
114 | this.#print();
115 | this.#ended = true;
116 | }
117 | #endEarly(controller) {
118 | if (!this.#ended) {
119 | this.#streaming && this.#enqueue('data: [DONE]\n\n', controller);
120 | this.#config.Settings.Superfetch && this.#source.rape();
121 | this.#abortControl.abort();
122 | controller.terminate();
123 | this.#print();
124 | this.#ended = true;
125 | }
126 | }
127 | #impersonationCheck(reply, controller) {
128 | const fakeAny = indexOfAny(reply);
129 | if (fakeAny > -1) {
130 | this.#impersonated = true;
131 | if (this.#config.Settings.PreventImperson) {
132 | const selection = reply.substring(0, fakeAny);
133 | this.#enqueue(this.#build(selection), controller);
134 | this.#endEarly(controller);
135 | }
136 | }
137 | }
138 | async #handle(chunk, controller) {
139 | if ('string' != typeof chunk) {
140 | this.#recvLength += chunk.byteLength;
141 | chunk = Decoder.decode(chunk, {'stream': true}); //chunk = Decoder.decode(chunk);
142 | } else {
143 | this.#recvLength += Buffer.byteLength(chunk);
144 | }
145 | this.#compRaw += chunk.replace(/event: [\w]+\s*|\r/gi,''); //this.#compRaw += chunk;
146 | const substr = this.#compRaw.split('\n\n'), lastMsg = substr.length - 1;
147 | 0 !== substr[lastMsg].length ? this.#compRaw = substr[lastMsg] : this.#compRaw = '';
148 | for (let i = 0; i < lastMsg; i++) {
149 | await this.#parseBuf(substr[i], controller);
150 | }
151 | }
152 | async #parseBuf(json, controller) {
153 | if (!json) {
154 | return;
155 | }
156 | if (this.#ended) {
157 | return;
158 | }
159 | let parsed, delayChunk;
160 | try {
161 | parsed = JSON.parse(cleanJSON(json));
162 | if (parsed.error) {
163 | const err = await checkResErr(JSON.stringify({
164 | error: {
165 | ...parsed.error
166 | },
167 | status: this.#source.status,
168 | superfetch: this.#source.superfetch
169 | }), false);
170 | delete err.stack;
171 | return this.#err(err, controller);
172 | }
173 | if (!this.#compModel && parsed.model) this.#compModel = parsed.model;
174 | if (parsed.completion || parsed.delta?.text || parsed.choices?.[0]?.delta?.content) { //if (parsed.completion) {
175 | parsed.completion = genericFixes(parsed.completion || parsed.delta?.text || parsed.choices?.[0]?.delta?.content); //parsed.completion = genericFixes(parsed.completion);
176 | this.#compOK += parsed.completion;
177 | this.#compAll.push(parsed.completion);
178 | delayChunk = DangerChars.some((char => this.#compOK.endsWith(char) || parsed.completion.startsWith(char)));
179 | }
180 | !this.#stopLoc && parsed.stop && (this.#stopLoc = parsed.stop.replace(/\n/g, '\\n'));
181 | !this.#stopReason && parsed.stop_reason && (this.#stopReason = parsed.stop_reason);
182 | if (this.#streaming) {
183 | delayChunk && this.#impersonationCheck(this.#compOK, controller);
184 | for (;!delayChunk && this.#compOK.length >= this.#minSize; ) {
185 | const selection = this.#collectBuf();
186 | this.#enqueue(this.#build(selection), controller);
187 | }
188 | } else {
189 | delayChunk && this.#impersonationCheck(this.#compAll.join(''), controller);
190 | }
191 | } catch (err) {}
192 | }
193 | }
194 |
195 | module.exports = ClewdStream;
--------------------------------------------------------------------------------
/lib/clewd-superfetch.js:
--------------------------------------------------------------------------------
1 | /*
2 | * https://gitgud.io/ahsk/clewd
3 | * https://github.com/h-a-s-k/clewd
4 | */"use strict";const{spawn:e}=require("node:child_process"),{randomInt:r}=require("node:crypto"),{relative:t,resolve:s,join:n,normalize:o,basename:i,dirname:a}=require("node:path"),{writeFileSync:d,unlinkSync:c,existsSync:u,mkdirSync:m,rmdirSync:l}=require("node:fs"),{ReadableStream:f}=require("node:stream/web"),p=e=>"win32"===process.platform?".\\"+e:e,h=e=>"win32"===process.platform||e.indexOf(" ")>-1?`"${e}"`:e,w={win32:{x64:"clewd-superfetch-win-amd64.exe",ia32:"clewd-superfetch-win-ia32.exe"},darwin:{x64:"clewd-superfetch-linux-amd64",arm64:"clewd-superfetch-linux-arm64"},linux:{x64:"clewd-superfetch-linux-amd64",arm64:"clewd-superfetch-linux-arm64"},android:{x64:"clewd-superfetch-linux-amd64",arm64:"clewd-superfetch-linux-arm64"}}[process.platform]?.[process.arch],y=""+o(t("./","./bin/"+w)),b=o(s(__dirname,y,"../","../")),S=s(b,y),x=a(S),_=""+o(s(x,"./cfg")),g=""+o(s(x,"./pyld")),v=""+o(s(x,"./hdr")),O=[123,34,115,101,99,45,99,104,45,117,97,34,58,34,92,34,78,111,116,95,65,32,66,114,97,110,100,92,34,59,118,61,92,34,56,92,34,44,32,92,34,67,104,114,111,109,105,117,109,92,34,59,118,61,92,34,49,50,48,92,34,44,32,92,34,71,111,111,103,108,101,32,67,104,114,111,109,101,92,34,59,118,61,92,34,49,50,48,92,34,34,44,34,115,101,99,45,99,104,45,117,97,45,109,111,98,105,108,101,34,58,34,63,48,34,44,34,115,101,99,45,99,104,45,117,97,45,112,108,97,116,102,111,114,109,34,58,34,92,34,109,97,99,79,83,92,34,34,44,34,85,112,103,114,97,100,101,45,73,110,115,101,99,117,114,101,45,82,101,113,117,101,115,116,115,34,58,49,44,34,85,115,101,114,45,65,103,101,110,116,34,58,34,77,111,122,105,108,108,97,47,53,46,48,32,40,77,97,99,105,110,116,111,115,104,59,32,73,110,116,101,108,32,77,97,99,32,79,83,32,88,32,49,48,95,49,53,95,55,41,32,65,112,112,108,101,87,101,98,75,105,116,47,53,51,55,46,51,54,32,40,75,72,84,77,76,44,32,108,105,107,101,32,71,101,99,107,111,41,32,67,104,114,111,109,101,47,49,50,48,46,48,46,48,46,48,32,83,97,102,97,114,105,47,53,51,55,46,51,54,34,44,34,65,99,99,101,112,116,34,58,34,116,101,120,116,47,104,116,109,108,44,97,112,112,108,105,99,97,116,105,111,110,47,120,104,116,109,108,43,120,109,108,44,97,112,112,108,105,99,97,116,105,111,110,47,120,109,108,59,113,61,48,46,57,44,105,109,97,103,101,47,97,118,105,102,44,105,109,97,103,101,47,119,101,98,112,44,105,109,97,103,101,47,97,112,110,103,44,42,47,42,59,113,61,48,46,56,44,97,112,112,108,105,99,97,116,105,111,110,47,115,105,103,110,101,100,45,101,120,99,104,97,110,103,101,59,118,61,98,51,59,113,61,48,46,55,34,44,34,83,101,99,45,70,101,116,99,104,45,83,105,116,101,34,58,34,110,111,110,101,34,44,34,83,101,99,45,70,101,116,99,104,45,77,111,100,101,34,58,34,110,97,118,105,103,97,116,101,34,44,34,83,101,99,45,70,101,116,99,104,45,85,115,101,114,34,58,34,63,49,34,44,34,83,101,99,45,70,101,116,99,104,45,68,101,115,116,34,58,34,100,111,99,117,109,101,110,116,34,44,34,65,99,99,101,112,116,45,69,110,99,111,100,105,110,103,34,58,34,103,122,105,112,44,32,100,101,102,108,97,116,101,44,32,98,114,34,44,34,65,99,99,101,112,116,45,76,97,110,103,117,97,103,101,34,58,34,101,110,45,85,83,44,101,110,59,113,61,48,46,57,34,125],j=[91,34,45,45,99,105,112,104,101,114,115,32,84,76,83,95,65,69,83,95,49,50,56,95,71,67,77,95,83,72,65,50,53,54,44,84,76,83,95,65,69,83,95,50,53,54,95,71,67,77,95,83,72,65,51,56,52,44,84,76,83,95,67,72,65,67,72,65,50,48,95,80,79,76,89,49,51,48,53,95,83,72,65,50,53,54,44,69,67,68,72,69,45,69,67,68,83,65,45,65,69,83,49,50,56,45,71,67,77,45,83,72,65,50,53,54,44,69,67,68,72,69,45,82,83,65,45,65,69,83,49,50,56,45,71,67,77,45,83,72,65,50,53,54,44,69,67,68,72,69,45,69,67,68,83,65,45,65,69,83,50,53,54,45,71,67,77,45,83,72,65,51,56,52,44,69,67,68,72,69,45,82,83,65,45,65,69,83,50,53,54,45,71,67,77,45,83,72,65,51,56,52,44,69,67,68,72,69,45,69,67,68,83,65,45,67,72,65,67,72,65,50,48,45,80,79,76,89,49,51,48,53,44,69,67,68,72,69,45,82,83,65,45,67,72,65,67,72,65,50,48,45,80,79,76,89,49,51,48,53,44,69,67,68,72,69,45,82,83,65,45,65,69,83,49,50,56,45,83,72,65,44,69,67,68,72,69,45,82,83,65,45,65,69,83,50,53,54,45,83,72,65,44,65,69,83,49,50,56,45,71,67,77,45,83,72,65,50,53,54,44,65,69,83,50,53,54,45,71,67,77,45,83,72,65,51,56,52,44,65,69,83,49,50,56,45,83,72,65,44,65,69,83,50,53,54,45,83,72,65,34,44,34,45,45,104,116,116,112,50,34,44,34,45,45,104,116,116,112,50,45,115,101,116,116,105,110,103,115,32,39,49,58,54,53,53,51,54,59,50,58,48,59,52,58,54,50,57,49,52,53,54,59,54,58,50,54,50,49,52,52,39,34,44,34,45,45,104,116,116,112,50,45,119,105,110,100,111,119,45,117,112,100,97,116,101,32,49,53,54,54,51,49,48,53,34,44,34,45,45,99,111,109,112,114,101,115,115,101,100,34,44,34,45,45,101,99,104,32,71,82,69,65,83,69,34,44,34,45,45,116,108,115,118,49,46,50,34,44,34,45,45,97,108,112,115,34,44,34,45,45,116,108,115,45,112,101,114,109,117,116,101,45,101,120,116,101,110,115,105,111,110,115,34,44,34,45,45,99,101,114,116,45,99,111,109,112,114,101,115,115,105,111,110,32,98,114,111,116,108,105,34,93],$=(e=false)=>{if(!w||!u(S)){e&&console.warn(`superfetch [[31merr[0m] unavailable for ${process.platform}-${process.arch}, use 3.8.5 for the time being\n`);return false}e&&console.log(`superfetch [[32mfound[0m] ${t(__dirname,S)}\n`);return true},q=(i,a)=>{a.headers??={};"string"!=typeof a.body&&(a.body=a.body?JSON.stringify(a.body):"");if(!$())return;const u=r(1,2e4).toString(),m=o(t(b,s(_,u))),l=o(t(b,s(g,u))),f=o(t(b,s(v,u))),w=o(t("./","bin/ca"));let x={...JSON.parse(Buffer.from(O).toString()),...a.headers};x=Object.entries(x).map((([e,r])=>`${e}: ${r}`));const q=h(p(m)),A=h(p(f)),L=h(p(l)),k=["-v","--http2","--cacert",""+h(p(w)),"--config",""+q,"--header","@"+A],J=[...JSON.parse(Buffer.from(j).toString()),"-X "+(a.method||"GET")];d(n(__dirname,m),J.join("\n"));d(n(__dirname,f),x.join("\n"));a.body&&d(n(__dirname,l),a.body);if("POST"===a.method||"PUT"===a.method||"PATCH"===a.method){k.push("--data");k.push("@"+L)}return new Promise((r=>{const t=e("android"===process.platform?S:y,[...k,""+i],{cwd:b,windowsHide:true,killSignal:"SIGKILL",windowsVerbatimArguments:true,detached:"win32"!==process.platform});t.superfetch=true;t.rape=function(){this.stdout?.end();this.stderr?.end()}.bind(t);t.once("spawn",(()=>{t.stream=a.stream||false;t.id=u;if(t.stream){Object.defineProperty(t,"body",{get:()=>t.stdout});return r(t)}t.body="";t.stdout.on("data",(e=>t.body+=e.toString()));t.json=async()=>JSON.parse(t.body);t.text=async()=>t.body;t.stdout.once("end",(()=>r(t)))}));t.once("error",(e=>{console.warn("superfetch [[31merr[0m]",e)}));t.once("close",(()=>{(e=>{try{c(n(__dirname,m));c(n(__dirname,f));a.body&&c(n(__dirname,l))}catch(e){}e.stdout.removeAllListeners();e.stderr.removeAllListeners();e.stream&&e.body.removeAllListeners()})(t)}));t.stderr.on("data",(e=>{const r=/HTTP\/2 (\d{3})+/g,s=(e=e.toString().trim()).match(r);if(!t.status&&s){const s=r.exec(e);t.status=+s[1]}const n=/(?:< )(.+?)(?:: )(.+)/g,o=e.match(n);if(o){const e={};o.forEach((r=>{const t=r.split(n);e[t?.[1]]=t?.[2]}));t.headers=e}}))}))};module.exports={SuperfetchFoldersMk:()=>{m(_,{recursive:true});m(g,{recursive:true});m(v,{recursive:true})},SuperfetchFoldersRm:()=>{l(_,{recursive:true});l(g,{recursive:true});l(v,{recursive:true})},ClewdSuperfetch:q,SuperfetchAvailable:$,SuperfetchRelative:y};
--------------------------------------------------------------------------------
/lib/clewd-utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | * https://rentry.org/teralomaniac_clewd
3 | * https://github.com/teralomaniac/clewd
4 | */
5 | 'use strict';
6 |
7 | const {randomInt, randomBytes} = require('node:crypto'), {version: Version} = require('../package.json'), Encoder = (new TextDecoder,
8 | new TextEncoder), Main = 'clewd修改版 v' + Version + '(11) by tera', Replacements = {
9 | user: 'Human',
10 | assistant: 'Assistant',
11 | system: '',
12 | example_user: 'H',
13 | example_assistant: 'A'
14 | }, DangerChars = [ ...new Set([ ...Object.values(Replacements).join(''), ...'\n', ...':', ...'\\n' ]) ].filter((char => ' ' !== char)).sort(), AI = {
15 | end: () => Buffer.from([ 104, 116, 116, 112, 115, 58, 47, 47, 97, 112, 105, 46, 99, 108, 97, 117, 100, 101, 46, 97, 105 ]).toString(),
16 | mdl: () => JSON.parse(Buffer.from([ 91, 34, 99, 108, 97, 117, 100, 101, 45, 51, 45, 53, 45, 115, 111, 110, 110, 101, 116, 45, 50, 48, 50, 52, 48, 54, 50, 48, 34, 44, 34, 99, 108, 97, 117, 100, 101, 45, 51, 45, 111, 112, 117, 115, 45, 50, 48, 50, 52, 48, 50, 50, 57, 34, 44, 34, 99, 108, 97, 117, 100, 101, 45, 51, 45, 115, 111, 110, 110, 101, 116, 45, 50, 48, 50, 52, 48, 50, 50, 57, 34, 44, 34, 99, 108, 97, 117, 100, 101, 45, 51, 45, 104, 97, 105, 107, 117, 45, 50, 48, 50, 52, 48, 51, 48, 55, 34, 44, 34, 99, 108, 97, 117, 100, 101, 45, 50, 46, 49, 34, 44, 34, 99, 108, 97, 117, 100, 101, 45, 50, 46, 48, 34, 44, 34, 99, 108, 97, 117, 100, 101, 45, 49, 46, 51, 34, 44, 34, 99, 108, 97, 117, 100, 101, 45, 105, 110, 115, 116, 97, 110, 116, 45, 49, 46, 50, 34, 44, 34, 99, 108, 97, 117, 100, 101, 45, 105, 110, 115, 116, 97, 110, 116, 45, 49, 46, 49, 34, 93 ]).toString()),
17 | zone: () => Buffer.from([ 65, 109, 101, 114, 105, 99, 97, 47, 78, 101, 119, 95, 89, 111, 114, 107 ]).toString(),
18 | agent: () => Buffer.from([ 77, 111, 122, 105, 108, 108, 97, 47, 53, 46, 48, 32, 40, 87, 105, 110, 100, 111, 119, 115, 32, 78, 84, 32, 49, 48, 46, 48, 59, 32, 87, 105, 110, 54, 52, 59, 32, 120, 54, 52, 41, 32, 65, 112, 112, 108, 101, 87, 101, 98, 75, 105, 116, 47, 53, 51, 55, 46, 51, 54, 32, 40, 75, 72, 84, 77, 76, 44, 32, 108, 105, 107, 101, 32, 71, 101, 99, 107, 111, 41, 32, 67, 104, 114, 111, 109, 101, 47, 49, 49, 54, 46, 48, 46, 48, 46, 48, 32, 83, 97, 102, 97, 114, 105, 47, 53, 51, 55, 46, 51, 54 ]).toString(),
19 | cp: () => Buffer.from([ 55, 55, 49, 44, 52, 56, 54, 53, 45, 52, 56, 54, 54, 45, 52, 56, 54, 55, 45, 52, 57, 49, 57, 53, 45, 52, 57, 49, 57, 57, 45, 52, 57, 49, 57, 54, 45, 52, 57, 50, 48, 48, 45, 53, 50, 51, 57, 51, 45, 53, 50, 51, 57, 50, 45, 52, 57, 49, 55, 49, 45, 52, 57, 49, 55, 50, 45, 49, 53, 54, 45, 49, 53, 55, 45, 52, 55, 45, 53, 51, 44, 48, 45, 50, 51, 45, 54, 53, 50, 56, 49, 45, 49, 48, 45, 49, 49, 45, 51, 53, 45, 49, 54, 45, 53, 45, 49, 51, 45, 49, 56, 45, 53, 49, 45, 52, 53, 45, 52, 51, 45, 50, 55, 45, 49, 55, 53, 49, 51, 45, 50, 49, 44, 50, 57, 45, 50, 51, 45, 50, 52, 44, 48 ]).toString(),
20 | extra: () => JSON.parse(Buffer.from([ 123, 34, 115, 101, 99, 45, 99, 104, 45, 117, 97, 34, 58, 34, 92, 34, 67, 104, 114, 111, 109, 105, 117, 109, 92, 34, 59, 118, 61, 92, 34, 49, 49, 54, 92, 34, 44, 32, 92, 34, 78, 111, 116, 59, 65, 61, 66, 114, 97, 110, 100, 92, 34, 59, 118, 61, 92, 34, 50, 52, 92, 34, 44, 32, 92, 34, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 69, 100, 103, 101, 92, 34, 59, 118, 61, 92, 34, 49, 49, 54, 92, 34, 34, 44, 34, 115, 101, 99, 45, 99, 104, 45, 117, 97, 45, 109, 111, 98, 105, 108, 101, 34, 58, 34, 63, 48, 34, 44, 34, 115, 101, 99, 45, 99, 104, 45, 117, 97, 45, 112, 108, 97, 116, 102, 111, 114, 109, 34, 58, 34, 92, 34, 87, 105, 110, 100, 111, 119, 115, 92, 34, 34, 44, 34, 115, 101, 99, 45, 102, 101, 116, 99, 104, 45, 100, 101, 115, 116, 34, 58, 34, 101, 109, 112, 116, 121, 34, 44, 34, 115, 101, 99, 45, 102, 101, 116, 99, 104, 45, 109, 111, 100, 101, 34, 58, 34, 110, 97, 118, 105, 103, 97, 116, 101, 34, 44, 34, 115, 101, 99, 45, 102, 101, 116, 99, 104, 45, 115, 105, 116, 101, 34, 58, 34, 110, 111, 110, 101, 34, 44, 34, 115, 101, 99, 45, 102, 101, 116, 99, 104, 45, 117, 115, 101, 114, 34, 58, 34, 63, 49, 34, 44, 34, 117, 112, 103, 114, 97, 100, 101, 45, 105, 110, 115, 101, 99, 117, 114, 101, 45, 114, 101, 113, 117, 101, 115, 116, 115, 34, 58, 49, 125 ]).toString()),
21 | hdr: refPath => ({
22 | ...AI.extra(),
23 | 'Content-Type': 'application/json',
24 | 'User-Agent': AI.agent(),
25 | Referer: `${AI.end()}/${refPath ? 'chat/' + refPath : ''}`,
26 | Origin: '' + AI.end()
27 | })
28 | }, indexOfH = (text, last = false) => {
29 | let location = -1;
30 | const matchesH = text.match(/(?:(?:\\n)|\r|\n){2}((?:Human|H)[:︓:﹕] ?)/gm); //const matchesH = text.match(/(?:(?:\\n)|\n){2}((?:Human|H): ?)/gm);
31 | matchesH?.length > 0 && (location = last ? text.lastIndexOf(matchesH[matchesH.length - 1]) : text.indexOf(matchesH[0]));
32 | return location;
33 | }, indexOfA = (text, last = false) => {
34 | let location = -1;
35 | const matchesA = text.match(/(?:(?:\\n)|\r|\n){2}((?:Assistant|A)[:︓:﹕] ?)/gm); //const matchesA = text.match(/(?:(?:\\n)|\n){2}((?:Assistant|A): ?)/gm);
36 | matchesA?.length > 0 && (location = last ? text.lastIndexOf(matchesA[matchesA.length - 1]) : text.indexOf(matchesA[0]));
37 | return location;
38 | };
39 |
40 | module.exports = {
41 | Main,
42 | AI,
43 | Replacements,
44 | DangerChars,
45 | encodeDataJSON: completion => Encoder.encode(`data: ${JSON.stringify(completion)}\n\n`),
46 | genericFixes: text => text.replace(/(\r\n|\r|\\n)/gm, '\n'),
47 | checkResErr: async (res, throwIt = true) => {
48 | let err, json, errAPI;
49 | if ('string' == typeof res) {
50 | json = JSON.parse(res);
51 | errAPI = json.error;
52 | err = Error(errAPI.message);
53 | } else if (res.status < 200 || res.status >= 300) {
54 | err = Error('Unexpected response code: ' + (res.status || json.status));
55 | const text = await res.text();
56 | try { //
57 | json = JSON.parse(text);
58 | errAPI = json.error;
59 | } catch { //
60 | console.log(text); //
61 | throw err; //
62 | } //
63 | }
64 | if (errAPI) {
65 | err.status = res.status || json.status;
66 | err.planned = true;
67 | errAPI.message && (err.message = errAPI.message);
68 | errAPI.type && (err.type = errAPI.type);
69 | if (429 === res.status || 429 === json.status) { //if ((429 === res.status || 429 === json.status) && errAPI.resets_at) {
70 | try { //
71 | const hours = ((new Date(1e3 * JSON.parse(errAPI.message).resetsAt).getTime() - Date.now()) / 1e3 / 60 / 60).toFixed(1); //const hours = ((new Date(1e3 * errAPI.resets_at).getTime() - Date.now()) / 1e3 / 60 / 60).toFixed(1);
72 | err.message += `, expires in ${hours} hours`;
73 | err.exceeded_limit = true; //
74 | } catch {} //
75 | }
76 | if (throwIt) {
77 | throw err;
78 | }
79 | }
80 | return err;
81 | },
82 | bytesToSize: (bytes = 0, decimals = 2) => {
83 | if (0 === bytes) {
84 | return '0 B';
85 | }
86 | const dm = decimals < 0 ? 0 : decimals, i = Math.round(Math.log(bytes) / Math.log(1024));
87 | return `${(bytes / Math.pow(1024, i)).toFixed(dm)} ${[ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ][i]}`;
88 | },
89 | indexOfAny: (text, last = false) => {
90 | let location = -1;
91 | const fakes = [ indexOfH(text, last), indexOfA(text, last) ].filter((idx => idx > -1)).sort();
92 | location = last ? fakes.reverse()[0] : fakes[0];
93 | return isNaN(location) ? -1 : location;
94 | },
95 | cleanJSON: json => json.indexOf('data:') > -1 ? json.split('data: ')?.[1] : json,
96 | fileName: () => {
97 | const len = randomInt(5, 15);
98 | let name = randomBytes(len).toString('hex');
99 | for (let i = 0; i < name.length; i++) {
100 | const char = name.charAt(i);
101 | isNaN(char) && randomInt(1, 5) % 2 == 0 && ' ' !== name.charAt(i - 1) && (name = name.slice(0, i) + ' ' + name.slice(i));
102 | }
103 | return name + '.txt';
104 | },
105 | indexOfA,
106 | indexOfH,
107 | setTitle: title => {
108 | title = `${Main} - ${title}`;
109 | process.title !== title && (process.title = title);
110 | }
111 | };
--------------------------------------------------------------------------------
/media/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teralomaniac/clewd/1d90b063fe2454488e66edb7235f6ab41405fff6/media/logo.png
--------------------------------------------------------------------------------
/media/program.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/teralomaniac/clewd/1d90b063fe2454488e66edb7235f6ab41405fff6/media/program.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clewd",
3 | "version": "4.8",
4 | "description": " Clewd
",
5 | "main": "clewd.js",
6 | "engines": {
7 | "node": ">=20.4.0"
8 | },
9 | "scripts": {
10 | "run": "node clewd.js"
11 | },
12 | "keywords": [
13 | "doom",
14 | "coom"
15 | ],
16 | "author": {
17 | "name": "ahsk",
18 | "url": "https://gitgud.io/ahsk"
19 | },
20 | "contributors": [
21 | {
22 | "name": "ahsk",
23 | "url": "https://gitgud.io/ahsk"
24 | },
25 | {
26 | "name": "h-a-s-k",
27 | "url": "https://github.com/h-a-s-k"
28 | }
29 | ],
30 | "homepage": "https://rentry.org/teralomaniac_clewd",
31 | "repository": {
32 | "type": "git",
33 | "url": "git+https://github.com/teralomaniac/clewd.git"
34 | },
35 | "bugs": {
36 | "url": "https://gitgud.io/ahsk/clewd/-/issues"
37 | },
38 | "dependencies": {
39 | "localtunnel": "^2.0.2",
40 | "@anthropic-ai/tokenizer": "^0.0.4"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/start.bat:
--------------------------------------------------------------------------------
1 | pushd %~dp0
2 | call npm install --no-audit --fund false
3 | node clewd.js
4 | pause
5 | popd
--------------------------------------------------------------------------------
/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if ! command -v npm &> /dev/null
4 | then
5 | echo "Install nodejs"
6 | fi
7 |
8 | npm install --no-audit --fund false
9 | chown -R $(whoami) lib/bin/*
10 | chmod u+x lib/bin/*
11 | chmod -R 777 $(pwd)
12 | node clewd.js
--------------------------------------------------------------------------------
/update.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | pushd %~dp0
3 |
4 | if not exist .git (
5 | GOTO:notgit
6 | )
7 |
8 | where /q git.exe
9 | if %ERRORLEVEL% EQU 0 (
10 | GOTO:pull
11 | )
12 | GOTO:missgit
13 |
14 |
15 | :pull
16 | call git config --local url."https://".insteadOf git://
17 | call git config --local url."https://github.com/".insteadOf git@github.com:
18 | call git config --local url."https://".insteadOf ssh://
19 | call git pull --rebase --autostash
20 | if %ERRORLEVEL% neq 0 (
21 | echo Error updating
22 | )
23 | GOTO:end
24 |
25 | :missgit
26 | echo Install git to update
27 | GOTO:end
28 |
29 | :notgit
30 | echo Only able to update if you clone the repository (git clone https://github.com/teralomaniac/clewd.git)
31 | GOTO:end
32 |
33 |
34 | :end
35 | pause
36 | popd
37 | exit /B
--------------------------------------------------------------------------------
/update.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | if ! [ -x "$(command -v git)" ]
4 | then
5 | echo "Install git to update"
6 | exit
7 | fi
8 |
9 | if [ -x "$(command -v git)" ]
10 | then
11 | if [ -d ".git" ]
12 | then
13 | git config --local url."https://".insteadOf git://
14 | git config --local url."https://github.com/".insteadOf git@github.com:
15 | git config --local url."https://".insteadOf ssh://
16 | git pull --rebase --autostash
17 | else
18 | echo "Only able to update if you clone the repository (git clone https://github.com/teralomaniac/clewd.git)"
19 | fi
20 | fi
--------------------------------------------------------------------------------