Pitfalls (must stay in code)

August 24, 2026 · View on GitHub

Ported from adpworker CONTRIBUTING-ADP.md and docs/ADP-接口盘点与需求.md. Each item names the fixture that pins it.

Install

  • dsh plugin add records the package.json name (@tencentcloudadp/dsh-adp), not the folder name, and injects cordis.patch.yml as one profile bundle layer. Do not also pass --patch ./cordis.patch.yml, and do not copy those rows into the profile's cordis.patch.yml — either duplicates loader id adp-core and dsh web fails.
  • If this checkout was already linked under another name (the folder alias adp-dsh-plugin is the usual leftover), remove the old name, then add once: dsh plugin --profile web remove adp-dsh-plugin.
  • ~/.dsh/profiles/web/package.json must list @tencentcloudadp/dsh-adp once under both dependencies and dsh.profile.bundles. A profile cordis.patch.yml row that still says name: adp-dsh-plugin (even under a leftover id such as mcp-tencent-cloud-docs) is not this bundle — delete it; it re-registers ctx.adp.
  • Git installs fetch source and run prepare (tsdown). pnpm ≥10 blocks that until the package is listed under allowBuilds in the profile's pnpm-workspace.yaml. A pnpm pack tarball is already compiled and does not need that allowance.
  • After editing this checkout, run pnpm run prepare (or pnpm test) and restart dsh web; the profile link loads lib/, not src/.
  1. Plugins are MCP and API. Only wiring MCP drops half the marketplace. API tools live at ToolConfig.ApiToolConfig.ExternalApiUrl. Fixture: tests/fixtures/control/plugin-detail-api.json.

  2. MCPTransport 0 = SSE, 1 = streamable-http. POST to an SSE endpoint is 405. Never silently retry the other transport. Fixture / sim: sim-plugin-mcp-405.

  3. Availability is DescribePlugin only. Catalogue AllowExternalAccess is false even for working plugins. Fixture: tests/fixtures/control/plugin-summary-page0.json (false) vs plugin-detail-usable.json (URL present). Sim: sim-plugin-filter.

  4. API responses may be cumulative SSE. Accept: application/json is ignored. Last Data.Answer wins; concatenating repeats the text. Fixture: tests/fixtures/plugin/api-cumulative.sse, search-cumulative.sse.

  5. Plugin catalogue PageNumber is 0-based. Sending 1 as the first page looks like “no results” next to a non-zero TotalCount. Fixture: page0 + page1 JSON. Sim: sim-plugin-list.

  6. Account envelope is reqId/code/data, not PascalCase. Fixture: tests/fixtures/account/lowercase.json. OneID login-url is proxied at /adp/account/login-url for the settings card; it does not fill the three keys. Fixture: tests/fixtures/account/login-url.json. Do not export default AdpService from the package entry: Loader.unwrapExports would load the class and skip apply, so the proxy never registers and dsh web answers POST with 405 empty. Sim: sim-export.

  7. MCP idle ~600s, no ping. Reconnect on idle. Fast fail (<3s, request likely never arrived) may retry; a call that ran for a while must not auto-retry (may already be billed).

  8. Generation COS links expire ~24h. Harvest into the workspace; canonical value carries saved_files. Sim: sim-media.

  9. Three keys do not substitute. SSE chat still needs AppKey. Sim: sim-cred-missing, sim-appkey-absent.

  10. Skill detail examples often omit SkillUrl / SkillMarkdownUrl. Empty URL → list only, never use Profile.Description as the body. Fixtures: skill-detail-empty.json, skill-detail-md.json. Sim: sim-skill-empty-url, sim-skill-md.

  11. DescribeApp.SecretInfo needs FieldMask.Paths=["SecretInfo"]. Without it, null. May also be empty before release finishes. Fixtures: describe-app-no-mask.json, describe-app-with-mask.json. Sim: sim-appkey-mask.

  12. Agent/App edits do not take effect until CreateRelease. Release is async (Status 3 success / 4 fail). Poll via ctx.jobs (or inside the provision tool), never ask the model to sleep. Sim: sim-provision.

  13. Claw models are ModelScene=18, not Agent thinking 3.

  14. adp-core is a function plugin (name / inject / apply), same as llm-adp. A class default export is eaten by the loader. Sim: sim-export.

  15. 独立站 / 公有云 switches control + SSE, not the model gateway. Completions stay on api.adp.cloud.tencent.com and use the site's Tool Key (ADP_API_KEY). SecretId/SecretKey is a separate credential pair. Sim: adp site proxy.

  16. Site settings wait for adp, not only settings. ctx.inject(['settings']) can run before AdpService provides adp. Then registerSiteSettings no-ops, settings.update('adp-core') throws, and dsh-host-webserver answers POST /adp/site with an empty 400. Inject ['settings', 'adp']. Sim: sim-site-settings.

  17. Public-cloud DescribeSkillSummaryList rejects FilterList.Perspective. Official body is { SpaceId, PageNumber, PageSize }. Perspective defaults to USER and is only legal with custom ProviderType. DescribeSpaceList has no PageNumber. Live: tests/live/cloud-e2e.test.ts.

  18. Model gateway path is /chat/completions, not /v1/chat/completions. Official ADP docs only cover CAM control + AppKey SSE (wss.lke…/adp/v2/chat). The OpenAI-shaped host is api.adp.cloud.tencent.com with no /v1 prefix (adpworker OpenAIProvider base_url). /v1/chat/completions returns 401 not_authorized for a key that 403s AccountOverdueError on the unprefixed path. Live: tests/live/cloud-e2e.test.ts.

  19. Public-cloud SpaceId is a real workspace, not default_space. cordis.patch.yml still defaults spaceId: default_space. Cloud AKSK injects that value into space-scoped list/create calls, and DescribeAppSummaryList / DescribePluginSummaryList then return 4510004 空间信息不存在 if it is wrong. Pick a workspace on the Settings card (stored as adp-core.spaceId). Completions do not use SpaceId; apps and plugins do. Do not send SpaceId on AppId-scoped actions (DescribeApp, DeleteApp, CreateAgent, DescribeAgentSummaryList, CreateRelease, DescribeReleaseSummary, …) — they return UnknownParameter. Sim: sim-spaceid-scope. Live: tests/live/cloud-e2e.test.ts.

  20. DescribeModelList ids live under ModelBasic. Public-cloud and independent-site DescribeModelList rows are { ModelBasic: { ModelId, ModelName, … } }. Reading only top-level ModelId yields an empty list and listModels silently returns builtins. Independent-site ListModel uses ModelName (no ModelId). Fixture: nested rows in tests/unit/sim.test.ts normalizeModelList. Live: tests/live/cloud-e2e.test.ts, tests/live/standalone-e2e.test.ts.

  21. Independent-site completions use the same cloud gateway. There is no api.adp.tencent.com. A 401 AuthenticationError(凭证无效或已过期)on POST /chat/completions means that API key is dead; 401 not_authorized is the /v1 path bug. A valid gateway ADP_API_KEY completes independent-site catalog ids (Deepseek/deepseek-v4-flash, Hunyuan/hy3, …) on api.adp.cloud.tencent.com. Live: tests/live/standalone-e2e.test.ts.

  22. CreateAgent takes nested Agent, not top-level Name. Official body is { AppId, Kind, Agent: { Profile: { Name, Role }, Instructions, Model: { ModelId }, PluginList: [{ PluginId }], SkillList: [{ SkillId }] } }. A flat Name returns UnknownParameter. Claw models come from DescribeModelList ModelScene=18. Sim: sim-provision. Live: tests/live/cloud-e2e.test.ts.

  23. DescribeReleaseSummary requires ReleaseId. Official body is { AppId, ReleaseId }. CreateRelease returns ReleaseId; polling with only AppId is MissingParameter. Status is ReleaseSummary.Status (3 published, 4 failed). Sim: sim-provision. Live: tests/live/cloud-e2e.test.ts.

  24. adp_call payload must be an object. Models often stringify type: json args. A string body used to become {} and then MissingParameter. Parse JSON strings; keep objects. Sim: sim-adp-call-json-string.

  25. App lists paginate with PageNumber / PageSize, 0-based. Offset / Limit are ignored. TotalCount of hundreds with 15 rows means the first page only. Same for DescribePluginSummaryList (pitfall 5). DescribeAgentSummaryList is AppId-scoped and rejects SpaceId.

  26. adp_ask_<slug> must use the same slug provision advertises. kebab("Claw Demo 应用") is claw-demo, but slugAscii used to return '' on any CJK character and agentToolName hashed, so provision told the model to call adp_ask_claw-demo while registerAskTool registered adp_ask_a…. The executor then returned unknown tool "adp_ask_claw-demo". Both sides now use agentToolName / askSlug. The provision result also returns ask: { tool: "adp_ask", appKeyEnv } because DSH snapshots the tool list for the current turn. Sim: sim-ask-name, tests/unit/llm-loop.test.ts.

  27. Agent-loop wire format follows llm-deepseek, not a looser OpenAI subset. ADP's gateway hosts Hunyuan, DeepSeek V4, GLM, Kimi, MiniMax. Tool-call turns must send content: "" (never null), pass back reasoning_content when thinking ran, keep user text plus role: tool results, and never let continuation SSE deltas with id/name "" or null wipe the first identity (community unknown tool "" on DSH 0.1.0-rc.7). Object-shaped function.arguments is JSON.stringified. Sim: tests/unit/llm-loop.test.ts, sim-llm-sse.

  28. adp_call needs a per-action contract. Raw OpenAPI passthrough made the model guess: DescribeModelList without ModelScene, DescribeAgentDetail without AppId, CreateRelease with AgentId, GetAppSecret.AppId instead of AppBizId, top-level ModifyAgent.SkillList, ChatCompletions as a control action (InvalidAction). adp_list_actions now ships required / hint / example; adp_call remaps/nests/drops known mistakes and refuses dead actions before the signed request. CreateRelease 450027 is recovered via DescribeLatestRelease. Sim: sim-adp-call-contract, sim-release-already.

  29. DescribeSkillCategoryList rejects SpaceId. Cloud AKSK auto-inject must not add it (same class as AppId-scoped actions). Sim: sim-spaceid-scope.