Skip to content

feat: add io.github.yahooyuan666.zcode2pi — ZCode/BigModel Coding Plan free quota as a PI-Desktop model provider - #64

Closed
YahooYuan666 wants to merge 1 commit into
vastsa:mainfrom
YahooYuan666:feat/zcode2pi-marketplace
Closed

YahooYuan666 wants to merge 1 commit into
vastsa:mainfrom
YahooYuan666:feat/zcode2pi-marketplace

Conversation

@YahooYuan666

Copy link
Copy Markdown
Contributor

Summary

Adds io.github.yahooyuan666.zcode2pi — a model provider that exposes the ZCode / BigModel Coding Plan free quota (GLM-5.3-Flash) as a row in PI-Desktop's model picker — plus the packed 0.1.0 artifact and the catalog entry.

Model: GLM-5.3-Flash, 1M context, vision.

What and why

ZCode's client already talks to BigModel's Coding Plan, but PI-Desktop has no way to reach that quota: the keys live encrypted inside ZCode's credential store and there is no API-key path exposed. This plugin closes the gap by declaring a provider that PI-Desktop can authenticate itself, pointed at BigModel's Coding Plan endpoint.

Design decisions

The endpoint is not arbitrary. The provider uses apiStyle: "chat_completions" against https://open.bigmodel.cn/api/coding/paas/v4. That is PI-Desktop's own first-class path for Zhipu Coding Plan — ADR 0155 defines the zhipuai-coding-plan preset with exactly that URL and apiStyle. Both ends therefore speak Chat Completions and the relay translates nothing; SSE is forwarded chunk by chunk.

I first built this against BigModel's Anthropic endpoint (api/anthropic/v1/messages) and it failed in a way worth recording: PI-Desktop sends OpenAI-shaped tools ({type, function}) and that endpoint rejects them with body.tools.0.name: Field required. The two endpoints are individually correct and both accept the same API key; they simply cannot be mixed. Anchoring to the documented preset is the fix.

The request body is forwarded byte for byte. stream is never forced, no placeholder tools are injected, tool_choice is never added. This is what keeps the plugin clear of the two failure modes reported against JasonYu0822/opencode2pi-desktop:

passthrough.test.mjs asserts this against a local echo upstream rather than taking it on faith.

The API key never reaches this plugin. authKind: "api_key" keeps the key in PI-Desktop's secret store and attaches it to the outgoing request; the relay forwards request headers unchanged and has no code path that reads, stores or logs a credential. There is no .env, no hard-coded key, and nothing to revoke here.

Capability matrix

This plugin requests background.service and performs outbound network access, so it is High risk under SECURITY.md (two maintainers + manual review + threat-model notes + explicit confirmation tests). Full accounting:

Capability Source entry What it does Data-flow destination Guard
Loopback listener startService in main.js Binds 127.0.0.1, tries 41940 → 41941 → 41942 — listen() only ever passes '127.0.0.1'; port fallback prevents a stale process hijacking the provider
Inbound request filter handleRequest Rejects any request whose Host is not 127.0.0.1 / localhost / ::1 with 403 — isLoopbackHost(); blocks DNS rebinding and other local ports
Outbound HTTPS handleForward, main.js:109 POST to the single hard-coded URL https://open.bigmodel.cn/api/coding/paas/v4/chat/completions open.bigmodel.cn only Constant is not configurable, not read from env, not from settings; no second destination exists in the code
Request body handleForward Forwarded byte for byte; never modified open.bigmodel.cn No rewriting of stream, tools or tool_choice; asserted by tests
Request headers handleForward Hop-by-hop headers stripped; accept-encoding: identity forced; everything else forwarded verbatim open.bigmodel.cn Header rewriting exists only to prevent a corrupted response body, never to alter content
API key none Never read, stored or logged by this plugin Host secret store → open.bigmodel.cn, via the host No code path accesses a credential; the audit's "credential/secret handling" signal fires on the word "API Key" in comments only
Log output describeShape, called only on upstream 4xx/5xx One line: model, stream flag, tool count, tools[0] field names, top-level field names Plugin log Never logs message content, tool arguments, or any header
Client disconnect abortUpstream Cancels the upstream read so a stopped generation does not keep billing — Guarded by streamDone; cancels the reader, not the locked body
Filesystem none Reads and writes nothing. No fs permission requested. — —
Clipboard / shell / remote code none None. No such permission requested. — —

Threat model

The plugin runs a local network listener, which is the capability worth being careful about.

  • Other local processes could call it. Mitigated by the loopback-only bind and the Host check; there is no separate auth token, because every request it can serve is a request PI-Desktop itself is authorized to make. A local attacker who can already read PI-Desktop's process memory can also read the key; this does not widen that.
  • A malicious web page could reach it. A page cannot bind to a raw TCP port with a chosen Host from a browser context, and the Host check rejects a cross-origin-shaped request. DNS rebinding is covered because a rebound name resolves to a loopback address but arrives with a non-loopback Host.
  • The upstream could be redirected. Not reachable through configuration — the URL is a constant. A compromised open.bigmodel.cn would see whatever the user already sends there directly.
  • Credential exfiltration. No code path reads a credential, so there is nothing to exfiltrate.

Negative-path tests

passthrough.test.mjs (12 checks, all passing) points the relay at a local echo upstream and covers the failure directions, not just the happy path:

