├── .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 |
2 |

Changelog

3 | 4 | Clewd 12 | 13 |
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 | Clewd 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 |

CHANGELOG

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]: ${percentage.toFixed(2)}%\n[length]: ${Config.CookieArray.length}\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(`Regex error: ` + 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(`${Main}\nhttp://${Config.Ip}:${Config.Port}/v1\n\n${Object.keys(Config.Settings).map((setting => UnknownSettings?.includes(setting) ? `??? ${setting}: ${Config.Settings[setting]}` : `${setting}: ${ChangedSettings?.includes(setting) ? '' : ''}${Config.Settings[setting]}`)).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(`No cookie available, enter apiKey-only mode.\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(`Null!`); 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: ${currentIndex + 1 || Config.CookieArray.length}) 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(`${flag}!`); 312 | return CookieCleaner(flag, percentage); 313 | } else uuidOrgArray.push(bootAccInfo.uuid); 314 | if (Config.Cookiecounter < 0) { 315 | console.log(`[progress]: ${percentage.toFixed(2)}%\n[length]: ${Config.CookieArray.length}\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 ? '' : ''}Your account has warnings %o`, formattedFlags); //console.warn('Your account has warnings %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 ? 'Banned' : 'Restricted'}!`); // 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(`Invalid!`); 390 | return CookieCleaner('Invalid', percentage); 391 | } 392 | console.error('Clewd:\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('having AllSamples and NoSamples 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(' / ') : ''}`); 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(`${err.status || 'Aborted'}!\n`) : console.error('Clewd:\n%o', err); //err.planned || console.error('Clewd:\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('likely your account is hard-censored'); 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 ? '' : ''}${fetchAPI.status}!\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(`Exceeded limit!\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: ${config}`); 889 | })); 890 | UnknownSettings.forEach((setting => { 891 | console.warn(`unknown setting in config.js: Settings.${setting}`); 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: ${config}`); 896 | userConfig[config] = Config[config]; 897 | })); 898 | missingSettings.forEach((setting => { 899 | console.warn(`adding missing setting in config.js: Settings.${setting}`); 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 [err] unavailable for ${process.platform}-${process.arch}, use 3.8.5 for the time being\n`);return false}e&&console.log(`superfetch [found] ${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 [err]",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

\"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 --------------------------------------------------------------------------------