Skip to content

Add positron.runtime.getConsoleHistory extension API - #15207

Merged
samclark2015 merged 10 commits into
mainfrom
samclark2015/console-output-retrieval
Jul 31, 2026
Merged

Add positron.runtime.getConsoleHistory extension API#15207
samclark2015 merged 10 commits into
mainfrom
samclark2015/console-output-retrieval

Conversation

@samclark2015

@samclark2015 samclark2015 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a read-only positron.runtime.getConsoleHistory(sessionId, numberOfEntries?) extension API that returns recent console history for a session: the code fragments that have already run, each paired with its output and any error. It sits alongside the other session-scoped read APIs getSessionVariables and querySessionTables, and reads from the execution history service via a shared projectExecutionEntriesToConsoleHistory helper (completed executions only, oldest first, skipping the startup banner and entries without input). numberOfEntries limits the result to the most recent N and defaults to 5, applied server-side so only those entries cross the extension-host boundary rather than the whole session history.

Console history reading is gated by a new console.historyApiEnabled setting (WINDOW-scoped, default true). Users can disable it for privacy; when disabled the API rejects rather than returning content. The setting is read live so a mid-session toggle takes effect immediately.

This exists so Posit Assistant can read console history through a native tool, in line with the direction of moving assistant tools off the vscode.lm surface. The companion native tool lives in the assistant repo on branch samclark2015/expose-getconsolecontent-tool (posit-dev/assistant#1924).

Part of posit-dev/assistant#1826 (split code execution and console context into separate tools).

History of this PR

This PR originally added a core getConsoleContent tool exposed via vscode.lm. That tool has since been removed in favor of the native extension API above, so the net change to Positron core is the new positron.runtime API (plus its shared projection helper and privacy setting), not a vscode.lm tool. Recommend squashing on merge.

Release Notes

New Features

  • N/A (internal extension API; the user-facing console-reading tool ships in the Posit Assistant extension).

Bug Fixes

  • N/A

Validation Steps

@:assistant

Without Posit Assistant (via the positron-zed demo extension):

  1. Start a Zed console session and run a few Zed commands so there is history.
  2. Run the Zed: Get Console History command from the Command Palette; optionally enter an entry count (blank uses the default of 5).
  3. Verify the JSON editor that opens shows the recent console entries (input/output/error/when), oldest first.
  4. Disable the Console: History Api Enabled setting and re-run the command; verify the call is rejected rather than returning history.

With Posit Assistant (native getConsoleHistory tool, posit-dev/assistant#1924):

  1. Open a Python or R session and run a few console commands (including one that errors).
  2. Ask the assistant about a recent console result, e.g. "what did my last command print?"
  3. Verify it reads the console history via positron.runtime.getConsoleHistory and answers without re-running any code.

Adds a read-only Positron Assistant tool that returns recent console
executions (input, output, and any error) for a session, letting the
model inspect what has already run without executing code. Because it
carries no requires-actions tag, it stays available in Ask mode while
executeCode remains gated to agent mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

E2E Tests 🚀
This PR will run tests tagged with: @:critical @:assistant

Why these tags?
Tag Source
@:critical Always runs (required)
@:assistant PR description

More on automatic tags from changed files.

readme  valid tags

samclark2015 and others added 4 commits July 29, 2026 10:32
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exposes recent console history to extensions via a new
positron.ai.getConsoleContent(sessionId?) function, so Posit Assistant
can read console content through a native tool rather than the vscode.lm
tool allowlist (the pattern the assistant is moving away from).

The reading/projection logic is extracted into a shared helper,
projectExecutionEntriesToConsoleContent, in the execution history
service and reused by both the existing core getConsoleContent tool
(for Copilot Chat) and the new extension API (for the native Posit
Assistant tool), so the two stay in sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The core getConsoleContent tool was exposed to chat clients via
vscode.lm. Now that Posit Assistant reads console history natively
through the positron.ai.getConsoleContent extension API, this tool is
vestigial and contradicts the direction of moving off vscode.lm tools,
so remove it (tool data, class, registration, and tool-name enum entry).

The projection logic (projectExecutionEntriesToConsoleContent) stays in
the execution history service, still used by the extension API, and gains
a direct unit test in place of the removed tool's tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Relocate getConsoleContent from positron.ai to positron.runtime, where
it sits alongside the other session-scoped read APIs getSessionVariables
and querySessionTables (which back the equivalent inspect tools). Reading
a session's console history is a runtime-state concern, not an AI-specific
one, so it belongs with the runtime session APIs rather than the AI
feature surface.

The signature now takes a required sessionId to match its siblings; the
caller resolves the foreground session. Plumbing moves from the AiFeatures
extHost/mainThread pair to the LanguageRuntime pair accordingly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@samclark2015 samclark2015 changed the title Add getConsoleContent tool for reading recent console history Add positron.runtime.getConsoleContent extension API Jul 29, 2026
Add an optional numberOfEntries argument to
positron.runtime.getConsoleContent, applied server-side (in the shared
projection helper) so only the requested number of most recent entries
crosses the extension-host boundary instead of the whole session history.
Defaults to 5; non-positive values fall back to the default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a "Zed: Get Console Content" command to the positron-zed extension
that reads the foreground session's recent console history via
positron.runtime.getConsoleContent (prompting for an optional entry
count) and opens the result in a JSON editor. Provides a way to exercise
the new API without Posit Assistant installed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@jonvanausdeln jonvanausdeln left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 things/questions:

  1. Finding from our good friend Claude
### `getConsoleContent` with an unknown `sessionId` allocates a permanent `SessionExecutionHistory`

`$getConsoleContent` reads through `IExecutionHistoryService.getExecutionEntries(sessionId)`, which
doesn't throw for a session it hasn't seen — it *creates* one
(`executionHistory.ts:335-352`):

- constructs a `SessionExecutionHistory`
- `this._register(history)`s it onto the long-lived service
- stores it in `_executionHistories` and returns empty entries

Each of those registers an `onWillSaveState` listener that lives for the workbench lifetime
(`sessionExecutionHistory.ts:62`), and nothing ever releases it. Since `numberOfEntries` and
`sessionId` now come straight from extension input, `getConsoleContent(someRandomId)` called
repeatedly grows both memory and the save-state listener list without bound.

Two smaller consequences of the same behavior:

- A caller passing a stale or wrong session ID gets `[]` and can't tell "this session has no
  history" from "this session doesn't exist."
- It's inconsistent with the sibling APIs this one is modeled on. `getSessionVariables` /
  `querySessionTables` go through `getVariablesClient`, which throws
  `No variables provider found for session ${sessionId}` (`sessionVariableQueries.ts:21`).

Worth noting the create-on-read is arguably a latent bug in `getExecutionEntries` itself —
`clearExecutionEntries`, immediately below it, *does* throw for an unknown session ID. This PR is
just the first caller to hand it untrusted input.

**Two ways to fix, either works:**

1. Validate in `$getConsoleContent` before reading, and throw in the siblings' style so the error
   surface is consistent across the session-scoped read APIs.
2. Give `IExecutionHistoryService` a non-allocating read path (e.g. have `getExecutionEntries`
   return `[]` for unknown sessions instead of creating, or add a separate read-only accessor).
   This fixes it for any future caller too, at the cost of touching shared service behavior.

I'd lean toward (1) for this PR since it keeps the blast radius inside the new API, with (2) as a
follow-up if you want the service itself hardened.

One piece of good news: `save()` early-returns when `!_dirty`, so bogus session IDs don't write
empty history keys into workspace storage. The cost is memory and listeners, not persisted junk.

Worth a test for this too — the helper tests are solid, but nothing currently covers
`$getConsoleContent`, which is where this lives.```

2. Since things like secrets/api-keys/etc could be exposed in the console, do we want to have any gating? Or just leave that up to the extension (assistant in this case) ?

samclark2015 and others added 3 commits July 31, 2026 13:13
$getConsoleContent passed untrusted sessionId straight into
getExecutionEntries(), which creates-on-read: an unknown ID would
allocate and permanently register a SessionExecutionHistory (with a
workbench-lifetime onWillSaveState listener), growing memory and the
listener list without bound, and returning [] instead of erroring.

Validate the session up front via a new sessionConsoleContent helper
(mirroring sessionVariableQueries), throwing "No such session" for
unknown IDs so the create-on-read path is never reached and the error
surface matches the sibling session-scoped read APIs. Add coverage for
the known-session and unknown-session (no-allocation) paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…output-retrieval

# Conflicts:
#	extensions/positron-zed/package.json
Rename getConsoleContent to getConsoleHistory across the API surface,
protocol, and helpers, and add a console.historyApiEnabled setting
(default true) that gates extension access to console history.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@samclark2015 samclark2015 changed the title Add positron.runtime.getConsoleContent extension API Add positron.runtime.getConsoleHistory extension API Jul 31, 2026
@samclark2015

Copy link
Copy Markdown
Contributor Author

Thanks! I redid some of the session-getting logic. I also renamed the API calls to history rather than content, based on some feedback from the Assistant-side PR.

For security: I did consider this previously, and just added a default-on setting that users can disable this API with. Disabling the setting will throw an error if the API is called.

@jonvanausdeln jonvanausdeln left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@samclark2015
samclark2015 merged commit f361812 into main Jul 31, 2026
27 checks passed
@samclark2015
samclark2015 deleted the samclark2015/console-output-retrieval branch July 31, 2026 21:51
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 31, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants