diff --git a/.changeset/ai-generated-attribute.md b/.changeset/ai-generated-attribute.md deleted file mode 100644 index 1884a27b..00000000 --- a/.changeset/ai-generated-attribute.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/ai-sdk-adapter.md b/.changeset/ai-sdk-adapter.md deleted file mode 100644 index fad97014..00000000 --- a/.changeset/ai-sdk-adapter.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@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. diff --git a/.changeset/allowed-origins.md b/.changeset/allowed-origins.md deleted file mode 100644 index 6754946a..00000000 --- a/.changeset/allowed-origins.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@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. diff --git a/.changeset/bright-sqlite-store.md b/.changeset/bright-sqlite-store.md deleted file mode 100644 index 3c1c2d85..00000000 --- a/.changeset/bright-sqlite-store.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/store-sqlite": minor ---- - -Add SQLite storage for conversations, counters, key-value data, and analytics events. diff --git a/.changeset/budgets.md b/.changeset/budgets.md deleted file mode 100644 index b9fc6262..00000000 --- a/.changeset/budgets.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@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. diff --git a/.changeset/busy-lights-hammer.md b/.changeset/busy-lights-hammer.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/busy-lights-hammer.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/chatty-cows-melt.md b/.changeset/chatty-cows-melt.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/chatty-cows-melt.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/claude-step.md b/.changeset/claude-step.md deleted file mode 100644 index bc8bc99d..00000000 --- a/.changeset/claude-step.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/cloudflare-row-hook.md b/.changeset/cloudflare-row-hook.md deleted file mode 100644 index 21b29299..00000000 --- a/.changeset/cloudflare-row-hook.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/compatible-provider-registry.md b/.changeset/compatible-provider-registry.md deleted file mode 100644 index ed4f5119..00000000 --- a/.changeset/compatible-provider-registry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/counter-store-rate-limiter.md b/.changeset/counter-store-rate-limiter.md deleted file mode 100644 index 6fe8845d..00000000 --- a/.changeset/counter-store-rate-limiter.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@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`. diff --git a/.changeset/default-budgets.md b/.changeset/default-budgets.md deleted file mode 100644 index 9eeb7df8..00000000 --- a/.changeset/default-budgets.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@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. diff --git a/.changeset/deprecation-tags.md b/.changeset/deprecation-tags.md deleted file mode 100644 index a15f4baf..00000000 --- a/.changeset/deprecation-tags.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/core": patch ---- - -`chat()` and `chatSync()` on `ClaudeProvider`, `OpenAIProvider` and `GeminiProvider` are marked `@deprecated` in their types, pointing to `runTurn()`. They behave as before. diff --git a/.changeset/dull-pianos-lick.md b/.changeset/dull-pianos-lick.md deleted file mode 100644 index ae302a90..00000000 --- a/.changeset/dull-pianos-lick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/core": minor ---- - -Map protocol error codes to HTTP statuses (`statusForError`), build wire error bodies (`errorBody`), and format validation error paths (`describeInvalidRequest`). diff --git a/.changeset/eighty-pots-drop.md b/.changeset/eighty-pots-drop.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/eighty-pots-drop.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/fiery-rooms-retire.md b/.changeset/fiery-rooms-retire.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/fiery-rooms-retire.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/five-cobras-add.md b/.changeset/five-cobras-add.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/five-cobras-add.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/gemini-step.md b/.changeset/gemini-step.md deleted file mode 100644 index 153c831a..00000000 --- a/.changeset/gemini-step.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/handler-run-turn.md b/.changeset/handler-run-turn.md deleted file mode 100644 index 4af54013..00000000 --- a/.changeset/handler-run-turn.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@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. diff --git a/.changeset/json-schema-tools.md b/.changeset/json-schema-tools.md deleted file mode 100644 index 71144af6..00000000 --- a/.changeset/json-schema-tools.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@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. diff --git a/.changeset/lazy-provider-sdks.md b/.changeset/lazy-provider-sdks.md deleted file mode 100644 index 9b2f53f6..00000000 --- a/.changeset/lazy-provider-sdks.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/legacy-chunk-translator.md b/.changeset/legacy-chunk-translator.md deleted file mode 100644 index 257b3d3d..00000000 --- a/.changeset/legacy-chunk-translator.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/manager-on-append.md b/.changeset/manager-on-append.md deleted file mode 100644 index 905c8123..00000000 --- a/.changeset/manager-on-append.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@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`. diff --git a/.changeset/max-tokens-docs.md b/.changeset/max-tokens-docs.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/max-tokens-docs.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/message-parts-and-protocol-types.md b/.changeset/message-parts-and-protocol-types.md deleted file mode 100644 index 6015e033..00000000 --- a/.changeset/message-parts-and-protocol-types.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@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. diff --git a/.changeset/neat-buses-give.md b/.changeset/neat-buses-give.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/neat-buses-give.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/openai-step.md b/.changeset/openai-step.md deleted file mode 100644 index d2265893..00000000 --- a/.changeset/openai-step.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/package-metadata.md b/.changeset/package-metadata.md deleted file mode 100644 index b90c03fb..00000000 --- a/.changeset/package-metadata.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@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. diff --git a/.changeset/petite-experts-march.md b/.changeset/petite-experts-march.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/petite-experts-march.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/postgres-row-hook.md b/.changeset/postgres-row-hook.md deleted file mode 100644 index 2ea8c78f..00000000 --- a/.changeset/postgres-row-hook.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/prechat-once.md b/.changeset/prechat-once.md deleted file mode 100644 index 8d7a2cc7..00000000 --- a/.changeset/prechat-once.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@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. diff --git a/.changeset/prompt-caching.md b/.changeset/prompt-caching.md deleted file mode 100644 index 34947060..00000000 --- a/.changeset/prompt-caching.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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`. diff --git a/.changeset/protocol-v1-wire.md b/.changeset/protocol-v1-wire.md deleted file mode 100644 index 073e4f1c..00000000 --- a/.changeset/protocol-v1-wire.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@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. diff --git a/.changeset/provider-object.md b/.changeset/provider-object.md deleted file mode 100644 index 2f111775..00000000 --- a/.changeset/provider-object.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@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. diff --git a/.changeset/provider-setup-errors.md b/.changeset/provider-setup-errors.md deleted file mode 100644 index 536295a5..00000000 --- a/.changeset/provider-setup-errors.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/quiet-sinks-batch.md b/.changeset/quiet-sinks-batch.md deleted file mode 100644 index cbcee980..00000000 --- a/.changeset/quiet-sinks-batch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/core": minor ---- - -Add buffered analytics event writing and sink-backed statistics. diff --git a/.changeset/real-words-refuse.md b/.changeset/real-words-refuse.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/real-words-refuse.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/redis-storage.md b/.changeset/redis-storage.md deleted file mode 100644 index 9168d6e2..00000000 --- a/.changeset/redis-storage.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@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. diff --git a/.changeset/regenerate-retry.md b/.changeset/regenerate-retry.md deleted file mode 100644 index d1bac9cb..00000000 --- a/.changeset/regenerate-retry.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@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. diff --git a/.changeset/run-turn.md b/.changeset/run-turn.md deleted file mode 100644 index 3572c553..00000000 --- a/.changeset/run-turn.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@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. diff --git a/.changeset/security-startup-checks.md b/.changeset/security-startup-checks.md deleted file mode 100644 index b2fded7f..00000000 --- a/.changeset/security-startup-checks.md +++ /dev/null @@ -1,7 +0,0 @@ ---- ---- - -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. ---- diff --git a/.changeset/security-startup-log.md b/.changeset/security-startup-log.md deleted file mode 100644 index d5cf9f07..00000000 --- a/.changeset/security-startup-log.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/server-analytics-sink.md b/.changeset/server-analytics-sink.md deleted file mode 100644 index 6cceadea..00000000 --- a/.changeset/server-analytics-sink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/server-rate-limit-counters.md b/.changeset/server-rate-limit-counters.md deleted file mode 100644 index 0ac56d4d..00000000 --- a/.changeset/server-rate-limit-counters.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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. diff --git a/.changeset/server-storage.md b/.changeset/server-storage.md deleted file mode 100644 index d75b48cb..00000000 --- a/.changeset/server-storage.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@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). diff --git a/.changeset/session-endpoint.md b/.changeset/session-endpoint.md deleted file mode 100644 index 5d035771..00000000 --- a/.changeset/session-endpoint.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@chatcops/server": minor -"@chatcops/core": minor ---- - -Session tokens. With `sessionSecrets` set, `POST /session` gives a short-lived token bound to one conversation and the page's origin, and chat requests need it as `Authorization: Bearer ` (401 `session_required`, `token_expired` or `conversation_mismatch` otherwise). A conversation that has messages is resumed only with a token that was issued for it, so a guessed id gets a new conversation. An id with nothing stored yet is given out as is. `requireSession: false` lets requests without a token through while older widgets are around. Adds the `conversation_mismatch` error code to protocol v1. diff --git a/.changeset/shared-sse-encoder.md b/.changeset/shared-sse-encoder.md deleted file mode 100644 index 00882e54..00000000 --- a/.changeset/shared-sse-encoder.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@chatcops/core": minor ---- - -Expose shared SSE utilities for streaming responses: - -- `encodeSSE(part)`: encodes a `WirePart` or object into standard SSE framing (`data: \n\n`). -- `SSE_PING`: comment frame (`: ping\n\n`) to keep connections alive without emitting events. -- `SSE_DONE`: terminal frame (`data: [DONE]\n\n`) signaling stream completion. -- `SSE_HEADERS`: immutable standard HTTP headers for SSE responses (`Content-Type`, `Cache-Control`, and `X-Accel-Buffering`, with `Connection` omitted for HTTP/2 compatibility). diff --git a/.changeset/sqlite-row-hook.md b/.changeset/sqlite-row-hook.md deleted file mode 100644 index 4938d719..00000000 --- a/.changeset/sqlite-row-hook.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/store-sqlite": minor ---- - -`extraColumns` and `rowHook` stamp columns you've added (a tenant or site id) on every new conversation and message row, as in `@chatcops/store-postgres`. The hook runs on insert only, undeclared keys throw, and values are always bound as parameters. diff --git a/.changeset/storage-interfaces.md b/.changeset/storage-interfaces.md deleted file mode 100644 index e1e69189..00000000 --- a/.changeset/storage-interfaces.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@chatcops/core": minor ---- - -Add the storage layer the next server release uses. Nothing uses it yet, so behavior doesn't change. - -- `ConversationStore` gains optional `append` (atomic, with an `expectedVersion` check that throws `ConflictError`), `updateMessage`, `removeMessage`, `list` and `purge`. A store with only `get`, `save` and `delete` is still valid. -- New `CounterStore`, `KeyValueStore` and `EventSink` interfaces, grouped in `ChatCopsStorage`. -- `memoryStorage()` implements all of them in process, with a cap on conversations (least recently used go first), idle expiry, capped counters, keys and events, and no timers, so it's safe at global scope on Workers. -- `withAppend(store)` adds the new conversation methods to a 0.4 store, serializing writes per conversation within one process. -- `@chatcops/core/testing` exports `runStorageConformance(name, factory)`, the checks every store must pass, for store packages and custom stores. It needs `vitest`, an optional peer dependency. diff --git a/.changeset/store-cloudflare.md b/.changeset/store-cloudflare.md deleted file mode 100644 index dba41ba5..00000000 --- a/.changeset/store-cloudflare.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/store-cloudflare": minor ---- - -Add Cloudflare storage for D1, KV, and Durable Objects counters. diff --git a/.changeset/store-postgres.md b/.changeset/store-postgres.md deleted file mode 100644 index f52f5056..00000000 --- a/.changeset/store-postgres.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/store-postgres": minor ---- - -New package: Postgres storage for conversations, counters, keys and analytics events. Works with `pg`, Neon (`fromNeon`) and postgres.js (`fromPostgresJs`), since every write is a single statement. Create the tables with `npx chatcops-postgres migrate` or `migrate(db)`. diff --git a/.changeset/stream-end-without-done.md b/.changeset/stream-end-without-done.md deleted file mode 100644 index 5e71cae2..00000000 --- a/.changeset/stream-end-without-done.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/widget": patch ---- - -A reply whose connection is cut before the server's final `[DONE]` (a proxy timeout or a server crash, for example) now shows a network error and a Retry button under the text received so far. Before, it looked like a finished answer. diff --git a/.changeset/thick-cloths-sip.md b/.changeset/thick-cloths-sip.md deleted file mode 100644 index ab903513..00000000 --- a/.changeset/thick-cloths-sip.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/core": patch ---- - -Use random UUIDs for fallback tool call IDs in OpenAI and Gemini providers instead of module counters. diff --git a/.changeset/trim-widget-bundle.md b/.changeset/trim-widget-bundle.md deleted file mode 100644 index 706a77d3..00000000 --- a/.changeset/trim-widget-bundle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/widget": patch ---- - -The script tag bundle is 37 bytes smaller (gzipped): the inline icons no longer repeat an `xmlns` attribute, and the stylesheet drops the default `ease` keyword from its transitions. The widget looks and behaves the same. diff --git a/.changeset/trusted-client-ip.md b/.changeset/trusted-client-ip.md deleted file mode 100644 index c6293699..00000000 --- a/.changeset/trusted-client-ip.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@chatcops/server": minor ---- - -The visitor's IP, which rate limits and the per-IP budget key on, is only read from headers a platform sets itself. - -- `chatcopsVercelHandler` reads Vercel's headers only when it runs on Vercel. Elsewhere it no longer trusts the first `X-Forwarded-For` entry, which a visitor can set to get a fresh limit with every request. Set the new `clientIp` option to read your host's header (Netlify's `x-nf-client-connection-ip`, or one your proxy sets). Until then per-IP limits are skipped, with one warning. -- `chatcopsCloudflareHandler` reads `CF-Connecting-IP`, and also takes `clientIp`. -- Express keeps `req.ip`, and warns once when requests carry `X-Forwarded-For` while `trust proxy` is off. -- A request without a known IP skips per-IP limits instead of sharing one `"unknown"` limit with every other such visitor. diff --git a/.changeset/turnstile-session.md b/.changeset/turnstile-session.md deleted file mode 100644 index 71d7c443..00000000 --- a/.changeset/turnstile-session.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -"@chatcops/server": minor -"@chatcops/widget": minor -"@chatcops/core": minor ---- - -Cloudflare Turnstile when a session is issued. With `turnstile: { secretKey }` on the server, `/session` checks a Turnstile token with Cloudflare before signing (401 `turnstile_failed` otherwise, 503 when Cloudflare can't be reached), and a refresh with the previous token skips it. The widget's `turnstileSiteKey` (`data-turnstile-site-key`) loads Cloudflare's script only when it first needs a session and renders the challenge in the page, outside the shadow root. diff --git a/.changeset/upset-plums-attack.md b/.changeset/upset-plums-attack.md deleted file mode 100644 index a845151c..00000000 --- a/.changeset/upset-plums-attack.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/validate-stored-conversations.md b/.changeset/validate-stored-conversations.md deleted file mode 100644 index c619bcce..00000000 --- a/.changeset/validate-stored-conversations.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/widget": patch ---- - -Validate stored conversations before restoring them. Invalid top-level structures and malformed JSON are cleared from storage, and messages without a valid user or assistant role and string content are filtered out. diff --git a/.changeset/widget-limit-messages.md b/.changeset/widget-limit-messages.md deleted file mode 100644 index db7c7a9c..00000000 --- a/.changeset/widget-limit-messages.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/widget": minor ---- - -Clearer messages when a reply can't happen. The widget now reads the server's reason and says it: too many messages, with the wait in seconds when the server sends one; the conversation or the day's budget used up; the assistant unavailable (the provider couldn't be set up); or the reply cut off. Each one is a new `strings` key (`errorRateLimitWait`, `errorBudget`, `errorBudgetDaily`, `errorUnavailable`, `errorCutOff`). The script tag bundle's size budget goes from 14 kB to 14.5 kB (18 kB for ESM) to fit them. diff --git a/.changeset/widget-require-export.md b/.changeset/widget-require-export.md deleted file mode 100644 index 2db76db3..00000000 --- a/.changeset/widget-require-export.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/widget": patch ---- - -Remove the `require` export condition. It pointed at the script tag bundle, which never sets `module.exports`, so `require('@chatcops/widget')` returned an empty object. CommonJS code now gets a clear `ERR_PACKAGE_PATH_NOT_EXPORTED` error; use `import` instead. The `main` field and the `dist/chatcops.min.js` file that the CDN snippet loads are unchanged. The build also always writes the type declarations now, even after an earlier local build. diff --git a/.changeset/widget-sessions.md b/.changeset/widget-sessions.md deleted file mode 100644 index d6c003e2..00000000 --- a/.changeset/widget-sessions.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/widget": minor ---- - -Session tokens. When the server has them on and a chat request gets a 401, the widget gets a token from `/session`, keeps it in `localStorage`, and sends the request again with `Authorization: Bearer`. The saved token lets the server resume the conversation after a reload, and the widget moves to a new conversation id when the server gives one. Servers without session tokens see no change. diff --git a/.changeset/widget-v1.md b/.changeset/widget-v1.md deleted file mode 100644 index 4df60303..00000000 --- a/.changeset/widget-v1.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/widget": minor ---- - -The widget asks for protocol v1 (`protocol: 1` in the request body) and reads its typed parts. Servers before 0.6 ignore the field and answer in the old format, which the widget still reads. With a v1 server, replies keep the id the server saved them under, so regenerate names the exact reply to replace, and a stream that ends without its `finish` part shows "cut off" with Retry instead of passing as a finished answer. Unknown part types are ignored. diff --git a/.changeset/yellow-bobcats-appear.md b/.changeset/yellow-bobcats-appear.md deleted file mode 100644 index bbae5632..00000000 --- a/.changeset/yellow-bobcats-appear.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@chatcops/server": minor ---- - -Add `analyticsToken` option to require a bearer token on `chatcopsAnalyticsHandler` diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index add8be54..a052bd21 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,94 @@ # @chatcops/core +## 0.5.0 + +### Minor Changes + +- 79fea7e: 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. + +- 955809f: 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. +- b138ded: 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. +- e1e5e79: 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. +- c2963af: 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`. + +- 0d666f2: 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. +- d320b44: Map protocol error codes to HTTP statuses (`statusForError`), build wire error bodies (`errorBody`), and format validation error paths (`describeInvalidRequest`). +- 1ca06e1: 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. +- 47ed5f1: 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. + +- aa3bb33: 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. +- e8795ca: 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. +- fd80586: 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`. + +- 049c52c: 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. + +- 0578d5c: 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. +- 1c76cf8: 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`. +- 639a50c: 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. + +- 6d80bc9: 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. +- 238eaef: Add buffered analytics event writing and sink-backed statistics. +- be2ad74: 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. + +- ad1d9db: Session tokens. With `sessionSecrets` set, `POST /session` gives a short-lived token bound to one conversation and the page's origin, and chat requests need it as `Authorization: Bearer ` (401 `session_required`, `token_expired` or `conversation_mismatch` otherwise). A conversation that has messages is resumed only with a token that was issued for it, so a guessed id gets a new conversation. An id with nothing stored yet is given out as is. `requireSession: false` lets requests without a token through while older widgets are around. Adds the `conversation_mismatch` error code to protocol v1. +- 96372c8: Expose shared SSE utilities for streaming responses: + + - `encodeSSE(part)`: encodes a `WirePart` or object into standard SSE framing (`data: \n\n`). + - `SSE_PING`: comment frame (`: ping\n\n`) to keep connections alive without emitting events. + - `SSE_DONE`: terminal frame (`data: [DONE]\n\n`) signaling stream completion. + - `SSE_HEADERS`: immutable standard HTTP headers for SSE responses (`Content-Type`, `Cache-Control`, and `X-Accel-Buffering`, with `Connection` omitted for HTTP/2 compatibility). + +- f930e9c: Add the storage layer the next server release uses. Nothing uses it yet, so behavior doesn't change. + + - `ConversationStore` gains optional `append` (atomic, with an `expectedVersion` check that throws `ConflictError`), `updateMessage`, `removeMessage`, `list` and `purge`. A store with only `get`, `save` and `delete` is still valid. + - New `CounterStore`, `KeyValueStore` and `EventSink` interfaces, grouped in `ChatCopsStorage`. + - `memoryStorage()` implements all of them in process, with a cap on conversations (least recently used go first), idle expiry, capped counters, keys and events, and no timers, so it's safe at global scope on Workers. + - `withAppend(store)` adds the new conversation methods to a 0.4 store, serializing writes per conversation within one process. + - `@chatcops/core/testing` exports `runStorageConformance(name, factory)`, the checks every store must pass, for store packages and custom stores. It needs `vitest`, an optional peer dependency. + +- 545963f: Cloudflare Turnstile when a session is issued. With `turnstile: { secretKey }` on the server, `/session` checks a Turnstile token with Cloudflare before signing (401 `turnstile_failed` otherwise, 503 when Cloudflare can't be reached), and a refresh with the previous token skips it. The widget's `turnstileSiteKey` (`data-turnstile-site-key`) loads Cloudflare's script only when it first needs a session and renders the challenge in the page, outside the shadow root. + +### Patch Changes + +- c34a8d4: `chat()` and `chatSync()` on `ClaudeProvider`, `OpenAIProvider` and `GeminiProvider` are marked `@deprecated` in their types, pointing to `runTurn()`. They behave as before. +- 8e3e79e: 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. +- 332e588: 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. +- 3dd7dc8: Use random UUIDs for fallback tool call IDs in OpenAI and Gemini providers instead of module counters. + ## 0.4.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index 2230fda0..9e220a76 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@chatcops/core", - "version": "0.4.0", + "version": "0.5.0", "description": "Core AI provider abstraction, tools, and knowledge base for ChatCops", "keywords": [ "chatcops", diff --git a/packages/server/CHANGELOG.md b/packages/server/CHANGELOG.md index bca0f6a2..3d162482 100644 --- a/packages/server/CHANGELOG.md +++ b/packages/server/CHANGELOG.md @@ -1,5 +1,100 @@ # @chatcops/server +## 0.6.0 + +### Minor Changes + +- 79fea7e: 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. + +- f7fb16a: 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. + +- 955809f: 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. +- 0d666f2: 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. +- 5cf6116: 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. + +- 47ed5f1: 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. + +- f9e13f1: 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. +- 639a50c: 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. + +- 6d80bc9: 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. +- 332e588: 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. +- be6ef6a: 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. +- 7c27f8c: 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. +- a01dd3e: 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. +- 8c582e4: 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). +- ad1d9db: Session tokens. With `sessionSecrets` set, `POST /session` gives a short-lived token bound to one conversation and the page's origin, and chat requests need it as `Authorization: Bearer ` (401 `session_required`, `token_expired` or `conversation_mismatch` otherwise). A conversation that has messages is resumed only with a token that was issued for it, so a guessed id gets a new conversation. An id with nothing stored yet is given out as is. `requireSession: false` lets requests without a token through while older widgets are around. Adds the `conversation_mismatch` error code to protocol v1. +- da93824: The visitor's IP, which rate limits and the per-IP budget key on, is only read from headers a platform sets itself. + + - `chatcopsVercelHandler` reads Vercel's headers only when it runs on Vercel. Elsewhere it no longer trusts the first `X-Forwarded-For` entry, which a visitor can set to get a fresh limit with every request. Set the new `clientIp` option to read your host's header (Netlify's `x-nf-client-connection-ip`, or one your proxy sets). Until then per-IP limits are skipped, with one warning. + - `chatcopsCloudflareHandler` reads `CF-Connecting-IP`, and also takes `clientIp`. + - Express keeps `req.ip`, and warns once when requests carry `X-Forwarded-For` while `trust proxy` is off. + - A request without a known IP skips per-IP limits instead of sharing one `"unknown"` limit with every other such visitor. + +- 545963f: Cloudflare Turnstile when a session is issued. With `turnstile: { secretKey }` on the server, `/session` checks a Turnstile token with Cloudflare before signing (401 `turnstile_failed` otherwise, 503 when Cloudflare can't be reached), and a refresh with the previous token skips it. The widget's `turnstileSiteKey` (`data-turnstile-site-key`) loads Cloudflare's script only when it first needs a session and renders the challenge in the page, outside the shadow root. +- c892542: Add `analyticsToken` option to require a bearer token on `chatcopsAnalyticsHandler` + +### Patch Changes + +- fd80586: 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`. + +- 8e3e79e: 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. +- ad584a4: 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. +- Updated dependencies [79fea7e] +- Updated dependencies [955809f] +- Updated dependencies [b138ded] +- Updated dependencies [e1e5e79] +- Updated dependencies [c2963af] +- Updated dependencies [0d666f2] +- Updated dependencies [c34a8d4] +- Updated dependencies [d320b44] +- Updated dependencies [1ca06e1] +- Updated dependencies [47ed5f1] +- Updated dependencies [aa3bb33] +- Updated dependencies [e8795ca] +- Updated dependencies [fd80586] +- Updated dependencies [049c52c] +- Updated dependencies [0578d5c] +- Updated dependencies [8e3e79e] +- Updated dependencies [1c76cf8] +- Updated dependencies [639a50c] +- Updated dependencies [6d80bc9] +- Updated dependencies [238eaef] +- Updated dependencies [332e588] +- Updated dependencies [be2ad74] +- Updated dependencies [ad1d9db] +- Updated dependencies [96372c8] +- Updated dependencies [f930e9c] +- Updated dependencies [3dd7dc8] +- Updated dependencies [545963f] + - @chatcops/core@0.5.0 + ## 0.5.0 ### Minor Changes diff --git a/packages/server/package.json b/packages/server/package.json index e632cbf9..30a85eb6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@chatcops/server", - "version": "0.5.0", + "version": "0.6.0", "description": "Server-side handler and adapters for ChatCops", "keywords": [ "chatcops", diff --git a/packages/store-cloudflare/CHANGELOG.md b/packages/store-cloudflare/CHANGELOG.md new file mode 100644 index 00000000..ee0f7bb1 --- /dev/null +++ b/packages/store-cloudflare/CHANGELOG.md @@ -0,0 +1,39 @@ +# @chatcops/store-cloudflare + +## 0.1.0 + +### Minor Changes + +- 63680cf: `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. +- 131099b: Add Cloudflare storage for D1, KV, and Durable Objects counters. + +### Patch Changes + +- Updated dependencies [79fea7e] +- Updated dependencies [955809f] +- Updated dependencies [b138ded] +- Updated dependencies [e1e5e79] +- Updated dependencies [c2963af] +- Updated dependencies [0d666f2] +- Updated dependencies [c34a8d4] +- Updated dependencies [d320b44] +- Updated dependencies [1ca06e1] +- Updated dependencies [47ed5f1] +- Updated dependencies [aa3bb33] +- Updated dependencies [e8795ca] +- Updated dependencies [fd80586] +- Updated dependencies [049c52c] +- Updated dependencies [0578d5c] +- Updated dependencies [8e3e79e] +- Updated dependencies [1c76cf8] +- Updated dependencies [639a50c] +- Updated dependencies [6d80bc9] +- Updated dependencies [238eaef] +- Updated dependencies [332e588] +- Updated dependencies [be2ad74] +- Updated dependencies [ad1d9db] +- Updated dependencies [96372c8] +- Updated dependencies [f930e9c] +- Updated dependencies [3dd7dc8] +- Updated dependencies [545963f] + - @chatcops/core@0.5.0 diff --git a/packages/store-cloudflare/package.json b/packages/store-cloudflare/package.json index 8ebb3bbf..559f3f19 100644 --- a/packages/store-cloudflare/package.json +++ b/packages/store-cloudflare/package.json @@ -1,6 +1,6 @@ { "name": "@chatcops/store-cloudflare", - "version": "0.0.0", + "version": "0.1.0", "private": true, "description": "Cloudflare D1, KV and Durable Objects storage for ChatCops", "keywords": [ diff --git a/packages/store-postgres/CHANGELOG.md b/packages/store-postgres/CHANGELOG.md new file mode 100644 index 00000000..95dc25fb --- /dev/null +++ b/packages/store-postgres/CHANGELOG.md @@ -0,0 +1,39 @@ +# @chatcops/store-postgres + +## 0.1.0 + +### Minor Changes + +- bebf4ba: `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. +- ea66add: New package: Postgres storage for conversations, counters, keys and analytics events. Works with `pg`, Neon (`fromNeon`) and postgres.js (`fromPostgresJs`), since every write is a single statement. Create the tables with `npx chatcops-postgres migrate` or `migrate(db)`. + +### Patch Changes + +- Updated dependencies [79fea7e] +- Updated dependencies [955809f] +- Updated dependencies [b138ded] +- Updated dependencies [e1e5e79] +- Updated dependencies [c2963af] +- Updated dependencies [0d666f2] +- Updated dependencies [c34a8d4] +- Updated dependencies [d320b44] +- Updated dependencies [1ca06e1] +- Updated dependencies [47ed5f1] +- Updated dependencies [aa3bb33] +- Updated dependencies [e8795ca] +- Updated dependencies [fd80586] +- Updated dependencies [049c52c] +- Updated dependencies [0578d5c] +- Updated dependencies [8e3e79e] +- Updated dependencies [1c76cf8] +- Updated dependencies [639a50c] +- Updated dependencies [6d80bc9] +- Updated dependencies [238eaef] +- Updated dependencies [332e588] +- Updated dependencies [be2ad74] +- Updated dependencies [ad1d9db] +- Updated dependencies [96372c8] +- Updated dependencies [f930e9c] +- Updated dependencies [3dd7dc8] +- Updated dependencies [545963f] + - @chatcops/core@0.5.0 diff --git a/packages/store-postgres/package.json b/packages/store-postgres/package.json index 1043944c..b1e797cc 100644 --- a/packages/store-postgres/package.json +++ b/packages/store-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@chatcops/store-postgres", - "version": "0.0.0", + "version": "0.1.0", "private": true, "description": "Postgres storage for ChatCops: conversations, counters, keys and analytics events", "keywords": [ diff --git a/packages/store-redis/CHANGELOG.md b/packages/store-redis/CHANGELOG.md new file mode 100644 index 00000000..daf8c221 --- /dev/null +++ b/packages/store-redis/CHANGELOG.md @@ -0,0 +1,44 @@ +# @chatcops/store-redis + +## 0.1.0 + +### Minor Changes + +- 5bd99f4: 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. + +### Patch Changes + +- Updated dependencies [79fea7e] +- Updated dependencies [955809f] +- Updated dependencies [b138ded] +- Updated dependencies [e1e5e79] +- Updated dependencies [c2963af] +- Updated dependencies [0d666f2] +- Updated dependencies [c34a8d4] +- Updated dependencies [d320b44] +- Updated dependencies [1ca06e1] +- Updated dependencies [47ed5f1] +- Updated dependencies [aa3bb33] +- Updated dependencies [e8795ca] +- Updated dependencies [fd80586] +- Updated dependencies [049c52c] +- Updated dependencies [0578d5c] +- Updated dependencies [8e3e79e] +- Updated dependencies [1c76cf8] +- Updated dependencies [639a50c] +- Updated dependencies [6d80bc9] +- Updated dependencies [238eaef] +- Updated dependencies [332e588] +- Updated dependencies [be2ad74] +- Updated dependencies [ad1d9db] +- Updated dependencies [96372c8] +- Updated dependencies [f930e9c] +- Updated dependencies [3dd7dc8] +- Updated dependencies [545963f] + - @chatcops/core@0.5.0 diff --git a/packages/store-redis/package.json b/packages/store-redis/package.json index 979e4dff..d0659ae4 100644 --- a/packages/store-redis/package.json +++ b/packages/store-redis/package.json @@ -1,6 +1,6 @@ { "name": "@chatcops/store-redis", - "version": "0.0.0", + "version": "0.1.0", "private": true, "description": "Redis and Upstash storage for ChatCops", "keywords": [ diff --git a/packages/store-sqlite/CHANGELOG.md b/packages/store-sqlite/CHANGELOG.md new file mode 100644 index 00000000..89178526 --- /dev/null +++ b/packages/store-sqlite/CHANGELOG.md @@ -0,0 +1,39 @@ +# @chatcops/store-sqlite + +## 0.1.0 + +### Minor Changes + +- 36b03b9: Add SQLite storage for conversations, counters, key-value data, and analytics events. +- 74f4c63: `extraColumns` and `rowHook` stamp columns you've added (a tenant or site id) on every new conversation and message row, as in `@chatcops/store-postgres`. The hook runs on insert only, undeclared keys throw, and values are always bound as parameters. + +### Patch Changes + +- Updated dependencies [79fea7e] +- Updated dependencies [955809f] +- Updated dependencies [b138ded] +- Updated dependencies [e1e5e79] +- Updated dependencies [c2963af] +- Updated dependencies [0d666f2] +- Updated dependencies [c34a8d4] +- Updated dependencies [d320b44] +- Updated dependencies [1ca06e1] +- Updated dependencies [47ed5f1] +- Updated dependencies [aa3bb33] +- Updated dependencies [e8795ca] +- Updated dependencies [fd80586] +- Updated dependencies [049c52c] +- Updated dependencies [0578d5c] +- Updated dependencies [8e3e79e] +- Updated dependencies [1c76cf8] +- Updated dependencies [639a50c] +- Updated dependencies [6d80bc9] +- Updated dependencies [238eaef] +- Updated dependencies [332e588] +- Updated dependencies [be2ad74] +- Updated dependencies [ad1d9db] +- Updated dependencies [96372c8] +- Updated dependencies [f930e9c] +- Updated dependencies [3dd7dc8] +- Updated dependencies [545963f] + - @chatcops/core@0.5.0 diff --git a/packages/store-sqlite/package.json b/packages/store-sqlite/package.json index 8045bee0..dc4bc14d 100644 --- a/packages/store-sqlite/package.json +++ b/packages/store-sqlite/package.json @@ -1,16 +1,27 @@ { "name": "@chatcops/store-sqlite", - "version": "0.0.0", + "version": "0.1.0", "private": true, "description": "SQLite storage for ChatCops single-server deployments", - "keywords": ["chatcops", "storage", "sqlite"], + "keywords": [ + "chatcops", + "storage", + "sqlite" + ], "homepage": "https://chat.codercops.com", "bugs": "https://github.com/codercops/chatcops/issues", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", - "exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } }, - "files": ["dist"], + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], "sideEffects": false, "scripts": { "build": "tsc", @@ -19,15 +30,25 @@ "test:watch": "vitest", "clean": "rm -rf dist *.tsbuildinfo" }, - "peerDependencies": { "@chatcops/core": "workspace:^" }, + "peerDependencies": { + "@chatcops/core": "workspace:^" + }, "devDependencies": { "@chatcops/core": "workspace:*", "@types/node": "^22", "typescript": "^5.7.0", "vitest": "^4.1.11" }, - "repository": { "type": "git", "url": "git+https://github.com/codercops/chatcops.git", "directory": "packages/store-sqlite" }, - "publishConfig": { "access": "public" }, + "repository": { + "type": "git", + "url": "git+https://github.com/codercops/chatcops.git", + "directory": "packages/store-sqlite" + }, + "publishConfig": { + "access": "public" + }, "license": "MIT", - "engines": { "node": ">=22.13" } + "engines": { + "node": ">=22.13" + } } diff --git a/packages/widget/CHANGELOG.md b/packages/widget/CHANGELOG.md index 2c42ca12..525df650 100644 --- a/packages/widget/CHANGELOG.md +++ b/packages/widget/CHANGELOG.md @@ -1,5 +1,24 @@ # @chatcops/widget +## 0.6.0 + +### Minor Changes + +- 545963f: Cloudflare Turnstile when a session is issued. With `turnstile: { secretKey }` on the server, `/session` checks a Turnstile token with Cloudflare before signing (401 `turnstile_failed` otherwise, 503 when Cloudflare can't be reached), and a refresh with the previous token skips it. The widget's `turnstileSiteKey` (`data-turnstile-site-key`) loads Cloudflare's script only when it first needs a session and renders the challenge in the page, outside the shadow root. +- 5b31899: Clearer messages when a reply can't happen. The widget now reads the server's reason and says it: too many messages, with the wait in seconds when the server sends one; the conversation or the day's budget used up; the assistant unavailable (the provider couldn't be set up); or the reply cut off. Each one is a new `strings` key (`errorRateLimitWait`, `errorBudget`, `errorBudgetDaily`, `errorUnavailable`, `errorCutOff`). The script tag bundle's size budget goes from 14 kB to 14.5 kB (18 kB for ESM) to fit them. +- 79a2253: Session tokens. When the server has them on and a chat request gets a 401, the widget gets a token from `/session`, keeps it in `localStorage`, and sends the request again with `Authorization: Bearer`. The saved token lets the server resume the conversation after a reload, and the widget moves to a new conversation id when the server gives one. Servers without session tokens see no change. +- 4aa886d: The widget asks for protocol v1 (`protocol: 1` in the request body) and reads its typed parts. Servers before 0.6 ignore the field and answer in the old format, which the widget still reads. With a v1 server, replies keep the id the server saved them under, so regenerate names the exact reply to replace, and a stream that ends without its `finish` part shows "cut off" with Retry instead of passing as a finished answer. Unknown part types are ignored. + +### Patch Changes + +- e13dfc9: 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. +- 8e3e79e: 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. +- f9e13f1: 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. +- 5ad6b1a: A reply whose connection is cut before the server's final `[DONE]` (a proxy timeout or a server crash, for example) now shows a network error and a Retry button under the text received so far. Before, it looked like a finished answer. +- 43d3d57: The script tag bundle is 37 bytes smaller (gzipped): the inline icons no longer repeat an `xmlns` attribute, and the stylesheet drops the default `ease` keyword from its transitions. The widget looks and behaves the same. +- 785a00a: Validate stored conversations before restoring them. Invalid top-level structures and malformed JSON are cleared from storage, and messages without a valid user or assistant role and string content are filtered out. +- 2104c3b: Remove the `require` export condition. It pointed at the script tag bundle, which never sets `module.exports`, so `require('@chatcops/widget')` returned an empty object. CommonJS code now gets a clear `ERR_PACKAGE_PATH_NOT_EXPORTED` error; use `import` instead. The `main` field and the `dist/chatcops.min.js` file that the CDN snippet loads are unchanged. The build also always writes the type declarations now, even after an earlier local build. + ## 0.5.0 ### Minor Changes diff --git a/packages/widget/package.json b/packages/widget/package.json index 21ecd855..0d3d8ad7 100644 --- a/packages/widget/package.json +++ b/packages/widget/package.json @@ -1,6 +1,6 @@ { "name": "@chatcops/widget", - "version": "0.5.0", + "version": "0.6.0", "description": "Embeddable AI chatbot widget - zero dependencies, Shadow DOM isolated", "keywords": [ "chatcops", diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index 8275c38b..fc5595cc 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -1,5 +1,58 @@ # @chatcops/website +## 0.0.5 + +### Patch Changes + +- Updated dependencies [e13dfc9] +- Updated dependencies [79fea7e] +- Updated dependencies [f7fb16a] +- Updated dependencies [955809f] +- Updated dependencies [b138ded] +- Updated dependencies [e1e5e79] +- Updated dependencies [c2963af] +- Updated dependencies [0d666f2] +- Updated dependencies [c34a8d4] +- Updated dependencies [d320b44] +- Updated dependencies [1ca06e1] +- Updated dependencies [5cf6116] +- Updated dependencies [47ed5f1] +- Updated dependencies [aa3bb33] +- Updated dependencies [e8795ca] +- Updated dependencies [fd80586] +- Updated dependencies [049c52c] +- Updated dependencies [0578d5c] +- Updated dependencies [8e3e79e] +- Updated dependencies [f9e13f1] +- Updated dependencies [1c76cf8] +- Updated dependencies [639a50c] +- Updated dependencies [6d80bc9] +- Updated dependencies [ad584a4] +- Updated dependencies [238eaef] +- Updated dependencies [332e588] +- Updated dependencies [be2ad74] +- Updated dependencies [be6ef6a] +- Updated dependencies [7c27f8c] +- Updated dependencies [a01dd3e] +- Updated dependencies [8c582e4] +- Updated dependencies [ad1d9db] +- Updated dependencies [96372c8] +- Updated dependencies [f930e9c] +- Updated dependencies [5ad6b1a] +- Updated dependencies [3dd7dc8] +- Updated dependencies [43d3d57] +- Updated dependencies [da93824] +- Updated dependencies [545963f] +- Updated dependencies [785a00a] +- Updated dependencies [5b31899] +- Updated dependencies [2104c3b] +- Updated dependencies [79a2253] +- Updated dependencies [4aa886d] +- Updated dependencies [c892542] + - @chatcops/widget@0.6.0 + - @chatcops/server@0.6.0 + - @chatcops/core@0.5.0 + ## 0.0.4 ### Patch Changes diff --git a/website/package.json b/website/package.json index 111832b7..d0ed6157 100644 --- a/website/package.json +++ b/website/package.json @@ -1,6 +1,6 @@ { "name": "@chatcops/website", - "version": "0.0.4", + "version": "0.0.5", "private": true, "type": "module", "scripts": {