Bundle
dsh-web-search-glm
Zhipu GLM-backed search provider (native web_search via the Anthropic-compatible API) for the DeepSeek Harness web capability seam (ctx.web)
- Source
- Noemm
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 23 hours ago
Readme
# dsh-web-search-glm
Zhipu GLM-backed search provider for the dsh (`ctx.web`) seam. It executes the native `web_search_20250305` server tool through GLM's Anthropic-compatible endpoint and maps GLM's `web_search_prime` result blocks to normalized search sources.
## How it works
The outgoing request is shaped exactly like the official `@deepseek-ai/dsh-web-search-deepseek` provider's: `POST {baseURL}/messages` carrying the `web_search_20250305` server tool (bounded by `max_uses`), the `anthropic-version` header, `redirect: "error"`, and full `AbortSignal` support.
The difference is entirely in the response mapping. Instead of Anthropic-standard `web_search_tool_result` blocks, GLM returns its own pair of blocks:
- `server_tool_use` blocks named `web_search_prime`, paired with
- `tool_result` blocks whose `content` is a stringified Python-repr list of search results.
The provider pairs `server_tool_use` with `tool_result` by id (multiple searches in one response are merged), parses the content (`JSON.parse` first, then a small hand-written Python-repr tokenizer), maps each `{title, link, content}` entry to `{url, title, snippet}`, and dedupes by `url`.
If the response contains no usable structured search results, the search fails with `WEB_PROVIDER_ERROR` — there is no scraping or prose fallback, matching the official provider's semantics. On non-2xx responses the server-side error message is passed through verbatim (still `WEB_PROVIDER_ERROR`). Other codes: `WEB_PROVIDER_CREDENTIAL_MISSING` when no API key resolves, `WEB_ABORTED` on cancellation.
Observed endpoint behavior (live test, 2026-08-27): GLM does not reject nonsense queries — it still executes the web search and returns ~10 sources, so the "no results" branch is rarely taken; this is endpoint behavior, not something the provider configures. Searches on this endpoint were billed under the GLM Coding Plan, like chat traffic.
## Install
From the dsh plugin marketplace (once the package is listed):
```bash
dsh plugin --profile <profile> add dsh-web-search-glm
```
Or from a local checkout:
```bash
dsh plugin --profile <profile> add /path/to/dsh-web-search-glm
```
**Either way, you must explicitly select the provider.** `dsh-web` reads its selection from the `web` row's config — the `dsh-base` bundle pins `deepseek-official`, and a `web:` section in `~/.dsh/settings.yaml` has no effect there. Override it in your profile's own patch layer, `~/.dsh/profiles/<profile>/cordis.patch.yml` (applied after every bundle layer):
```yaml
# ~/.dsh/profiles/web/cordis.patch.yml
- id: web
config:
searchProvider: glm
```
A patch replaces the targeted row's whole `config` — restate every key the base row owns (today just `searchProvider`).
Exporting `DSH_WEB_SEARCH_PROVIDER=glm` in the launching environment works too.
Without this, if the official `@deepseek-ai/dsh-web-search-deepseek` provider is also installed and available, more than one search provider is simultaneously `available()` and dsh-web throws `WEB_PROVIDER_AMBIGUOUS` rather than guessing.
## Configuration
Settings live in the `web-search-glm` section of `~/.dsh/settings.yaml`:
| Key | Default | Description |
| --- | --- | --- |
| `apiKey` | — | Literal API key (secret). Prefer the `apiKeyEnv` credential reference. |
| `apiKeyEnv` | `ZAI_API_KEY` | Credential reference name. Keep it in sync with the `apiKeyEnv` of the `zai` provider in your dsh settings so both read one stored key. |
| `baseURL` | `https://open.bigmodel.cn/api/anthropic/v1` | Anthropic-compatible endpoint; `/messages` is appended by the provider. The overseas deployment can point to `https://api.z.ai/api/anthropic/v1`. |
| `model` | `glm-5.3` | Model that executes the native web search. Verified live — see [Model notes](#model-notes). |
| `apiVersion` | `2023-06-01` | Value of the `anthropic-version` header. |
| `maxTokens` | `4096` | Upper bound on generated tokens for the Messages request. |
| `maxUses` | `5` | Maximum `web_search` server-tool uses per request. |
Example:
```yaml
# ~/.dsh/settings.yaml — plugin options (provider selection lives in the
# profile patch, see Install):
web-search-glm:
apiKeyEnv: ZAI_API_KEY
# baseURL: https://api.z.ai/api/anthropic/v1 # overseas endpoint
# model: glm-5.3-flash # verified alternative, see Model notes
```
**Credential resolution chain.** The key is resolved per search, in order: a literal `apiKey` set in the `web-search-glm` section → the credentials service via the `apiKeyEnv` reference → a launch-environment variable of the same name. If nothing resolves, the search fails with `WEB_PROVIDER_CREDENTIAL_MISSING`.
**Endpoint environment fallback.** If the `web-search-glm` section does not set `baseURL`, the launching environment's `GLM_SEARCH_BASE_URL` is consulted before the built-in default. The variable is deliberately distinct from any chat-completions base-URL variable — search speaks the Anthropic-compatible Messages API under its own address (mirroring upstream `DEEPSEEK_SEARCH_BASE_URL`).
## Model notes
- `glm-5.3` (default) — verified live on 2026-08-27: english and chinese queries each returned 10 sources through the native web search.
- `glm-5.3-flash` — verified live on 2026-08-27: all four smoke cases (english / chinese / nonsense / bad-key) behaved identically to `glm-5.3`, with real searches executed (english and chinese each returned 10 sources). A working alternative — set `model: glm-5.3-flash` to use it.
- `glm-5-flash` — does not exist on this endpoint. Every request is rejected by the server with `[1214][modelCode:不存在]` (surfaced as `WEB_PROVIDER_ERROR`). Do not use this name.
The package default stays `glm-5.3`; it is not changed merely because the flash variant is available.
## Compatibility
- Targets the dsh plugin seam protocol of `dsh >= 0.1.1-rc.2` (peer dependencies `@deepseek-ai/dsh-*` `^0.1.1-rc.2`).
- MIT License — see [LICENSE](LICENSE).
## Development
- `npm test` — parser unit tests on the built-in `node:test` runner; no extra dev dependencies. Fixtures are real captured GLM responses under `test/fixtures/`.
- `npm run capture` — one-off live capture that refreshes `test/fixtures/` (`scripts/capture.mjs`). Needs `ZAI_API_KEY` (falls back to `ANTHROPIC_AUTH_TOKEN`).
- `npm run smoke` — live-endpoint smoke run of four cases (english / chinese / nonsense / bad-key): `node scripts/smoke.mjs [model]`. Needs `ZAI_API_KEY` (falls back to `ANTHROPIC_AUTH_TOKEN`).
The live scripts intentionally live in `scripts/`, not `test/` — Node 26's `node --test` auto-discovery would execute them during `npm test` — and they are not shipped in the published npm package.
Install
dsh plugin --profile web add github:Noemm/dsh-web-search-glm
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-web-search-glm from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.