Check Guards against
Non-loopback Host rejected with 403 DNS rebinding / cross-port access
Upstream sees stream: false unmodified Regression of opencode2pi #3 (forced streaming)
Request body byte-identical to what was sent Silent body rewriting
Non-streaming caller receives one JSON object, Content-Type: application/json SSE fragments leaking to non-streaming consumers
Non-streaming response contains no data: fragments Same
Omitted stream not injected as true Same, second shape
Streaming caller receives SSE with original chunk order intact Stream corruption from buffering
No placeholder tools injected Regression of opencode2pi #1 (free-lane gate)
No tool_choice injected Same
Earlier suite: client abort mid-stream leaves the process alive and the service healthy Crash-on-disconnect, and wasted quota

Run with:

cd plugins/io.github.yahooyuan666.zcode2pi && node passthrough.test.mjs

Gate results

  • python3 scripts/pack_plugin.py plugins/io.github.yahooyuan666.zcode2pi → ok, sha256 a779a30b…, 28465 bytes
  • python3 scripts/security_audit.py --check-packages → no BLOCKERs for this plugin; five REVIEW signals, each justified in the capability matrix above
  • python3 scripts/rebuild_catalog.py → catalog entry added, sha256 matches the artifact; no existing entry touched (45 insertions / 1 deletion, the deletion being the updatedAt timestamp)
  • Tested in PI-Desktop: installed from the packed artifact, granted both permissions, entered a key, GLM-5.3-Flash selected from the model picker and returned normal streamed completions

One note for maintainers, unrelated to this PR

scripts/security_audit.py --check-packages currently reports BLOCKER: packed bytes do not match the current plugin source for every package in the repository when run from a Windows checkout. The cause is that this repository has no .gitattributes, so with core.autocrlf=true the working tree is checked out as CRLF while the packages were built from LF sources. I verified this on pi.todo-0.6.5: the source has 415 CRLF endings and the packed main.js has 415 bare LF, so the two are not byte-identical.

The packages themselves are fine. Adding .gitattributes with * -text (as the distribution mirror AIUO-Net/pi-desktop-plugins already has) would make the audit meaningful on Windows. I did not add it here, since it is outside the scope of this submission.

This plugin avoids the trap: its sources and its packed artifact are both LF and byte-identical, verified against the pushed blobs rather than the working tree.

Credits

The provider-declaration + loopback-relay shape follows JasonYu0822/opencode2pi-desktop. The .piplug byte layout was worked out from PI-Desktop's packages/plugin-devkit/src/pack.ts and confirmed against existing packages in this repository.

License

MIT

… free quota)

Adds a model provider that exposes the ZCode / BigModel Coding Plan free quota
inside PI-Desktop, together with the packed 0.1.0 artifact and the catalog entry.

Design notes worth review:

- The provider uses apiStyle "chat_completions" against
  https://open.bigmodel.cn/api/coding/paas/v4. That is PI-Desktop's own
  first-class path for Zhipu Coding Plan (ADR 0155), so both ends speak Chat
  Completions and the relay translates nothing.
- The relay binds 127.0.0.1 only and rejects non-loopback Host headers.
- The request body is forwarded byte for byte. `stream` is never forced, no
  placeholder tools are injected and `tool_choice` is never added. That keeps
  this plugin clear of the two failure modes reported against
  JasonYu0822/opencode2pi-desktop#1 (free-lane gate) and vastsa#3 (non-streaming
  callers receiving SSE fragments), both of which stem from that plugin having
  to satisfy OpenCode's anonymous-channel gate. BigModel has no such gate.
- The API key never reaches this plugin: authKind "api_key" keeps it in the
  host secret store and the relay forwards headers unchanged.

Security review signals raised by scripts/security_audit.py (expected for this
capability set, all justified in the PR body):
  manifest.json  unknown permission requires host-policy review: provider.register
  main.js:109    network access
  main.js:8      credential/secret handling (comment text only)
  main.js:9      persistent activation/timer
  passthrough.test.mjs:59  network access (local echo server in the test)

No BLOCKERs. passthrough.test.mjs passes 12/12 against a local echo upstream.
@vercel

vercel Bot commented Oct 2, 2026

Copy link
Copy Markdown

@YahooYuan666 is attempting to deploy a commit to the vastsa's projects Team on Vercel.

A member of the Team first needs to authorize it.

@vastsa

vastsa commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Closing this as part of the migration that removed plugin sources from this repository.

Since commit 38284f1 the plugins/ tree is gone: this repository only ships the published catalog.json and the packages/*.piplug artifacts. Sources now live in each author's own repository, and every release goes through the plugin center, which packs the files, audits the source, records the SHA-256 and publishes the version.

Your work is not lost — your branch and fork stay exactly as they are. To ship io.github.yahooyuan666.zcode2pi:

  1. move plugins/io.github.yahooyuan666.zcode2pi/ into your own public repository (e.g. YahooYuan666/pi-desktop-zcode2pi), keeping manifest.json at the package root;
  2. tag the version you want reviewed (v<x.y.z>); the tag, or a commit SHA, becomes the sourceRef the review reads;
  3. sign in at https://plugins.aiuo.net → My Plugins → Create plugin: upload the package, bind that repository, submit — or let an agent run the publishing skill https://plugins.aiuo.net/skill.md over MCP https://plugins.aiuo.net/mcp;
  4. later versions are submitted from the plugin's own page.

One detail worth keeping: the center rebuilds the published manifest from what you submit, so send ui, contributes, activationEvents, fs and net in full — anything omitted is dropped and the installed plugin becomes invalid.

This repository still welcomes distribution-side contributions (a broken .piplug, a stale catalog entry, website or docs fixes); anything that adds plugins/<id>/ will be closed with this pointer. Thanks for the work on io.github.yahooyuan666.zcode2pi — the review gate is the same, it just runs against your own repository now.

@vastsa vastsa closed this Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants