posit-dev / posit-dev/shinychat
History save fails for app-local S7 `Content` subclasses: "Unable to find the S7 class"
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 139
- Forks
- 28
- Avg merge
- 23h 44m
- Merged PRs (30d)
- 50
Description
When a chat contains ellmer::Content subclasses defined in a Shiny app's R/ files (i.e., not in a package), saving history fails with:
Warning: Could not save conversation
Caused by error in `FUN()`:
! Unable to find the S7 class: `ContentPromptStyle`.
Repro
library(S7)
library(ellmer)
# Shiny sources R/ helpers into new.env(parent = globalenv()), not globalenv()
app_env <- new.env(parent = globalenv())
eval(quote({
ContentPromptStyle <- new_class(
"ContentPromptStyle",
parent = ellmer::ContentText,
properties = list(slug = new_property(class_character, default = "friendly"))
)
}), envir = app_env)
turn <- with(app_env, Turn("user", list(ContentPromptStyle(text = "hi"))))
recorded <- ellmer::contents_record(turn)
# shinychat replays recorded turns at save time from its own namespace frame,
# e.g. extend_record_linear() -> lapply(turns, ellmer::contents_replay, tools)
f <- eval(
quote(function(x) lapply(x, ellmer::contents_replay, tools = list())),
new.env(parent = asNamespace("shinychat"))
)
f(list(recorded))
#> Error: Unable to find the S7 class: `ContentPromptStyle`.
What's happening
ellmer::contents_record()serializes a non-package class as a bare name ("ContentPromptStyle", nopkg::prefix).shinychat:::extend_record_linear()andshinychat:::derive_node_ui_from_turns()callellmer::contents_replay()without.envir, so it defaults toparent.frame()— a frame inside shinychat's namespace.contents_replay()resolves bare class names withget0(name, envir, inherits = TRUE). The namespace parent chain reachesglobalenv()but never descends into the app environment where Shiny sourced the class, so the lookup fails.
The current workaround is for apps to assign("MyClass", MyClass, envir = globalenv()), which works but is non-obvious and easy to get wrong.
Possible directions — not sure what the right fix is
- Capture the app's environment at
chat_server()setup (where the client/turns live) and pass it as.envirtocontents_replay(). - Expose a replay environment on
history_options()so apps defining custom content classes can say where they live. - At minimum, document that custom
Contentsubclasses must be resolvable fromglobalenv()(or a package namespace) for history to work.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with shinychat:::extend_record_linear() and shinychat:::derive_node_ui_from_turns(), then inspect ellmer::contents_replay() and its .envir lookup. Reproduce the failure using an S7 Content subclass in a child app environment, and determine how the replay context should be supplied. Done means history replay succeeds for app-local classes without requiring assignment to globalenv(), with regression coverage.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- r
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100