@maci0/dsh-chatjimmy 0.11.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API.md ADDED
@@ -0,0 +1,145 @@
1
+ # chatjimmy.ai: reverse-engineered API
2
+
3
+ Target: `https://chatjimmy.ai` (Next.js App Router, `x-powered-by: Next.js`, build `DCOFyTwcWkVONHBVSbo_G`).
4
+ Method: static analysis of the served JS chunks plus live black-box probing.
5
+ Owner per UI/legal links: Taalas Inc.
6
+
7
+ ## Endpoint inventory
8
+
9
+ The app exposes exactly three HTTP API routes. Everything else under `/api/*` returns 404.
10
+
11
+ | Method | Path | Auth | Notes |
12
+ |---|---|---|---|
13
+ | GET | `/api/health` | none | Readiness probe. Polled by the client every 10 s while on `/down`. |
14
+ | GET | `/api/models` | none | OpenAI-shaped model list. |
15
+ | POST | `/api/chat` | none | Streaming chat completion. `OPTIONS`/`POST` only; `GET` → 405. |
16
+
17
+ Confirmed 404: `/api/generate`, `/api/tags`, `/api/version`, `/api/stats`, `/api/status`, `/api/healthz`, `/api/config`, `/v1/models`, `/v1/chat/completions`.
18
+
19
+ `/api/chat/` (trailing slash) → 308 to `/api/chat`. No CORS headers are emitted, so all three are same-origin only.
20
+
21
+ There is no base-path prefix in the deployed build: the client computes its base as
22
+ `NEXT_PUBLIC_BASE_PATH ?? ""` (webpack module `93448`, export `s`) and appends `/api/...` to it.
23
+
24
+ ## GET /api/health
25
+
26
+ 200, `content-type: application/json`:
27
+
28
+ ```json
29
+ {
30
+ "status": "ok",
31
+ "nextjs": "healthy",
32
+ "backend": "healthy",
33
+ "backendStatus": 200,
34
+ "backendDetails": { "status": "healthy", "queue_size": 0, "current_adapter": "none" },
35
+ "timestamp": "2026-09-17T06:10:14.263Z"
36
+ }
37
+ ```
38
+
39
+ `backendDetails` is the parsed body of an upstream health check that the Next.js handler performs.
40
+ POST → 405.
41
+
42
+ ## GET /api/models
43
+
44
+ 200, `content-type: application/json`, `cache-control: no-store`:
45
+
46
+ ```json
47
+ {
48
+ "object": "list",
49
+ "data": [
50
+ { "id": "llama3.1-8B", "object": "model", "created": 1690000000, "owned_by": "Taalas Inc." }
51
+ ]
52
+ }
53
+ ```
54
+
55
+ The client reads `data[0].id` and stores it as `selectedModel`. POST → 405.
56
+
57
+ ## POST /api/chat
58
+
59
+ Request: `content-type: application/json`.
60
+
61
+ ```json
62
+ {
63
+ "messages": [
64
+ { "id": "uuid", "role": "user", "content": "hello" }
65
+ ],
66
+ "chatOptions": {
67
+ "selectedModel": "llama3.1-8B",
68
+ "systemPrompt": "",
69
+ "topK": 8
70
+ },
71
+ "attachment": { "name": "notes.txt", "size": 1234, "content": "raw text" }
72
+ }
73
+ ```
74
+
75
+ - `messages` is required. Standard user/assistant history, sent in full on every turn
76
+ (`useChat` from Vercel AI SDK v3, `streamMode: "text"`, with `options.body` merged in).
77
+ Message `id` is client-generated; the server echoes the last assistant id back in the stream.
78
+ - `chatOptions.selectedModel` is required: any non-empty string passes. The value is **not**
79
+ validated against `/api/models`; `"gpt-4"` is accepted and produces normal output. Only one
80
+ model is actually served.
81
+ - `chatOptions.systemPrompt` optional, prepended as a system message. Verified working.
82
+ - `chatOptions.topK` optional. Client default is 8; when omitted the upstream reports `topk: 1`.
83
+ - `attachment` optional, `null` when absent. Client reads the picked file **as UTF-8 text**
84
+ (`File.text()`), rejects files over 51200 bytes, and sends raw text, not base64, with no content type.
85
+ - `NEXT_PUBLIC_TOKEN_LIMIT` defaults to `6144` in the client; it only drives a UI counter.
86
+
87
+ Response: HTTP 200, `content-type: text/event-stream; charset=utf-8`,
88
+ `cache-control: no-cache, no-transform`. The body is **not** SSE-framed: it is the raw
89
+ generated text, streamed incrementally. Appended at the end, in the same byte stream:
90
+
91
+ ```
92
+ <|stats|>{ ...json... }<|/stats|>
93
+ ```
94
+
95
+ Example body:
96
+
97
+ ```
98
+ Hello my friend
99
+ <|stats|>{"created_at":1789625432.0217938,"done":true,"done_reason":"stop","total_duration":0.005132913589477539,"logprobs":null,"topk":8,"ttft":0.0011582374572753906,"reason":"termination token 128009/<|eot_id|> detected","status":0,"prefill_tokens":18,"prefill_rate":15540.854672704816,"decode_tokens":4,"decode_rate":19553.864801864802,"total_tokens":22,"total_time":0.0013763904571533203,"roundtrip_time":13}<|/stats|>
100
+ ```
101
+
102
+ The client strips the sentinel with `/<\|stats\|>([\s\S]+?)<\|stats\|>$/` and keeps the JSON as
103
+ per-message generation stats (tokens/s, TTFT, prefill/decode split). This is Ollama's
104
+ `/api/generate` response shape, so the Next.js route is a thin proxy onto an Ollama-compatible
105
+ inference backend.
106
+
107
+ ### Error responses
108
+
109
+ All errors are `application/json` with `{"success": false, "error": "<message>"}`.
110
+
111
+ | Status | Trigger | Body |
112
+ |---|---|---|
113
+ | 400 | `chatOptions` missing, or `selectedModel` empty/absent | `{"success":false,"error":"Selected model is required"}` |
114
+ | 500 | body is not valid JSON | `{"success":false,"error":"Unexpected token 'o', \"notjson\" is not valid JSON"}` |
115
+ | 500 | `messages` absent or empty | `{"success":false,"error":"Expected text/event-stream but received application/json; body: {\"created_at\":...,\"response\":\"\",\"done\":true,...}"}` |
116
+ | 405 | `GET /api/chat` | empty body, `allow: OPTIONS, POST` |
117
+
118
+ The empty-`messages` case is the interesting one: the handler leaked its upstream contract. It
119
+ fetches the backend, then asserts the upstream `content-type` is `text/event-stream` and raises on
120
+ anything else. An empty prompt makes the backend answer non-streamed JSON, so the assertion fires
121
+ and the backend's raw body lands in the client-visible error string. No upstream hostname is
122
+ exposed in any response header.
123
+
124
+ ### Rate limiting / queueing
125
+
126
+ No `x-ratelimit-*` headers on any route and no 429 observed. Backpressure is surfaced only through
127
+ `/api/health`'s `backendDetails.queue_size`.
128
+
129
+ ## Runtime behaviour
130
+
131
+ - The root layout gates the whole app: it `GET /api/health` with a 3 s abort, redirects to `/down`
132
+ when it fails, and re-polls every 10 s while there.
133
+ - Chat history lives only in browser `localStorage` under `chat_<id>` and `chatstats_<id>`.
134
+ There is no server-side conversation persistence endpoint.
135
+ - `/chats/<id>` is a client route over that local storage, not an API.
136
+
137
+ ## Reproducing
138
+
139
+ ```bash
140
+ curl -sS https://chatjimmy.ai/api/health
141
+ curl -sS https://chatjimmy.ai/api/models
142
+ curl -sS -X POST https://chatjimmy.ai/api/chat -H 'Content-Type: application/json' \
143
+ --data '{"messages":[{"role":"user","content":"Say hi in 3 words."}],
144
+ "chatOptions":{"selectedModel":"llama3.1-8B","systemPrompt":"","topK":8}}'
145
+ ```
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # dsh-chatjimmy
2
+
3
+ A `llama3.1-8B` route inside DeepSeek Harness, served by [chatjimmy.ai](https://chatjimmy.ai/). No API key and no account: the endpoint is public.
4
+ What you get is not the model. It is the accounting: the harness learns the service's real 6144-token ceiling, so a long session compacts instead of losing the turn to a zero-byte 200.
5
+ Install it for chat, session titles, and compaction. It is not an agent model.
6
+
7
+ ## What you get
8
+
9
+ - A `chatjimmy` provider route in the Web model picker, advertising `llama3.1-8B`.
10
+ - A reported 6144-token context window (prompt **and** completion), measured against the live service.
11
+ - Streaming replies with the trailing `<|stats|>…<|/stats|>` block stripped and reported as harness usage.
12
+ - Failure codes the harness can act on: overflow is `CONTEXT_WINDOW_EXCEEDED`, a stalled stream is `TIMEOUT`, a refused `stop` list is `UNSUPPORTED_OPTION`, a history with no text to send is `INVALID_REQUEST` (no request leaves), caller cancellation is `aborted`.
13
+ - No credential record to create. The endpoint is public and unauthenticated.
14
+
15
+ ## Install
16
+
17
+ > **Install it as a bundle.** `dsh plugin add …` mounts the row from the
18
+ > package's own patch layer, which is what the settings editor can write to. A
19
+ > row added with `--patch` is an overlay: it disappears at the next start, and
20
+ > the Plugins card cannot save into it (the editor refuses a write an overlay
21
+ > would win).
22
+
23
+ ```sh
24
+ dsh plugin --profile web add @maci0/dsh-chatjimmy@0.11.4
25
+ ```
26
+
27
+ This installs the public npm package; no GitHub token or `~/.secrets` setup is needed.
28
+ The version is pinned. To upgrade, run the same command with a newer version,
29
+ then restart `dsh web` (bundle layers compose at boot).
30
+
31
+ ## Configure
32
+
33
+ The defaults work with no row at all. Two surfaces write the same row: the **ChatJimmy** card on the Web client's **Plugins** page (its row's **Configure** control), and the profile patch.
34
+
35
+ In the client, open **Plugins** → the **chatjimmy** row → **Configure**. Every field below except `retryPolicy` is editable there; the card validates what it can see (an absolute http(s) URL, a non-empty model, positive whole numbers) and saves all changed fields in one write. A save lands on the next request: the adapter reads the row per read, so no remount and no dropped model selection.
36
+
37
+ To pin a value by hand instead, override the row by id in `~/.dsh/profiles/web/cordis.patch.yml`:
38
+
39
+ ```yaml
40
+ - id: chatjimmy
41
+ config:
42
+ model: llama3.1-8B
43
+ topK: 8
44
+ ```
45
+
46
+ A patch replaces the targeted row's whole `config`, so restate every key you keep. This file is live-watched: saving it remounts the plugin, no restart needed.
47
+
48
+ | Key | Default | Editable in the UI | Meaning |
49
+ |---|---|---|---|
50
+ | `baseUrl` | `https://chatjimmy.ai` | yes | Deployment origin. Must be an absolute http(s) URL; trailing slashes are trimmed. |
51
+ | `model` | `llama3.1-8B` | yes | Sent as `chatOptions.selectedModel`. `/api/models` advertises exactly this id; any id is accepted on the wire. |
52
+ | `topK` | `8` | yes | Forwarded as `chatOptions.topK`. Must be a positive integer. |
53
+ | `contextWindow` | `6144` | yes | Capacity reported to the harness. Change only if the backend does. |
54
+ | `streamIdleTimeoutMs` | `300000` | yes | Bound on a provider read, including HTTP error bodies. A stalled read ends with `TIMEOUT`; consumer backpressure does not consume the bound. |
55
+ | `retryPolicy` | *(absent)* | no | Provider-owned retry policy in the harness `RetryPolicyConfig` shape, e.g. `{ mode: normal, maxRetries: 2 }`. Absent keeps the harness defaults. Patch-only: a policy is an operator's decision. |
56
+
57
+ An invalid row throws at load rather than being silently defaulted: a typo'd `baseUrl` should not surface later as an opaque transport failure. The same check runs on every live read, so an edit that makes the row unusable is reported in the host log by the row's `loader/volatile-update` listener.
58
+
59
+ Do not also insert this row by hand while the package is a bundle in `dsh.profile.bundles`: `insert` does not dedupe ids, and two rows mount the plugin twice.
60
+
61
+ ## Routes and models
62
+
63
+ One route, one advertised model.
64
+
65
+ | Route | Picker label | Model id |
66
+ |---|---|---|
67
+ | `chatjimmy` | Chat Jimmy (Taalas) | `llama3.1-8B` |
68
+
69
+ The id is advisory: the adapter mirrors whatever `model` you configured in `listModels()` and accepts any string at request time. This route declares no reasoning efforts, so the picker shows no Effort menu for it.
70
+
71
+ The 6144-token limit is measured, not read from a header. The backend reports `prefill_tokens` per request, which makes the ceiling observable: 6141 prefill + 2 output tokens answered in full, while a request needing 6143 + 2 returned a zero-byte body. The site's own client caps at the same number (`NEXT_PUBLIC_TOKEN_LIMIT`, default 6144). Because the limit is on prompt **plus** completion, an oversized prompt fails only when the model would have generated enough to cross it, which is why the same prompt succeeds or fails at random.
72
+
73
+ ## Try it
74
+
75
+ 1. Restart `dsh web`, then open a session.
76
+ 2. Type `/model` in the composer, or click the model seat beside it.
77
+ 3. Pick **Chat Jimmy (Taalas)** → **Llama 3.1 8B (Chat Jimmy)**. Selecting a model also makes it the default for new sessions; a session that already sent a request keeps the model recorded in its own log.
78
+ 4. Type a prompt, for example:
79
+
80
+ ```
81
+ Suggest four names for a CLI that renames photos.
82
+ ```
83
+
84
+ The answer streams in as plain text. Nothing else is needed: no key, no settings page visit.
85
+
86
+ ## How it works
87
+
88
+ Failed HTTP requests forward valid `Retry-After` seconds or HTTP dates to the harness retry policy. Invalid, non-positive, and past delays are omitted.
89
+
90
+ - **Attribution.** Every request carries `attributionHeaders()` from `@deepseek-ai/dsh-llm`, so `User-Agent` cannot drift from the installed harness. That package's pure helpers (`attributionHeaders()`, `resolveRetryPolicy()`) are the plugin's only runtime dependency on `@deepseek-ai/dsh-llm`; `@deepseek-ai/schemastery` supplies the row schema, and the adapter is duck-typed, not an `LlmAdapter` subclass.
91
+ - **Request.** `POST {baseUrl}/api/chat` with the history flattened to text, every system- or developer-role message hoisted into the single `systemPrompt` slot, tool results projected onto user turns labelled `[tool result]` (the wire has no tool role), `attachment: null`.
92
+ - **Streaming.** The stats filter holds back text only as far as a marker could still be forming, so time-to-first-token is unaffected. Usage is emitted only when the provider reported counters; a synthesized zero would claim a measurement that never happened.
93
+ - **Failures.** HTTP 400/422 → `INVALID_REQUEST`, 401/403 → `AUTH`, 429 → `RATE_LIMIT`, 5xx → `SERVER`, anything else → `TRANSPORT`. A zero-byte HTTP 200 is the service's overflow signature, because the response headers are already committed as `text/event-stream`. That becomes `CONTEXT_WINDOW_EXCEEDED`, not an empty answer. A request whose history flattens to no user or assistant text is refused before it is sent, as `INVALID_REQUEST`: the service answers an empty `messages` list with HTTP 500, which the harness would retry as `SERVER`.
94
+ - **Stateless.** The service keeps no history, so the harness sends the full conversation every turn.
95
+
96
+ The wire contract this adapter implements (`GET /api/health`, `GET /api/models`, `POST /api/chat`) is documented in [`API.md`](API.md). It was reconstructed from the JavaScript the site serves to any visitor plus ordinary requests: no authentication was bypassed and no private endpoint was reached.
97
+
98
+ ## Limits
99
+
100
+ This is a text-only route. It is not an agent model.
101
+
102
+ The API accepts no `tools` field, so the adapter ignores `options.tools` rather than pretending otherwise: the model will never emit a tool call. A cross-provider history that contains tool calls and results is rendered as prose (`[tool call] name(args)`, `[tool result] output`) so the conversation still reads as one.
103
+
104
+ - **6144 tokens total**, prompt and completion together.
105
+ - **No images or files.** `inputModalities: ['text']` makes `LlmRuntime` project them to placeholder text before dispatch.
106
+ - **No sampling controls.** `temperature` and `maxTokens` are dropped: the API has no field for them. `stop` is refused with `UNSUPPORTED_OPTION` instead, because dropping it would change generation semantics. `topK` is the one knob it accepts.
107
+ - **No server-side history.** The harness resends everything each turn.
108
+ - **Prompts leave your machine** and go to a third-party service (Taalas Inc., per the site's own links).
109
+ - **Small model.** An 8B model is a fine chat, title, and compaction route; expect weak long-form reasoning.
110
+
111
+ ## Development
112
+
113
+ ```sh
114
+ bun test # hermetic, stubbed transport, no network
115
+ bun run build # tsc -p tsconfig.build.json → lib/index.js + lib/types/
116
+ bun run typecheck # tsc -p tsconfig.json
117
+ ```
118
+
119
+ The package ships the built `lib/` and declares `dsh.bundle`, so a change to `src/` needs `bun run build` before it takes effect. For local development, run `bun run build`, then `dsh plugin --profile <name> add <path-to-checkout>`.
120
+
121
+ Coverage: the wire-body projection and the stats splitter, including every single-character split point of the sentinel; the stream contract and both failure signatures over a stubbed fetch; and a real Cordis `Context` mount proving the route is registered and withdrawn with the fiber.
122
+
123
+ dsh loads plugins on Node `^22.19.0 || >=24.0.0`; development and tests run on bun.
124
+
125
+ ## Licence
126
+
127
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,13 @@
1
+ # The dsh-chatjimmy bundle patch: applied automatically when a profile lists
2
+ # this bundle (`dsh plugin add`/`update` appends the package to
3
+ # dsh.profile.bundles). Users override this row from their profile's own
4
+ # cordis.patch.yml (live-watched; dsh.profile.bundles is frozen at boot) with a
5
+ # `- id: chatjimmy` row, which replaces the row's whole `config`.
6
+ # Do not also insert this same row into the profile patch: insert does not
7
+ # dedupe ids, and a second row would register the plugin twice.
8
+ - insert:
9
+ - id: chatjimmy
10
+ name: '@maci0/dsh-chatjimmy'
11
+ config:
12
+ model: llama3.1-8B
13
+ topK: 8
package/icon.svg ADDED
@@ -0,0 +1,6 @@
1
+ <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M8 9.5h16.5a4 4 0 0 1 4 4v6.2a4 4 0 0 1-4 4H15.2L10 28.2V23.7H8a4 4 0 0 1-4-4v-6.2a4 4 0 0 1 4-4Z" fill="#6E56CF"/>
3
+ <circle cx="12.2" cy="16.6" r="1.35" fill="#FFFFFF"/>
4
+ <circle cx="16.6" cy="16.6" r="1.35" fill="#FFFFFF"/>
5
+ <circle cx="21" cy="16.6" r="1.35" fill="#FFFFFF"/>
6
+ </svg>