Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
bb4ff9b
Merge pull request #213 from codercops/production
anurag629 Oct 20, 2026
5c34552
docs: fix the widget size and i18n claims in the READMEs (#206)
An16og Oct 20, 2026
af10c01
chore: add Biome lint, run it in CI, and fix what it reports (#209)
bhushan4work Oct 20, 2026
eb632cd
fix(website): count each visitor once a day (#215)
aryanjsx Oct 20, 2026
5ad6b1a
fix(widget): treat a stream that ends without [DONE] as a network err…
anurag629 Oct 20, 2026
2104c3b
fix(widget): drop the require export that pointed at the browser bund…
anurag629 Oct 20, 2026
8e3e79e
chore: add homepage, bugs, keywords and engines to every package (#219)
anurag629 Oct 20, 2026
ee5bf5f
ci: add a gzip size budget for the widget bundles (#220)
anurag629 Oct 20, 2026
bb81001
ci: report test coverage and enforce a minimum per package (#221)
anurag629 Oct 20, 2026
2125772
docs: add a guide for Next.js, Hono, Bun and Deno (#222)
anurag629 Oct 20, 2026
ad584a4
fix(server): handle a provider that fails to set up (#216)
Saifjafri212213 Oct 20, 2026
785a00a
fix(widget): validate stored conversation before returning it (#172) …
bhushan4work Oct 20, 2026
872550e
docs: only subscribe the webhook examples to lead:captured (#224)
prakashNalla Oct 20, 2026
049c52c
feat(core): add message parts, provider step and protocol v1 types (#…
anurag629 Oct 20, 2026
e375714
fix(docs): correct outdated chatbot FAQ answers (#226)
anurag2787 Oct 20, 2026
f930e9c
feat(core): add storage interfaces, memoryStorage and a conformance s…
anurag629 Oct 20, 2026
e082628
ci: pick up store packages automatically and run their databases in C…
anurag629 Oct 20, 2026
47ed5f1
feat(core): describe tool input with JSON Schema and check tools at s…
anurag629 Oct 20, 2026
fd80586
feat(core): write conversations with append and trim history by token…
anurag629 Oct 20, 2026
c82c485
docs: fix the maxTokens default and the locale prompt line (#252)
prakashNalla Oct 20, 2026
3dd7dc8
fix(core): use random UUIDs for tool call ID (#253)
anurag2787 Oct 20, 2026
d09a183
fix(website): add secret salt to visitor hash (#223) (#254)
bhushan4work Oct 20, 2026
e13dfc9
feat(widget): mark assistant messages with data-ai-generated (#255)
prakashNalla Oct 20, 2026
d320b44
Map error code (#257)
anurag2787 Oct 20, 2026
b138ded
feat(core): add Claude step() and replay recorded provider streams in…
anurag629 Oct 21, 2026
0578d5c
feat(core): add OpenAI step() with usage from the stream (#261)
anurag629 Oct 21, 2026
1ca06e1
feat(core): add Gemini step() that keeps thought signatures (#262)
anurag629 Oct 21, 2026
be2ad74
feat(core): run every reply through runTurn, with tools between model…
anurag629 Oct 21, 2026
1c76cf8
feat(core): cache Claude's system prompt and tools, with per-request …
anurag629 Oct 21, 2026
6d80bc9
test(server): run the handler tests on the real core, and accept a pr…
anurag629 Oct 21, 2026
86d7ec6
feat(server): add wildcard origin matching (#258)
srikar-naidu Oct 21, 2026
e8795ca
feat(core): translate protocol v1 parts into the pre-0.6 stream forma…
Saifjafri212213 Oct 21, 2026
ea66add
feat(store-postgres): add Postgres storage for conversations, counter…
anurag629 Oct 21, 2026
928e38c
test: apply the store coverage floor only in CI, where the databases …
anurag629 Oct 21, 2026
5cf6116
feat(server): run every reply through runTurn and save it with its pa…
anurag629 Oct 21, 2026
332e588
feat(server): regenerate a named reply, replay answered retries and k…
anurag629 Oct 21, 2026
bebf4ba
feat(store-postgres): stamp your own columns on new rows with rowHook…
anurag629 Oct 21, 2026
4aa886d
feat(widget): read protocol v1 parts and keep the server's reply ids …
anurag629 Oct 21, 2026
c892542
feat(server): protect Express analytics endpoint (#272)
anurag2787 Oct 21, 2026
8c582e4
feat(server): add a storage option, retention and purge() (#273)
anurag629 Oct 21, 2026
c2963af
feat(core): add a sliding window rate limiter on CounterStore (#274)
abelzach Oct 21, 2026
a01dd3e
feat(server): count rate limits on the storage counters and drop the …
anurag629 Oct 21, 2026
955809f
feat: add token, reply and dollar budgets checked before each model r…
anurag629 Oct 21, 2026
f9e13f1
feat: keep pre-chat answers from the first message, with a field allo…
anurag629 Oct 21, 2026
5bd99f4
feat(store-redis): add a Redis and Upstash storage adapter (#256)
bhushan4work Oct 21, 2026
0d666f2
feat(server): turn budgets on by default (#279)
anurag629 Oct 21, 2026
da93824
feat(server): read the visitor's IP only from headers the platform se…
anurag629 Oct 21, 2026
f7fb16a
feat(server): add allowedOrigins and refuse other sites with 403 (#281)
anurag629 Oct 21, 2026
2af6d9b
feat(server): add Cloudflare Turnstile token verification (#282)
bhushan4work Oct 21, 2026
96372c8
feat(core): add a shared SSE encoder (#284)
kachamsiddarth Oct 21, 2026
639a50c
feat(server): speak protocol v1 on the wire (#285)
anurag629 Oct 21, 2026
79fea7e
feat(server): add chatcopsAiSdkHandler for the AI SDK's useChat (#287)
anurag629 Oct 21, 2026
aa3bb33
feat(core): stop loading provider SDKs from the main entry (#288)
abelzach Oct 21, 2026
7ee50eb
docs: add a server storage page (#283)
srikar-naidu Oct 21, 2026
e1e5e79
feat(core): add an openai-compatible provider and registerProvider (#…
Pratham2511 Oct 21, 2026
36001d8
feat(server): sign and verify session tokens with HS256 (#290)
bhushan4work Oct 21, 2026
43d3d57
perf(widget): trim 37 bytes from the script tag bundle (#292)
Saifjafri212213 Oct 21, 2026
36b03b9
feat(store-sqlite): add a SQLite store on node:sqlite (#293)
srikar-naidu Oct 22, 2026
25380aa
fix(website): serve the docs chat through the Cloudflare adapter (#297)
Pratham2511 Oct 22, 2026
d5a3995
test(e2e): cover a chat API on a different origin than the page (#294)
piyusshhjangid Oct 22, 2026
74f4c63
feat(store-sqlite): add rowHook and extraColumns (#299)
anurag629 Oct 22, 2026
746b2cf
docs(website): drop the stale timer note in the chat route (#300)
anurag629 Oct 22, 2026
ad1d9db
feat(server): add POST /session and session tokens on chat requests (…
anurag629 Oct 22, 2026
79a2253
feat(widget): get and send session tokens when the server has them on…
anurag629 Oct 22, 2026
545963f
feat(server): check Cloudflare Turnstile before issuing a session (#303)
anurag629 Oct 22, 2026
a5ee069
test(e2e): run the browser tests against the real server handler (#304)
anurag629 Oct 22, 2026
c34a8d4
docs: add an upgrade guide from 0.5 (#305)
anurag629 Oct 22, 2026
d46e5d3
docs(server): add a security page (#306)
anurag629 Oct 22, 2026
12e4dd2
docs(core): update providers page for openai-compatible, registerProv…
prakashNalla Oct 22, 2026
5bc1d4d
docs(core): run a turn instead of chat() in the direct provider sampl…
anurag629 Oct 22, 2026
7cc0c9c
docs: document pre-chat form (#309)
piyusshhjangid Oct 22, 2026
634dde7
test(e2e): cover the published 0.5.0 widget (#311)
Pratham2511 Oct 22, 2026
c2276f4
docs(server): add protocol reference for custom clients (#312)
kachamsiddarth Oct 22, 2026
131099b
feat(store-cloudflare): add Cloudflare storage for D1, KV and Durable…
bhushan4work Oct 22, 2026
238eaef
feat(core): buffer analytics events into sinks (#314)
srujankaleru2007 Oct 22, 2026
63680cf
feat(store-cloudflare): add rowHook and keep turn locks in D1 (#315)
anurag629 Oct 22, 2026
7c27f8c
feat(server): write analytics to the storage event sink (#316)
anurag629 Oct 22, 2026
dbe1706
docs(server): document running purge() on a schedule (#313)
kachamsiddarth Oct 22, 2026
e4b6f02
feat(server): add startup security checks for secrets and origins (#317)
varun-pahuja Oct 23, 2026
be6ef6a
feat(server): log security settings and mistakes at startup (#318)
anurag629 Oct 23, 2026
5b31899
feat(widget): say why a reply failed: rate limit, budget, unavailable…
anurag629 Oct 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 5 additions & 0 deletions .changeset/ai-generated-attribute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/widget": patch
---

Assistant replies now carry `data-ai-generated="true"`, so scripts, extensions and CSS can tell model-written text apart. User messages and the welcome message don't get it.
8 changes: 8 additions & 0 deletions .changeset/ai-sdk-adapter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@chatcops/server": minor
"@chatcops/core": minor
---

New `chatcopsAiSdkHandler(config)`: a Fetch API handler that the AI SDK's `useChat` can talk to. It reads `useChat` requests and answers in the AI SDK's UI message stream, so an app built on the AI SDK can use a ChatCops server without changing its chat UI. The server keeps the history and reads only the last user message, so a client can't add turns the model never wrote. Server tools show up as tool parts the client doesn't run, a captured lead as a `data-lead-captured` part, and each assistant message has `metadata.conversationId`. A contract test runs the real `ai` 7 client against it.

`@chatcops/core` exports the mapping on its own: `aiSdkEncoder()` and `fromAiSdkRequest()`. `protocolStats()` on the handler now also counts `'ai-sdk'` requests.
7 changes: 7 additions & 0 deletions .changeset/allowed-origins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@chatcops/server": minor
---

New `allowedOrigins` option: the sites whose pages can use the chat, as exact origins or subdomain wildcards (`'https://*.acme.com'`). A browser request from any other page gets 403 `origin_not_allowed` before the model is called, with CORS headers so the page can read why. Responses echo the matched origin with `Vary: Origin`. Requests without an `Origin` header still pass. The default is still any origin (`['*']`).

`cors` is deprecated and read as a one-item `allowedOrigins`, with one warning. Setting both is an error. One change for `cors` users: a request without an `Origin` header no longer gets `Access-Control-Allow-Origin` back, which only browsers read, and browsers always send `Origin` on cross-origin requests.
5 changes: 5 additions & 0 deletions .changeset/bright-sqlite-store.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/store-sqlite": minor
---

Add SQLite storage for conversations, counters, key-value data, and analytics events.
6 changes: 6 additions & 0 deletions .changeset/budgets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@chatcops/core": minor
"@chatcops/server": minor
---

Budgets: limits on tokens, replies and dollars per conversation, per IP per UTC day, and overall per day, counted on a `CounterStore` so they hold across instances. Core has `createBudgetGuard()`, and `runTurn()` takes a `budget` that is checked before each model request, charged after it, and caps the reply length to the tokens left. A used-up budget ends the reply with `budget_exceeded` (`provider_error` for widgets before 0.6) without calling the model.
2 changes: 2 additions & 0 deletions .changeset/busy-lights-hammer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
2 changes: 2 additions & 0 deletions .changeset/chatty-cows-melt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
5 changes: 5 additions & 0 deletions .changeset/claude-step.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": minor
---

Add `ClaudeProvider.step()`, which runs one model request and reports text, reasoning, tool calls, token usage and why the model stopped, without running tools. Stored messages with parts go back to Claude as native blocks, including thinking signatures. Also adds a `fetch` option to every provider config (for proxies, or replaying recorded responses in tests) and `normalizeTurns()`, which cleans up history like `normalizeHistory()` but keeps tool calls and their results. `estimateTokens()` now counts tool inputs and results on messages with parts.
5 changes: 5 additions & 0 deletions .changeset/cloudflare-row-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/store-cloudflare": minor
---

`extraColumns` and `rowHook` stamp columns you've added (a tenant or site id) on every new conversation and message row, as in the Postgres and SQLite stores. The `kv` option is gone: ChatCops only uses key-value storage for turn locks, which need the atomic `setIfAbsent` that Workers KV doesn't have, so turn locks always live in D1 and hold across isolates.
5 changes: 5 additions & 0 deletions .changeset/compatible-provider-registry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": minor
---

Add an openai-compatible provider with optional API keys, custom headers, configurable tool support and max_tokens by default. Allow applications to register custom provider factories while validating keys for hosted providers.
5 changes: 4 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"access": "public",
"baseBranch": "production",
"updateInternalDependencies": "patch",
"ignore": []
"ignore": [],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"onlyUpdatePeerDependentsWhenOutOfRange": true
}
}
11 changes: 11 additions & 0 deletions .changeset/counter-store-rate-limiter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@chatcops/core": minor
---

Add `createRateLimiter` — a sliding-window rate limiter built on `CounterStore`.

- Uses two fixed-window counters to produce a smooth sliding estimate without per-request timestamps: `estimate = prev × (1 − elapsed/windowMs) + current`.
- Counts denied requests, so a hammering client stays limited.
- Returns `remaining` (never below 0) and `retryAfterMs` (clamped to `[1, windowMs]`) on every call.
- No timers or in-process state — safe at global scope on Cloudflare Workers and Vercel Edge.
- Exported from `@chatcops/core` as `createRateLimiter`, `RateLimitRule`, `RateLimitResult`, `RateLimiterOptions`.
6 changes: 6 additions & 0 deletions .changeset/default-budgets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@chatcops/core": minor
"@chatcops/server": minor
---

The server has budgets on by default: 200 replies per conversation and 2M tokens per IP per UTC day (`DEFAULT_BUDGETS`, filled in by `withDefaultBudgets()`). There's no default daily total. The server logs the budgets in force once at startup. Set a scope in `budgets` to replace its default, set a scope to `false` to turn it off, or pass `budgets: false` to turn them all off.
5 changes: 5 additions & 0 deletions .changeset/deprecation-tags.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": patch
---

`chat()` and `chatSync()` on `ClaudeProvider`, `OpenAIProvider` and `GeminiProvider` are marked `@deprecated` in their types, pointing to `runTurn()`. They behave as before.
5 changes: 5 additions & 0 deletions .changeset/dull-pianos-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": minor
---

Map protocol error codes to HTTP statuses (`statusForError`), build wire error bodies (`errorBody`), and format validation error paths (`describeInvalidRequest`).
2 changes: 2 additions & 0 deletions .changeset/eighty-pots-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
2 changes: 2 additions & 0 deletions .changeset/fiery-rooms-retire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
2 changes: 2 additions & 0 deletions .changeset/five-cobras-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
5 changes: 5 additions & 0 deletions .changeset/gemini-step.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": minor
---

Add `GeminiProvider.step()`. Thought signatures stay on the parts they came with and go back to Gemini on the next step. Calls from another provider get the placeholder signature Google documents, so a conversation can switch to Gemini 3 partway through. Safety, recitation and blocklist stops are reported as `content-filter`. Function call ids that Gemini didn't send are made up locally and never sent back.
10 changes: 10 additions & 0 deletions .changeset/handler-run-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@chatcops/server": minor
---

The handler now runs every reply through core's `runTurn()`. The stream widgets receive is the same as in 0.5. What changes:

- Each reply is saved as one assistant message with its parts (text, tool calls and their results), status and token usage, written after each tool round.
- One reply per conversation at a time: a second message sent while a reply is still streaming gets `provider_error`.
- Knowledge base excerpts, the visitor-context note and the locale line go after the system prompt instead of into it, so Claude's prompt cache can hold the system prompt.
- Tools get a signal that also aborts on `toolTimeoutMs`, and a tool's `emits()` decides what it adds to the reply.
11 changes: 11 additions & 0 deletions .changeset/json-schema-tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@chatcops/core": minor
"@chatcops/server": minor
---

Tools can describe their input with a full JSON Schema in `inputSchema`: nested objects, arrays, `enum`, `anyOf` and `$ref` all go to Claude, OpenAI and Gemini as written. The flat `parameters` form from 0.4 still works and is converted to the same schema.

- `execute(input, context)` now gets a second argument with the `conversationId` and an `AbortSignal` that fires when the visitor leaves. Tools that ignore it keep working.
- Tools can carry `annotations` (`title`, `readOnlyHint`, `destructiveHint`).
- The server checks tool definitions when it starts and throws a `ToolDefinitionError` that lists every problem: an invalid or duplicate name, an `inputSchema` whose root isn't `type: 'object'`, both `inputSchema` and `parameters` set, or a `required` key that isn't in `parameters`. Before, these failed as a provider error on the first message.
- New exports: `toolInputSchema`, `legacyParametersToSchema`, `validateTools`, `ToolDefinitionError`, and the `ToolContext` and `ToolAnnotations` types. `ToolDefinition.parameters` and `required` are now optional.
5 changes: 5 additions & 0 deletions .changeset/lazy-provider-sdks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": minor
---

Importing `@chatcops/core` no longer loads the Claude, OpenAI and Gemini SDKs, which cuts serverless cold-start time. To get there, `ClaudeProvider`, `OpenAIProvider` and `GeminiProvider` are no longer exported from the package root. Import them from their own subpaths instead: `@chatcops/core/providers/claude`, `@chatcops/core/providers/openai` and `@chatcops/core/providers/gemini`. `createProvider()` is unchanged and still loads only the SDK for the provider you configure.
5 changes: 5 additions & 0 deletions .changeset/legacy-chunk-translator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": minor
---

Add `toLegacyChunk(part)`, which turns a protocol v1 stream part into the chunk a widget before 0.6 reads (`{"content":"Hi"}`, `{"done":true}`, `{"error":"rate_limit","retryAfter":2}` and so on), or `null` for parts those widgets have no use for. The 0.6 server will use it to keep answering old widgets.
12 changes: 12 additions & 0 deletions .changeset/manager-on-append.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@chatcops/core": minor
"@chatcops/server": patch
---

Conversations are written with `append` instead of rewriting the whole conversation, so two server instances answering the same conversation no longer lose each other's messages, when the store supports it.

- `ConversationManager` takes `storage` (a `ChatCopsStorage`) as well as `store`, and defaults to `memoryStorage()`, which is bounded, instead of the unbounded `MemoryStore`.
- A store with only `get`, `save` and `delete` still works; the manager adds the other methods with `withAppend()` and logs one warning that concurrent writes from two instances can lose a message.
- A conversation is saved with its first message, not when it's first read.
- The history sent to the model is trimmed by an estimated token budget (32,000 by default, `maxHistoryTokens`), keeping the visitor's first message and the newest ones, instead of by message count. `trimHistory()` and `estimateTokens()` are exported. The stored cap (`maxMessages`, 100) still applies, now through atomic removes, and still never leaves an assistant turn first.
- New `appendMessages()` (with `expectedVersion`), `updateMessage()`, `getHistory()` and `acquireTurn()`, which keeps two replies from being generated for one conversation at once, across instances when the storage has `kv`.
2 changes: 2 additions & 0 deletions .changeset/max-tokens-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
10 changes: 10 additions & 0 deletions .changeset/message-parts-and-protocol-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@chatcops/core": minor
---

Add the types the next releases build on, with no change in behavior yet:

- `ChatMessage` can carry `parts` (text, reasoning, sources, tool calls and results, captured leads, handoff), a `status` (`complete`, `incomplete`, `aborted`), `usage` and `aiGenerated`. `Conversation` can carry a `version`. All of them are optional, so stores written for 0.4 keep working. `partsOf(message)` reads the parts of any message, treating `content` as one text part for messages saved without them, and `messageText(parts)` joins the text parts.
- `ProviderEvent`, `ProviderStepParams` and an optional `step()` on `AIProvider`, for providers that run one model request at a time.
- Wire protocol v1 types (`WirePart`, `ChatRequestV1`, `WireErrorCode`, `WireErrorBody`) and `PROTOCOL_VERSION`.
- The built-in providers now expose their `model` as a read-only property.
2 changes: 2 additions & 0 deletions .changeset/neat-buses-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
5 changes: 5 additions & 0 deletions .changeset/openai-step.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": minor
---

Add `OpenAIProvider.step()`. It asks for token usage with `stream_options.include_usage`, and if an OpenAI-compatible server rejects that option, it retries once without it and stops asking. A step that ends with `stop` but has tool calls (Ollama does this) counts as a tool step. Arguments that aren't valid JSON are reported with an `inputError` instead of being run.
7 changes: 7 additions & 0 deletions .changeset/package-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@chatcops/core": patch
"@chatcops/server": patch
"@chatcops/widget": patch
---

Add `homepage`, `bugs`, `keywords` and `engines` (`node >=22`) to the package metadata, so npm links to the docs and the issue tracker. Core and server are marked `sideEffects: false` for tree shaking. The widget isn't, because importing it starts the widget.
2 changes: 2 additions & 0 deletions .changeset/petite-experts-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
5 changes: 5 additions & 0 deletions .changeset/postgres-row-hook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/store-postgres": minor
---

`extraColumns` and `rowHook` stamp columns you've added (a tenant or site id) on every new conversation and message row. The hook runs on insert only, undeclared keys throw, and values are always bound as parameters.
6 changes: 6 additions & 0 deletions .changeset/prechat-once.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@chatcops/server": minor
"@chatcops/widget": patch
---

Pre-chat form answers are stored with the conversation from its first message, and later requests can't change them, so a visitor can't rewrite their details mid-conversation. The new `preChatFields` option keeps only the listed fields and drops anything else a request sends. Once a 0.6 server has answered, the widget stops sending the answers with every message.
5 changes: 5 additions & 0 deletions .changeset/prompt-caching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": minor
---

Claude requests now mark the system prompt and tools as a cached prefix, so repeat requests read them from the prompt cache. Turn it off with `promptCaching: false`. Steps and `runTurn()` take a new `systemContext` for instructions that change per request (knowledge base excerpts, the visitor's language), sent after the cached prefix so it stays the same from one request to the next. Cache reads and writes are reported in `usage`.
12 changes: 12 additions & 0 deletions .changeset/protocol-v1-wire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@chatcops/server": minor
"@chatcops/core": minor
---

The server speaks protocol v1. A request with `"protocol": 1` in the body (what the 0.6 widget sends) or an `x-chatcops-protocol: 1` header gets typed parts (`start`, `text-delta`, `finish` and so on) and a response header `x-chatcops-protocol: 1`. Requests without it get exactly the 0.5 stream, and the server logs one line the first time it answers one.

In protocol v1, a request that can't be answered gets a real HTTP status with a JSON body and CORS headers: 400 `invalid_request`, 409 `conversation_busy`, 413 `payload_too_large` (bodies over 256 KB), 429 `rate_limited` with `Retry-After`, 503 `provider_unavailable`. Errors after the stream starts are an `error` part, then `finish`.

All three adapters now share one module for this, so they send the same headers (`Content-Type: text/event-stream; charset=utf-8`, `Cache-Control: no-cache, no-transform`, `X-Accel-Buffering: no`, and no `Connection`, which HTTP/2 doesn't allow) and one `[DONE]`. When nothing has been sent for 10 seconds, as during a slow tool, they send a `: ping` comment so proxies and the widget don't close the connection. The CORS preflight allows the `Authorization` and `X-ChatCops-Protocol` headers.

New: `exposeUsage: true` adds token usage to the v1 `finish` part. `createChatHandler()` has `startChat()`, which resolves a refusal or the reply's v1 parts, for custom adapters, and `protocolStats()`, which counts requests in each format. `@chatcops/core/protocol` exports the protocol types and helpers on their own, for clients that don't need the rest of core.
6 changes: 6 additions & 0 deletions .changeset/provider-object.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@chatcops/core": minor
"@chatcops/server": minor
---

The server's `provider` option also takes a provider object (anything with `step()` or `chat()`), for providers ChatCops doesn't ship and for tests with `scriptedProvider()`. Core exports `isAIProvider()` to tell the two apart.
5 changes: 5 additions & 0 deletions .changeset/provider-setup-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/server": patch
---

A provider that fails to set up (an unknown `provider.type`, or an SDK that throws on a missing key) no longer stops the server process at startup. The failure is logged once with the `[chatcops] Provider setup failed:` prefix, without the API key or config, and every chat request gets the same `provider_error` a failed reply already gets.
5 changes: 5 additions & 0 deletions .changeset/quiet-sinks-batch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/core": minor
---

Add buffered analytics event writing and sink-backed statistics.
2 changes: 2 additions & 0 deletions .changeset/real-words-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
11 changes: 11 additions & 0 deletions .changeset/redis-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@chatcops/store-redis": minor
---

Add Redis storage adapter (`@chatcops/store-redis`) supporting `@upstash/redis` and `ioredis`.

- Implements `conversations` with atomic version-checked appends, in-place message updates, removals, listing, and purging using Redis hashes, lists, sorted sets, and Lua scripts.
- Implements `counters` with atomic increments and expiry initialization for rate limits.
- Implements `kv` with JSON serialization and `setIfAbsent` (`NX`) for distributed locks.
- Implements `events` with hourly count hashes for analytics event recording and time-bucketed queries.
- Exposes `redisStorage`, `fromIoRedis`, and the minimal `RedisLike` interface.
6 changes: 6 additions & 0 deletions .changeset/regenerate-retry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@chatcops/server": minor
"@chatcops/core": patch
---

Regenerate can name the reply to replace: `regenerate: { assistantMessageId }`. `true` still works. Retrying a message whose reply was already saved sends that reply again instead of asking the model for a second one, and a partial reply is replaced once a new one succeeds. A reply the visitor walked away from is now saved, marked `aborted`, and left out of what the model sees next.
9 changes: 9 additions & 0 deletions .changeset/run-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@chatcops/core": minor
---

Add `runTurn()`, the loop that runs one reply: model steps, the tools the model asks for, and saving the reply as it grows. It yields protocol v1 parts and returns the reply as a `ChatMessage` with parts, status and token usage. It works with providers written for 0.4 that only have `chat()`, too.

`chat()` and `chatSync()` on the Claude, OpenAI and Gemini providers now run on `runTurn()`. They behave as before, with three differences: `chatSync()` streams under the hood, a tool timeout now aborts the tool's signal, and an OpenAI-compatible server that ends a tool step with `stop` gets its tools run.

Also new: `toolRegistry()`, an optional `emits()` on tools (`LeadCaptureTool` uses it to add a `lead-captured` part), and `scriptedProvider()` at `@chatcops/core/testing/provider` for testing without a model.
7 changes: 7 additions & 0 deletions .changeset/security-startup-checks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
---

Add server startup security checks for session secrets and allowed origins.
Not exported from @chatcops/server yet; the checks will be called by the
handler in a follow-up PR. Empty changeset per issue requirement.
---
5 changes: 5 additions & 0 deletions .changeset/security-startup-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/server": minor
---

The server logs its security settings once at startup (where the chat is allowed from, session tokens, Turnstile), plus an error or a warning for mistakes it can spot: an allowed origin without `http://` or `https://`, or with a path, query, fragment or trailing slash; an `http://` origin that isn't local; `"*"` next to other origins; a session secret that's also the value of a `NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`, `NUXT_PUBLIC_`, `EXPO_PUBLIC_` or `REACT_APP_` variable; two secrets that are the same.
5 changes: 5 additions & 0 deletions .changeset/server-analytics-sink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/server": minor
---

With `analytics: true` and a `storage` that has an event sink, the server writes analytics events there too, buffered with `BufferedEventSink` and flushed before each reply's stream ends, so serverless runtimes don't drop them. `chatcopsAnalyticsHandler` then counts from the store, so the stats cover every instance, for `?from=` and `?to=` in milliseconds (default: the last 30 days). The handler's new `analyticsStats(range)` does the same; `getAnalytics()` still describes this process only.
5 changes: 5 additions & 0 deletions .changeset/server-rate-limit-counters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/server": minor
---

Rate limits now count on the storage's counters (`createRateLimiter` from core, a sliding window), so with a shared store a limit holds across every server instance instead of per process. The handler starts no timers anymore, so it can be created at global scope on Workers; `cleanup()` is kept but does nothing. The `RateLimiter` export is deprecated.
5 changes: 5 additions & 0 deletions .changeset/server-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chatcops/server": minor
---

New `storage` option: pass a store package (`postgresStorage(...)`, and Redis, SQLite and Cloudflare stores as they land) and conversations, plus the one-reply-at-a-time lock, are shared by every server instance. `conversationStore` still works but is deprecated, and setting both is an error. On Vercel, Lambda, Netlify or Workers with no storage set, the server warns once that memory doesn't last there. The handler also returns `purge()`, which deletes conversations older than `retention.conversationsDays` (default 30).
Loading
Loading