dsh-payload: Inspect the actual request of every model call in DSH

Preface

The most common problem encountered when debugging agents in DSH is: what exactly did the model send out this round? A bunch of auto-injected information is stuffed into the context, tool calls keep getting appended back and forth, and after multiple steps, the request is drastically different from the user input. To troubleshoot “why the context grows so rapidly” or “who set this parameter,” one usually has to dig through event logs in the logs; the process is slow and prone to missing details.

Introducing a plugin, shengpj/dsh-payload. Its approach is to directly read the session log held by the client and offline reconstruct the complete request actually sent to the model for the call that generated a specific reply. It can be viewed in the interface in one step, without resending requests or changing the server. The plugin category is “Model Inference,” the directory page shows 1 GitHub star, maintainer is shengpj, current version 1.9.5 (from package.json in the repo), license MIT.

What is it

One-line definition: A pure client-side DSH Web GUI plugin that pairs a “View Request JSON” entry with every assistant reply. The dialog displays the model, call parameters, messages, tools, token decomposition, and context source proportions for that call.

The difference between this and “viewing logs” is: logs are an event stream, and you need to stitch them together yourself by looking for step/start and step/end. This plugin folds events into a request snapshot based on call boundaries and separates the newly added items this round from the history, directly answering “what was sent extra this time.”

Entry location: There is an extra { } icon button in the action row below every assistant reply (on the same row as “Copy” and “Good Answer”). Note that only finalized replies have this action row; the one currently being streamed out does not.

Core Features

Precise to a specific reply. The button for every assistant message points to the call that generated it, not the “last call of this round.” Even when there are forty calls in a round, you can still find the specific call corresponding to a middle reply.

Grouped by call, one entry. One step equals one call, one snapshot per call. Whether it is one call or forty, switching entries only involves the left step list, each row showing #k, tokens, and step n · m messages. The right side JSON takes up the remaining width. The UI shape does not change with the number of calls, so there is no “use cards when few, switch to list when many” dual mental model.

Current and history separated. In messages, messages newly generated this turn (the Turn this reply belongs to) are expanded by default, marked by a brand-colored vertical line; older history is collapsed into one line called “History Messages (n)”. The array title shows both Current n / History n counts. Collapsing is just a display layer; the indices remain the real indices in the array, so the copied JSON is identical.

Mark new messages in place. For the 2nd and subsequent calls in the same turn, messages that “appear for the first time” in messages are marked directly in the JSON: the left side switches to a green solid-line track, the row background is covered in green, and a “New” capsule is attached at the start. There is a dashed line separator before the new segment, labeled “Previous call had 1933 · Following is new 18 this round.” The logic uses prefix comparison between the message arrays of two calls (what is outside prefix is new), so “same content but reordered” will not be misidentified as new. Changes are marked with capsules next to the message count: New 18, Disappeared 4 (disappearing usually means compression occurred), Rewritten in place, Param changed. The first call in a round has no marks because “previous call” does not exist.

Tool call cards. Each call row in the tool_calls array shows ToolName(Parameter summary) plus a status badge (Success · 21ms / Failed · 1.3s / Not Returned). Hover to see raw parameters JSON, click the badge to expand full parameters and result body. Status, duration, and result bytes all come from the paired tool/call + tool/result in the current round’s logs, not guessed from parsing parameters. Calls that appear in this request but are not recorded in the current round’s logs (when history is truncated) degrade to Not Returned and will not disappear out of thin air.

Context source proportion. At the bottom of the dialog, messages is split by source into five categories with counts and proportions: your input, harness auto-injection (time context, runtime context, skill directory, system prompt, background task reports, etc.), model reply, and tool round-trip. Click a category to expand which subcategories it consists of. It marks “Current Turn Added” proportion to answer “why the context grows so fast.” The proportion is calculated by message bytes, not tokens.

