Contributing
Thanks for considering a contribution. This is a Go CLI + client library for the Z.AI (Zhipu AI) API. Start with docs/en/architecture.md if you want the package layout and design rationale before diving in, and docs/en/roadmap.md for known gaps that are good first contributions. A few things that make this repo different from a typical Go project:
The live-verification convention
Z.AI's official docs (docs.z.ai / docs.bigmodel.cn) and SDKs sometimes
disagree with each other, or with what the live API actually returns. Rather
than trust documentation alone, this project verifies request/response shapes
against real API calls and records the interaction as a
go-vcr cassette under
pkg/client/testdata/cassettes/, replayed in ModeReplayOnly so tests never
hit the network.
If you're adding a new endpoint or changing a request/response type:
- Prefer a cassette-backed test over a hand-written fixture when you can record one — it proves the types parse what the server actually sends.
- Name cassettes after what they call (
agents_invoke.yaml), not when/why they were recorded. - Redact credentials before committing a cassette. Never commit a real
Authorizationheader, API key, or other account-identifying data. Checkgrep -n "Bearer " your_cassette.yamland confirm it readsBearer REDACTEDbefore opening a PR. - If you can't record a live cassette (no account, insufficient balance,
etc.), say so in the PR — a
NOT VERIFIED LIVEdoc comment on the type is fine and expected; see existing examples inpkg/client/agents.go.
Before opening a PR
go build ./...
go vet ./...
gofmt -l . # must be empty
go test -race ./...
golangci-lint run ./...
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
All of the above run in CI; a green PR is a merged PR.
golangci-lint config lives in .golangci.yml;
it runs the default linter set (errcheck, govet, ineffassign, staticcheck,
unused) — the pass that has caught unused dead code and short-read test bugs
here before. Read (io.Reader.Read / r.Body.Read) stays checked on purpose;
drain a request body with io.ReadAll, never a single Read into a
ContentLength-sized buffer.
Code conventions
- Every service method takes
context.Contextand propagates it through to the HTTP call — no exceptions for "it's just a quick helper." - Required fields are validated before a request is built, not left to the API's error response.
- Don't add a new
http.Clientanywhere — every request goes throughClient.doRequest/doRequestBase/sendMultipartso retry, timeout, and error parsing stay centralized. - Keep exported contracts stable; if a change is breaking, call it out explicitly in the PR description.
Reporting a security issue
See SECURITY.md — please don't open a public issue for a vulnerability.
Documentation and translations
Docs live under docs/<lang>/ (en, ru, zh). English (docs/en/) is
the source of truth — write your change there first. The other locales are
translations that intentionally may lag behind; don't block a PR on them, but
do add a ## Translation debt line to your PR description listing which
docs/en/*.md you touched, so the next translation pass picks them up.
When updating docs/en/, also update the corresponding translated file under
docs/ru/ and docs/zh/ if your change is small (a one-line correction, a
new flag in a table). Larger translations are tracked as follow-up work.
Static site
The markdown under docs/ is the source of truth; the previously bundled
static-site generator has been extracted into a standalone project and is no
longer built or deployed from this repo. Render the markdown with whatever
static-site generator you prefer.