Repository navigation
v0.16.0
[0.16.0] - 2026-10-20
What's New
withContext({ reporter, workload, template }, fn)declares which workload/template the LLM calls captured insidefnbelong to. The declaration is sent asllm_request_log.declared, so the server skips log-to-template matching. It usesAsyncLocalStorage(loaded lazily, so Edge runtimes still load), nests with the innermost value winning per field andworkload/templatereplaced wholesale, and a workload without a template does not inherit the outer template. It is snapshotted synchronously when a call is intercepted (http,httpsandfetch, on both the auto-monitor andCoolhandpaths), so logs sent later from a background context keep it, and it never passes through body redaction. Keys and descriptions are validated client-side and throw before your callback runs. Serverwarningson the log response (for example a declaration ignored because the public-key workload/template cap was reached) are now logged, never thrown. Older servers ignore the unknowndeclaredkey. Seedocs/declared-workloads.md. (#279, #285)Coolhand#upsertWorkload/upsertTemplate(alsoWorkloadService#upsert/TemplateService#upsert, private API key) create or rename a workload or template by key viaPUT .../by_key/*key. They send only the fields given, flatten a template'sworkloadtoworkload_id/workload_key, percent-encode each key segment with/kept literal, and return{ workload | template, created }(201 vs 200). (#279, #285)- Client-file admin methods:
Coolhand#listClientFiles,downloadClientFileandupdateClientFileStatus(also onClientFileService), next to the existinguploadClientFile.listClientFiles({ status?, fileType?, page?, per? })returns{ files, pagination }with pagination read from theX-Page/X-Per-Page/X-Total-Count/X-Total-Pagesheaders;downloadClientFile(id)returns{ content: Buffer, contentType, filename }as raw bytes;updateClientFileStatus(id, status)setsdraft,publishedorarchived(returnsnullindryRun). All three require a client admin's per-user API key and throwHttpError(with.status) on a non-2xx response, unlikeuploadClientFile, which resolves tonull. Needs a backend carrying Coolhand-Labs/coolhand#1762. Seedocs/client-file-upload.md. (#278, #280)
Upgrade Notes
- Bare
openai.comis no longer an OpenAI domain. It matched every*.openai.comhost (auth, platform, www), so TLS-intercepting proxies hit the apex domain and OAuth token exchanges onauth.openai.comwere captured.api.openai.comand its subdomains still match. If you relied on anotheropenai.comhost being logged, add it through a custompatternsFile. (#73, #281) excludeApiPatternsentries are plain substrings, not globs, and the SDK now warns when an entry contains*. Matching behavior is unchanged:'/internal-*'has always matched only URLs that literally contain/internal-*. Non-string entries (e.g. aRegExpfrom untyped JavaScript) are now ignored instead of throwing aTypeErrorinside the patched request path. See the newdocs/exclude-patterns.md. (#231, #283)
Security
-
Generic credential keys are now redacted in JSON and form bodies. String values under
api_key,password,access_token,refresh_token,id_token,secret,private_key,session_tokenand similar names (matched case- and_/--insensitively, including bracketed names such asuser[password]) are replaced with[REDACTED]anywhere in a JSON orFormDatabody, andapplication/x-www-form-urlencodedbodies get the same names plus baretoken,assertionandclient_assertion. Object and non-string values (tool-schema properties namedpassword) are left alone, andpwdis deliberately not redacted in bodies. Known gaps are documented indocs/global-monitoring.md. (#276, #284) -
More credential names are redacted. URL query strings now also redact
oauth_token,passwdandsecret_access_key, and JSON,FormDataand form-urlencoded bodies now also redact string values under the env-var style namesGOOGLE_API_KEY,ANTHROPIC_AUTH_TOKEN,GITHUB_TOKENandapiSecret(the same case- and separator-insensitive matching as the existing names).
Bug Fixes
- Capturing a stream-bodied
Requestno longer lets the host's branch queue the whole upload.fetch(new Request(url, { body: stream }))capture now cancels its own branch of theclone()as soon as the 50 MiB cap is exceeded, so queued memory is bounded at about the cap plus one chunk and an endless stream no longer grows memory without bound.CappedBufferalso ignores empty chunks, which removes a false "truncated" flag when a stream emits a zero-length chunk after exactly filling the cap (affects thehttp/httpspaths too). Backpressure is still not restored for the first ~50 MiB of a stream upload. (#277, #282)
Documentation
- New
docs/exclude-patterns.mdcovering howexcludeApiPatternsmatches (substrings of the full URL, case-sensitive), the built-in defaults, and how to avoid over-matching; the README option andCOOLHAND_EXCLUDE_API_PATTERNSrows now state the correct default and semantics. (#231, #283)
Maintenance
BaseService#putByKeyis the shared write path for the workload and template upserts,fetchWithHeadersnow also returns the HTTP status, andutils/entity-key.tsholds the key/description rules. (#285)BaseService#fetchBytes(raw-bytes downloads) shares a new privatefetchResponsewithfetchWithHeaders;fetchWithHeadersbehavior is unchanged. (#280)- Removed a stray
node_modulessymlink that was committed tomainby mistake in #285. It is not part of the published package. - GitHub Actions bumps:
actions/setup-node7.0.0 → 7.1.0,actions/download-artifact8.0.1 → 8.0.2,actions/upload-artifact7.0.1 → 7.0.2. (#275, #274, #273)