Current turn timeline. When a turn has multiple steps, a timeline appears at the top, one segment per step. The width is calculated by the actual tokens consumed by that step (input + cache + output). Click a segment to jump to that call. The right side is the total tokens for this turn. It does not duplicate the function of the step list: the list answers “which call to select,” the timeline only answers “proportion.”

Request and Response separated. The top right of the JSON area has a Request | Response toggle. The response side is also collapsible and copyable. It includes finish_reason (requires_tool_call / stop / failure / interrupted etc.), every retry attempt for failed attempts (code + status + message, success is not counted), first byte / total duration / output chars / reasoning chars / response body bytes, and the reconstructed response body in the OpenAI chat-completion format (choices[0].message + finish_reason + usage, with reasoning_content additionally given if reasoning content is present). If this step completely fails and there is no reply, the response side directly writes “No reply record for this call in logs” and lists the failed attempts, without fabricating an empty reply. At this point, the Response button turns gray.

JSON and Dialogue linking. A Dialogue ↗ tag is permanently resident on the row of each message in messages. Clicking it closes the dialog and scrolls the conversation to that message’s own bubble (precise to data-chat-node-key, falls back to data-chat-turn if the bubble is not mounted in a collapsed round). The reverse direction works the same: when opening the dialog from a button on a reply, the JSON will stop directly at the line corresponding to that reply and highlight it, so you don’t need to search through hundreds of messages.

Copy and Navigation. Copy the current call, or “Copy All Calls” to export as a request array. The right side of the JSON panel has a permanent wide scrollbar, plus “↑ Top / ↓ Bottom” buttons at the top, auto-returning to top when switching calls or expanding/collapsing. Esc or clicking the mask closes the dialog. The dialog height is fixed at 88vh, and only the JSON area scrolls.

Installation and Enablement

The plugin is a Cordis bundle (cordis.patch.yml + a pure client client.js). Install it into the DSH profile and refresh the interface; no need to reinstall the server.

Method 1 (Install from GitHub for the agent in DSH to execute, or call plugin_manager’s install_bundle yourself):

git+https://github.com/shengpj/dsh-payload.git#<commit>

Use a 40-character SHA for <commit>. Get the current value with the command below:

git ls-remote https://github.com/shengpj/dsh-payload.git refs/heads/main

Method 2 (Via the plugin market index). The repo includes a market/index.json (DSH Plugin Market format 1). After enabling plugin-market, add this address to the index:

https://raw.githubusercontent.com/shengpj/dsh-payload/main/market/index.json

After that, search for dsh-payload in Discover for one-click installation. The version in the index points to a commit-pinned git source; what is installed is the corresponding version in the repo. When releasing a new version, add an entry to the index using node tools/build-market-index.mjs --commit <40-char SHA> --version <new version number>, and the old version entry will be retained.

Method 3 (More convenient when changing code): Pull the repo locally, then install this directory using link:<absolute path>. Subsequent changes don’t require reinstallation. The effect after installing all three is the same—you can see dsh-payload in the profile’s bundles list, and after refreshing the interface (Desktop Ctrl+R, or exit and reopen if it won’t refresh), the icon appears below replies.

Typical Usage

  1. Find a specific assistant reply, move your mouse over the { } in the action row, and the tooltip shows “View Request JSON”.
  2. Click, and the dialog opens and stops directly on the call that generated this reply.
  3. messages defaults to only expanding messages newly generated this turn, while earlier history is collapsed into the row “History Messages (n)”. The Current n / History n on the array title tells you how many there are each.
  4. If the Turn this reply belongs to calls many times, use the left step list to switch steps. Top right of the right side JSON is Request | Response: the former is what was sent, the latter is what was received.
  5. Rows in tool_calls are tool call cards: the badge gives success/failure and round-trip time, hover to see raw params, click badge to expand params and result body.
  6. “Copy” takes the current screen content (Request side is the request, Response side is the reconstructed response body), “Copy All Calls” takes the array of all requests in this turn.
  7. To know what the model replied, click Response: finish_reason explains why it stopped, Retry lists the attempts that failed at this step, and below is the reconstructed chat-completion JSON.

Data Source and Token Scope

The plugin does not resend requests or add new server interfaces; it completely reconstructs offline, and all data comes from the session log already held by the Client:

Required Fields Log Source
Call boundaries (one per call) {turn, step} from step/start + step/end
model, provider assistant/message.data.message.source of that step, falls back to header.config in request/header if missing
Call parameters like temperature / maxTokens header.config in request/header, sparse, needs to inherit forward
tools header.tools in request/header, also needs to inherit forward
messages All surface events before the first response event of that step
Split point for current / history turn/start.seq of the Turn this reply belongs to
Token usage assistant/message.data.usage of that step
Status / duration / result of tool calls Paired tool/call + tool/result by callId

request/header is only written when call config changes (reason is initial / change / resume / series), not every call, so the grouping anchor uses step/start, and config never exceeds the most recent header of that step.

The field scope for token decomposition is as follows, all from the assistant/message.data.usage reported by the provider:

Field Meaning
inputTokens The part of the actual prompt tokens sent this time that missed the cache
cacheReadTokens Tokens that hit the prompt cache
outputTokens Tokens generated by the model
totalTokens Provider’s total, equals inputTokens + cacheReadTokens + outputTokens

Hit rate is calculated as cacheReadTokens / (inputTokens + cacheReadTokens). The three easily confused quantities are clearly distinguished in the README: the “Input” chip is prompt tokens that missed the cache, not JSON size; the “Cache” chip is the part that hit the cache, input + cache is the complete prompt; “N messages” and “Request body X MB” are message count and byte count of the reconstructed JSON, bytes do not equal tokens.

The README also gives an example of a practical discrepancy: a certain call had input 1009 / cache 80,640 / output 1,425 / total 83,074, while the log reconstructed 1078 messages, 3.70 MB, and about 754k tokens, but the provider only billed 81,649 prompt tokens, a difference of 9.2x. The reason is that before sending the request, it first goes through context compression, folding old intervals into summaries. The logs preserve the original text, while the model only reads the summary. Therefore, tokens are always based on assistant/message.data.usage; counts and bytes are only for reference as to “how big this snapshot is.” Sometimes the difference between adjacent calls is only 2, because the product of one step is exactly “one assistant message (with tool_calls) + one corresponding tool result.”

Applicable Scenarios and Notes

Suitable for post-hoc troubleshooting of agent behavior in the DSH graphical interface: why the context grows, which step consumed the most tokens, whether tool calls succeeded or failed, why the model ended, and when parameters changed. It is not a cross-platform log tool.

Points to note:

  • Only supports the DSH graphical interface client; there is no entry under TUI/CLI. The plugin is a pure client implementation and does not have Host-side service registration.
  • The plugin runs with the permissions of the current DSH process; you should check the source code and license (MIT, see repo LICENSE) before installing.
  • When opening a Turn for the first time, you need to scroll the session window to the end (loadOlder()). The number of page turns is proportional to session length; results for the same Turn are cached and will open quickly again. The reconstruction itself only folds the window once; a Turn with 170 calls takes about 100ms in Node.
  • Calls where the provider did not report usage will show “Not recorded to usage,” while the rest of the part displays normally.
  • Verified environment is DSH 0.2.1-rc.3 Desktop.

Conclusion

The value of dsh-payload lies in turning “what exactly is this request” from a problem that requires digging through logs for an answer into a problem that is there as soon as you click to open the dialog. If you are doing agent debugging in DSH, it is worth installing and trying a long conversation, focusing on the context source proportion and the current turn timeline.

  • Community directory page: https://www.skillhub.cn/plugins/shengpj/dsh-payload
  • Repository: https://github.com/shengpj/dsh-payload
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye