Repository navigation
Conversation
Lets plugins render the per-message footer; also exposes agents on TuiState.
…ugin Replaces the hard-coded footer with a builtin plugin that can be stacked or replaced.
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
holny
left a comment
There was a problem hiding this comment.
Read the whole thing against the current footer and ran the affected suites locally at 883499d: the footer/slots/wrap tests 21/21 and plugin-toggle 6/6 pass, tui typecheck clean.
Behavior looks preserved, which is the thing I was checking for given this moves a rendered line behind a slot: the gate is the same (last || final || aborted), duration still only computes once finish is set and not tool-calls/unknown, the model name resolves through the same provider list (the old ctx.providers() was Model.index(sync.data.provider), and Model.name takes either form), and alwaysSeparate still gets stamped on the footer box. Reusing the existing slot registry also means no new render-path machinery. The spec note on order 0 plus the plugin_enabled/deactivate takeover path is a nice touch — that's usually the part people get wrong.
Three things:
-
state.agent doesn't seem to be used by this change — the builtin footer gets the agent color through useLocal, not the plugin api, and specs/tui-plugins.md doesn't mention the new field. It's fine if it's there for footer plugins to look up agent info, but the public TuiState is a permanent commitment, so either drop it from this PR or document it (with what a plugin is supposed to use it for).
-
Sequencing: #46108 and #12721 (tokens per second in the response footer) and #43246 (timestamps on assistant messages) all add to this exact line. After this lands they either become footer plugins or fold into the builtin, so it's worth picking a merge order deliberately rather than letting all three rebase against a moving target.
-
Minor: src/util/separate.ts and test/feature-plugins/assistant-message-footer.test.tsx both miss the trailing newline; a formatter run will pick that up.
On the tests: the builtin's own file stops at order + the not-last/not-terminal guard. The guard is the part that matters most, but if you want the duration and interrupted variants pinned, the captureSlot harness you already have can render them without much ceremony.
|
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
|
I understand that the team is busy, waiting is not a problem, however getting a PR closed after one month makes one feel that it's very unlikely that any contribution can be actually be accepted. When I chose OpenCode I thought I could contribute to it, this was the major choice factor, but if this is not true then I need to reconsider. |
Issue for this PR
Closes #46268
Type of change
What does this PR do?
This PR turns the per-assistant-message footer — the
▣ mode · model · durationline under each assistant message — from a hard-coded part of the session route into a plugin slot, so plugins can extend or fully replace it.1.
assistant_message_footerhost slot (plugin API)A new host slot forwards the assistant message and its parts plus two flags plugins need to render a live footer:
terminal(no more content will be produced — finished, or interrupted) andlast(the newest assistant message, may still be streaming).2. Default footer shipped as the builtin plugin
internal:assistant-message-footerThe hard-coded footer moves into a builtin plugin that registers at slot
order: 0and renders wheneverlast || terminal, preserving today's exact rendering while making it modular.Usage
Plugins can now register their own footer (default or positive
order) to stack below the default, use a negativeorderto stack above it, or replace the default entirely by disabling the builtin viaplugin_enabled: { "internal:assistant-message-footer": false }orapi.plugins.deactivate("internal:assistant-message-footer").How did you verify your code works?
bun typecheckpasses inpackages/plugin,packages/tui, andpackages/opencode.bun testpasses inpackages/tui(197 tests), including the plugin/slots and builtin footer tests.api.state, stacking below the default and fully replacing it.Screenshots / recordings
If this is a UI change, please include a screenshot or recording.
Checklist