Skip to content
dsh.fish
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

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source