from dialoghelper import *
import inspect
from safepyrun import find_vardialoghelper
from fastcore import tools
from fastcore.test import *Basics
a = 1
find_var('a')1
names_containing('ipython')['get_ipython', 'load_ipython_extension', 'ipython_shell', 'in_ipython']
Reaching rustygate
dialoghelper calls rustygate’s API through a rustygate comm in a gateway kernel, without a URL or token. Outside one, it sends HTTP to RUSTYGATE_URL. jupyasyncclient.http owns the transport and JSON request mechanics; dialoghelper supplies its gateway configuration and sends the same method, route, query parameters and body over either transport.
refresh_dlg
async def refresh_dlg():Ask every browser showing this dialog to render its current messages and templates
Here the comm carries a request for this kernel’s model. Its path is the notebook this code runs in.
After filling templates programmatically, call await refresh_dlg() to update every browser showing this dialog. It requests a full render without reconnecting or restarting the kernel; it does not wait for browsers to finish rendering.
test_eq((await _rg('GET', f"/api/kernels/{dh_settings['kid']}"))['path'], dh_settings['path'])find_dname
def find_dname(
dname:NoneType=None, required:bool=True
):Return dname as a gateway-root-relative notebook name without a leading slash, adding a missing .ipynb. A leading / starts from the gateway root, other names are relative to the current dialog’s folder, and a trailing / names a folder.
find_dname()'nbs/00_core.ipynb'
test_eq(find_dname('index'), find_dname('index.ipynb'))
find_dname('index')'nbs/index.ipynb'
find_dname('../index.ipynb')'index.ipynb'
find_dname('/foo/bar.ipynb')'foo/bar.ipynb'
browser runs JavaScript in each browser showing this dialog. It opens a browser comm, and Solveit runs the code in every connected browser as the body of an async function. That function receives done, which answers the call, and rid, the call’s id. Code that answers later from an event handler can instead pass rid on and call pushData(rid, {result}), as the solveit-chrome extension does.
The first browser to answer supplies the result. A JavaScript error raises RuntimeError, and no answer within timeout seconds raises TimeoutError. With wait=False, browser returns at once. The code never appears in a cell’s output, so reloading a page doesn’t run it again.
add_html
def add_html(
content:str, # HTML whose `hx-swap-oob` elements are swapped into the page
):Swap content’s out-of-band elements into each browser showing this dialog
browser
async def browser(
js:str, # Body of an async JavaScript function, which receives `done` and `rid`
wait:bool=True, # Wait for the first browser to call `done`?
timeout:float=15, # Seconds to wait for an answer
):Run js in each browser showing this dialog; with wait, return the value the first browser passes to done
await browser("done({width: window.innerWidth})")await browser("done(document.querySelectorAll('.msg').length)")add_html swaps the hx-swap-oob elements of some HTML into each browser’s page. It runs htmx.swap through browser, and returns without waiting:
add_html(Div(P('Hi'), hx_swap_oob='beforeend:#dialog-container')){'success': 'Content added to DOM'}
realpath() returns the gateway’s on-disk root. realpath('') returns the current notebook’s folder. Other relative paths use that folder, regardless of the kernel’s cwd. A leading slash uses the gateway root. Targets need not exist. .. can move up within the root, and a path that resolves outside it raises ValueError. realpath makes no gateway call and doesn’t need awaiting, so a $ reference can use it.
These helpers resolve names against dh_settings['path'], the kernel’s notebook, which follows renames. An unbound kernel can resolve root-qualified paths but has no notebook-relative base. list_dialogs() lists the current notebook’s folder by default and keeps the existing notebook-extension and folder-suffix conventions.
realpath
def realpath(
subpath:str='/', # Gateway-root-relative with a leading slash, otherwise relative to the current notebook's folder
)->str:The on-disk path of subpath, which must stay within the gateway root
realpath('data/results')'/Users/jhoward/aai-ws/dialoghelper/nbs/data/results'
list_dialogs
async def list_dialogs(
subpath:str='', # Path under data root (absolute with `/`, else relative to current dialog's folder)
depth:int=1, # Directory depth
)->dict:List dialogs and folders under subpath. Folders have / suffix.
(await list_dialogs())['items'][-4:]['06_utils.ipynb', '07_test.ipynb', 'data/', 'index.ipynb']
(await list_dialogs('/'))['items'][:5]['SOLVEIT.ipynb', '_proc/', 'data/', 'dialoghelper/', 'dialoghelper.egg-info/']
Messages API
data_root
def data_root():The gateway root recorded at startup, or derived from the current notebook’s folder
The reading and editing tools use Rustygate’s messages API. Rustygate resolves dialog paths, selects messages, converts between message and notebook formats, and applies changes to the current notebook state.
Text edits read content before sending a replacement. Writes do not check whether that content changed meanwhile; the last write to a field wins. Solveit updates its view from the gateway’s broadcasts.
The kernel’s bootstrap records the gateway root in dh_settings['root']. data_root returns it for helpers that use local filesystem paths.
View/edit dialog
s = 'x = 21\nx*2\ndone'
test_eq(_lnhashs_content(s), '\n'.join(lnhash(i,l)+l for i,l in enumerate(s.splitlines(),1)))
test_eq(_lnhashs_content(s, 2, -1).splitlines()[0], lnhash(2,'x*2')+'x*2')
test_eq(len(_lnhashs_content(s, 1, 2).splitlines()), 2)read_msg
async def read_msg(
n:int=-1, # Message index (if relative, +ve is downwards)
relative:bool=True, # Is `n` relative to current message (True) or absolute (False)?
id:str=None, # Message id to find (defaults to current message)
start_line:int=1, # Starting line to view
end_line:int=None, # End line (defaults to last line if None; -1 for EOF)
nums:bool=False, # Whether to show line numbers
lnhashs:bool=False, # Show exhash `lineno|hash|` addresses instead of line numbers?
dname:str='', # Dialog to get info for; defaults to current dialog
)->dict | None:Get the message indexed in the current dialog, or None when there’s no such message. NB: Messages in the current dialog above the current message are already visible; use this only when you need line numbers for editing operations, or for messages not in the current dialog or below the current message. - To get the exact message use n=0 and relative=True together with id. - To get a relative message use n (relative position index). - To get the nth message use n with relative=False, e.g n=0 first message, n=-1 last message.
find_msgs
async def find_msgs(
re_pattern:str='', # Optional regex to search for (re.DOTALL+re.MULTILINE is used)
msg_type:str=None, # optional limit by message type ('code', 'note', or 'prompt')
before:int=0, # Include additional n msgs before matches
after:int=0, # Include additional n msgs after matches
context:int=None, # Include additional n msgs around matches (default 1, or 0 when `headers_only`)
use_case:bool=False, # Use case-sensitive matching?
use_regex:bool=True, # Use regex matching?
only_err:bool=None, # Only messages with errors if True, only those without if False, all if None
only_exp:bool=None, # Only exported messages if True, only unexported if False, all if None
only_chg:bool=False, # Only messages whose input differs from Git HEAD
ids:str='', # Optionally filter by comma-separated list of message ids
limit:int=None, # Optionally limit number of returned items
include_output:bool=True, # Include output in returned dict?
include_meta:bool=True, # Include all additional message metadata
as_xml:bool=False, # Use concise unescaped XML output format
nums:bool=False, # Show line numbers?
trunc_out:bool=False, # Middle-out truncate code output to 100 characters?
trunc_in:bool=False, # Middle-out truncate cell content to 80 characters?
headers_only:bool=False, # Only return note messages that are headers (first line only); cannot be used together with `header_section`
header_section:str=None, # Find section starting with this header; returns it plus all children
include_skipped:bool=False, # Include messages hidden from AI (skipped)?
dname:str='', # Dialog to get info for; defaults to current dialog
)->list[dict]: # Messages in requested dialog that contain the given informationOften it is more efficient to call view_dlg (not find_msgs) to see the whole dialog, so you can use it all from then on. Message ids are identical to those in LLM chat history, so do NOT call this to view a specific message if it’s in the chat history–instead use view_msg. Do NOT use find_msgs to view message content in the current dialog above the current prompt – these are already provided in LLM context, so just read the content there directly. (NB: LLM context only includes messages above the current prompt, whereas find_msgs can access all messages.) To refer to a found message from code, use its id field.
1+12
r = await find_msgs(r'1\+1', include_meta=False, include_output=True, context=0)
r[{'id': '8ce548d6',
'content': '1+1',
'output': '2',
'msg_type': 'code',
'exported': False}]
hl_md(await find_msgs(r'1\+1', include_meta=False, as_xml=True, context=0))<msgs><code id="8ce548d6">1+1<out>2</out></code></msgs>view_dlg
async def view_dlg(
dname:str='', # Dialog to get info for; defaults to current dialog
msg_type:str=None, # optional limit by message type ('code', 'note', or 'prompt')
nums:bool=False, # Whether to show line numbers
include_output:bool=False, # Include output in returned dict?
trunc_out:bool=True, # Middle-out truncate code output to 100 characters (only applies if `include_output`)?
trunc_in:bool=False, # Middle-out truncate cell content to 80 characters?
include_skipped:bool=False, # Include messages hidden from AI (skipped)?
)->str:Concise XML view of all messages (optionally filtered by type), not including metadata. Often it is more efficient to call this to see the whole dialog at once (including line numbers if needed), instead of running find_msgs or view_msg multiple times.
hl_md((await view_dlg(nums=True))[:500])<msgs><code id="955b9784"> 1 │ #| default_exp core</code><code id="a982e24d"> 1 │ from dialoghelper import *</code><markdown id="0aafe008"> 1 │ # dialoghelper
2 │
3 │ > Inspect and edit Solveit dialogs, run messages, manage kernels, and communicate with browser pages
4 │
5 │ Use these helpers from a Solveit kernel. Most examples below need a running Solveit server and a current dialog. They change that dialog and include examples that run prompts or restart and add_msg
async def add_msg(
content:str, # Content of the message (i.e the message prompt, code, or note text)
msg_type:str='note', # Message type, can be 'code', 'note', or 'prompt'
msgid:str=None, # Message to place the new one beside; default: the calling cell in its own dialog, else the end (or start for `position='before'`)
position:str='after', # 'after' or 'before' `msgid`, or the dialog's 'start' or 'end'
dname:str='', # Dialog to add to; defaults to current dialog
output:str='', # Prompt/code output; Code outputs must be .ipynb-compatible JSON array
exported:int=0, # Mark message as exported (stored as nbdev `export` metadata)?
skipped:int=0, # Hide message from prompt?
i_collapsed:int=0, # Collapse input?
o_collapsed:int=0, # Collapse output?
heading_collapsed:int=0, # Collapse heading section?
pinned:int=0, # Pin to context?
bookmark:int=0, # Bookmark number, 1 to 9, or 0 for none
)->str: # Message ID of newly created messageAdd a message to a dialog, returning its id
add_msgs
async def add_msgs(
msgs:list[dict], # Message dictionaries with content and the same per-message options as add_msg
msgid:str=None, # Message to place the batch beside; defaults to the calling cell in its own dialog
position:str='after', # 'after' or 'before' msgid, or the dialog's 'start' or 'end'
dname:str='', # Dialog to add to; defaults to current dialog
)->list[str]:Add messages in their input order with one request, returning their ids
Without msgid, add_msg places the new message after the calling cell: the cell whose code is running, or the prompt whose tool call is running. position='before' places it before that cell instead. Successive calls therefore come out in reverse order. Use add_msgs to add a list of message dictionaries in input order with one request; each dictionary takes content and the same per-message options as add_msg. When there’s no calling cell, or the message goes to another dialog, it goes at the end, or at the start for position='before'. position='start' and position='end' put it at the dialog’s start or end, whatever the calling cell. The id of the prompt a model is answering is the sid attribute of its <prompt> tag.
_id = await add_msg('testing')
_id'9ed1250c'
read_msgid
async def read_msgid(
id:str, # Message id to find
start_line:int=1, # Starting line to view
end_line:int=None, # End line (defaults to last line if None; -1 for EOF)
nums:bool=False, # Whether to show line numbers
lnhashs:bool=False, # Show exhash `lineno|hash|` addresses instead of line numbers?
dname:str='', # Dialog to get message from; defaults to current dialog
add_to_dlg:bool=False, # Whether to add message content to current dialog (as a raw message)
)->dict | None:Get message id, or None when there’s no such message. Message IDs can be view directly in LLM chat history/context, or found in find_msgs results. Use add_to_dlg if the LLM or human may need to refer to the message content again later.
r = await read_msg(-2)
print((await read_msg(-2)).content)_id = await add_msg('testing')
_id
read_msg returns a dictionary with attribute access. Access its fields as dictionary keys, such as r['content'], or attributes, such as r.content. Functions that take message ids also accept the DOM form that Solveit’s page and message links use, with a leading _, such as _f5fd1d54.
bmsg = await add_msg('just below the calling cell')
emsg = await add_msg('at the end', position='end')test_eq((await read_msg(-1))['id'], bmsg)
test_eq((await read_msg(-1, relative=False))['id'], emsg)
test_eq((await read_msgid('_'+bmsg))['id'], bmsg)view_msg
async def view_msg(
id:str, # Message id to view
dname:str='', # Dialog to get message from; defaults to current dialog
nums:bool=True, # Whether to show line numbers
lnhashs:bool=False, # Show exhash `lineno|hash|` addresses instead of line numbers?
start_line:int=1, # Starting line to view. Rarely needed--read whole message in nearly all cases instead
end_line:int=None, # End line (defaults to last line if None; -1 for EOF)
incl_out:bool=False, # Append the message's output in an `<out>` block?
trunc_out:bool=True, # Truncate an included output to ~512 chars?
add_to_dlg:bool=False, # Whether to add message content to current dialog (as a raw message)
)->str:Views the content* of message id. Same as read_msgid(...)['content'], defaulting to nums=True.* Use add_to_dlg if the LLM or human may need to refer to the message content again later.
print((await view_msg(r.id))) 1 │ _id = await add_msg('testing')
2 │ _id
msg_ref
def msg_ref(
id, dname:NoneType=None
):Markdown ref to a message — same-dialog #_id or cross-dialog #dname/_id (anchors target DOM ids, which carry a _ prefix)
del_msgs
async def del_msgs(
ids:str=None, # Comma-separated ids of message(s) to delete
dname:str='', # Dialog to get info for; defaults to current dialog
log_changed:bool=False, # Add a note showing the deleted content?
)->list:Delete exactly the named messages (a collapsed heading’s hidden section stays). DO NOT USE THIS unless you have been explicitly instructed to delete messages.
await del_msgs(f'{bmsg},{emsg}')
await del_msgs(_id)['9ed1250c']
update_msg
async def update_msg(
id:str=None, # id of message to update (if None, uses current message)
msg:Optional[Dict]=None, # Dictionary of field keys/values to update
dname:str='', # Dialog to get info for; defaults to current dialog
log_changed:bool=False, # Add a note showing the diff?
*, content:str | None=None, # Content of the message (i.e the message prompt, code, or note text)
msg_type:str | None=None, # Message type, can be 'code', 'note', or 'prompt'
output:str | None=None, # Prompt/code output; Code outputs must be .ipynb-compatible JSON array
exported:int | None=None, # Set export state (nbdev `export` metadata)?
skipped:int | None=None, # Hide message from prompt?
i_collapsed:int | None=None, # Collapse input?
o_collapsed:int | None=None, # Collapse output?
heading_collapsed:int | None=None, # Collapse heading section?
pinned:int | None=None, # Pin to context?
meta:dict | None=None, # Replace message meta wholesale
mergemeta:dict | None=None, # Deep-merge into message meta; a `None` value deletes its key
)->str:Update an existing message. Provide either msg OR field key/values to update. - Use content param to update contents. - Only include parameters to update–missing ones will be left unchanged.
_id = await add_msg('testing')_id = await update_msg(_id, content='toasting')_id = await update_msg(_id, skipped=1)update_msg sends only the fields you pass. meta= replaces the metadata dictionary. mergemeta= recursively merges dictionaries and deletes keys whose value is None. Rustygate applies both against the message’s current state.
log_changed=True appends an audit note in the calling dialog, including when the edited messages belong to another dialog.
await update_msg(_id, mergemeta=dict(pins=dict(a=1)))
test_eq((await read_msgid(_id))['meta']['pins'], {'a': 1})
await update_msg(_id, mergemeta=dict(pins=None))
assert 'pins' not in (await read_msgid(_id))['meta']msg = await read_msgid(_id)
msg['content'] = 'toasted'
await update_msg(msg=msg)'e49c9729'
await del_msgs(_id)['e49c9729']
_edit_id = await add_msg('This message should be found.\n\nThis is a multiline message.')
_edit_id'c99d7c11'
print((await read_msg())['content'])This message should be found.
This is a multiline message.
print((await read_msg(n=0, id=_edit_id, nums=True))['content']) 1 │ This message should be found.
2 │
3 │ This is a multiline message.
print((await read_msg(n=0, id=_edit_id, nums=True, start_line=2, end_line=3))['content']) 2 │
3 │ This is a multiline message.
codeid = (await read_msg())['id']copy_msgs
async def copy_msgs(
ids:str, # Comma-separated ids of message(s) to copy
cut:bool=False, # Cut message(s)? (If not, copies)
dname:str='', # Dialog to copy messages from; defaults to current dialog
)->list:Copy messages to the gateway’s clipboard, replacing its contents, and delete them if cut
paste_msgs
async def paste_msgs(
msgid:str=None, # Message to paste beside; default: the calling cell in its own dialog, else the end (or start for `position='before'`)
position:str='after', # 'after' or 'before' `msgid`, or the dialog's 'start' or 'end'
dname:str='', # Dialog to paste into; defaults to current dialog
)->list:Paste the gateway clipboard’s messages where add_msg would place a new one, returning their ids
paste_msgs places the pasted messages where add_msg would place a new one. These functions use the gateway’s clipboard, which Solveit’s copy and paste use too, so messages copied in the browser can be pasted from code and the other way round. A copy replaces the clipboard’s contents. The first paste after a cut keeps the cut messages’ ids, so links to them keep working. Every other paste gives the copies new ids.
await copy_msgs(codeid)['eda5f04b']
with expect_fail(httpx2.HTTPStatusError): await copy_msgs('nosuchid')tgt = (await read_msg())['id']await paste_msgs(tgt)['bdaa7a50']
newmsg = await read_msg(1, id=tgt)
test_eq(newmsg['content'], (await read_msgid(codeid))['content'])
newmsg['content']"print((await read_msg(n=0, id=_edit_id, nums=True, start_line=2, end_line=3))['content'])"
await del_msgs(newmsg['id'])['bdaa7a50']
mermaid_script = enable_mermaid()
assert 'm => {' in to_xml(mermaid_script)
mermaid_scriptmermaid('graph LR; A[Start] --> B[Process]; B --> C[End];')You can also add to a note:
```mermaid
graph LR
A[Start] --> B[Process]
B --> C[End]
```
This renders as:
graph LR
A[Start] --> B[Process]
B --> C[End]
toggle_header
async def toggle_header(
id:str, # id of markdown header note message to toggle collapsed state
dname:str='', # Dialog to toggle in; defaults to current dialog
)->dict:Toggle collapsed header state for id
toggle_bookmark
async def toggle_bookmark(
id:str, # id of message to toggle bookmark on
n:int, # Bookmark number (1-9)
dname:str='', # Dialog to set bookmark in; defaults to current dialog
)->dict:Toggle numbered bookmark (1-9) on a message, clearing it from any other message when setting
toggle_export
async def toggle_export(
ids:str | list, # Message id(s) to toggle (comma-separated str, or list)
dname:str='', # Dialog to get info for; defaults to current dialog
)->dict:Toggle the nbdev export directive (meta form) on each message, independently
toggle_comment
async def toggle_comment(
id:str, # id of code message (or comma-separated ids) to toggle comments on
dname:str='', # Dialog to toggle comments in; defaults to current dialog
)->dict:Toggle line comments on code message(s). If any lines are uncommented, comments all; otherwise uncomments all.
await toggle_comment(codeid) # comment
await toggle_comment(codeid) # uncomment{'success': True}
with expect_fail(httpx2.HTTPStatusError): await toggle_comment('_fake', dname='/dlg/nonexistent.ipynb')test header
header end
hdid = (await read_msg())['id']await toggle_header(hdid){'success': True, 'id': '9bba132a'}
with expect_fail(httpx2.HTTPStatusError): await toggle_header('_fake', dname='/dlg/nonexistent.ipynb')Dlg conveniences
These functions manage dialogs and their kernels through rustygate. create_or_run_dialog gets or starts the kernel bound to a dialog, creating the dialog if it’s missing. A new dialog starts with its folder’s TEMPLATE.ipynb cells and settings. When the templates set no mode, it takes the current dialog’s mode. restart_dialog keeps the kernel’s id, and rm_dialog stops the kernel before deleting the file.
create_or_run_dialog
async def create_or_run_dialog(
name:str, # Name/path of the dialog (relative to current dialog's folder, or absolute if starts with '/')
)->dict: # The dialog's kernel modelCreate a new dialog, or start an existing one’s kernel. A new dialog takes its mode from the current one.
tk = await create_or_run_dialog('test_dialog.ipynb')
tk['path']'nbs/test_dialog.ipynb'
restart_dialog
async def restart_dialog(
name:str, # Name/path of the dialog (relative to current dialog's folder, or absolute if starts with '/')
)->dict: # The dialog's kernel modelRestart a dialog’s kernel, starting the dialog first if needed
test_eq((await restart_dialog('test_dialog.ipynb'))['id'], tk['id'])stop_dialog
async def stop_dialog(
name:str, # Name/path of the dialog (relative to current dialog's folder, or absolute if starts with '/')
):Stop a dialog’s kernel, if it’s running
await stop_dialog('test_dialog.ipynb')
assert not await _kernel_for(tk['path'])rm_dialog
async def rm_dialog(
name:str, # Name/path of the dialog, or of an empty folder, to delete (relative to current dialog's folder, or absolute if starts with '/')
):Delete a dialog or an empty folder, stopping the dialog’s kernel first if it’s running
await rm_dialog('test_dialog.ipynb')Frontmatter and import
Frontmatter is a fenced YAML mapping in physical message zero, whose type must be raw or note (not a prompt). Templates keep placeholder values under formdata. update_frontmatter preserves any text after the closing fence. Invalid or non-mapping headers are ordinary messages; malformed fenced YAML raises an API error.
update_frontmatter
async def update_frontmatter(
dname:str='', # Dialog to update; defaults to current dialog
**kwargs
)->dict: # The updated frontmatterMerge kwargs into a dialog’s frontmatter, preserving its body or adding a raw header when absent
read_frontmatter
async def read_frontmatter(
dname:str='', # Dialog to read; defaults to current dialog
)->dict | None: # The frontmatter, or None if the dialog has noneThe YAML mapping at the start of a dialog’s physical first raw or note message
tmpl = 'test_template.ipynb'
await create_or_run_dialog(tmpl)
await add_msg('Dear {{first_name}} {{last_name}}, your otter toys have shipped.', dname=tmpl)
await update_frontmatter(tmpl, type='letter', formdata=dict(first_name='John', last_name='Doe'))
fm = await update_frontmatter(tmpl, formdata=dict(last_name=None, city='Paris'))
test_eq(await read_frontmatter(tmpl), dict(type='letter', formdata=dict(first_name='John', city='Paris')))import_dlg copies a template without running code, filling placeholders, refreshing the dialog or changing browser selection. It assigns fresh IDs, preserves metadata, attachments and prompt replies, and clears ordinary code outputs and execution counts. The selected physical frontmatter merges into the destination with source values winning; nested null deletes a key, maps merge, and arrays and scalars replace. Any nonempty frontmatter body becomes an ordinary note at the import position. A destination frontmatter header stays at message zero even when importing at the start.
Selection and copying happen in one gateway request. The default imports every unskipped message. An ids list imports that exact selection, including skipped messages, in source order; ids=[] imports nothing. Other find_msgs filters select the source messages in Rustygate. Frontmatter merges only when physical message zero is selected. Omitted placement follows the calling cell in the current dialog and appends to an explicitly named destination. position='before' without msgid retains the calling-cell/start convenience.
import_dlg
async def import_dlg(
src_dname:str, # Dialog to copy messages from
dname:str='', # Dialog to add them to; defaults to current dialog
msgid:str=None, # Message to place them beside; default: the calling cell in its own dialog, else the end (or start for `position='before'`)
position:str='after', # 'after' or 'before' `msgid`, or the dialog's 'start' or 'end'
*, re_pattern:str='', # Optional regex to search for (re.DOTALL+re.MULTILINE is used)
msg_type:str=None, # optional limit by message type ('code', 'note', or 'prompt')
use_case:bool=False, # Use case-sensitive matching?
use_regex:bool=True, # Use regex matching?
only_err:bool=None, # Only messages with errors if True, only those without if False, all if None
only_exp:bool=None, # Only exported messages if True, only unexported if False, all if None
only_chg:bool=False, # Only messages whose input differs from Git HEAD
ids:str='', # Optionally filter by comma-separated list of message ids
limit:int=None, # Optionally limit number of returned items
header_section:str=None, # Find section starting with this header; returns it plus all children
include_skipped:bool=False, # Include messages hidden from AI (skipped)?
)->str: # The copied messages, each under a `# msg <id>` header with its new idImport a clean template, merging its selected physical frontmatter into the destination
dst = 'test_import.ipynb'
await create_or_run_dialog(dst)
await update_frontmatter(dst, type='draft', status='new')
await import_dlg(tmpl, dname=dst)
test_eq(await read_frontmatter(dst), fm | dict(status='new'))for o in (tmpl, dst): await rm_dialog(o)run_code_interactive
async def run_code_interactive(
code:str, # Code to have user run
):Insert code into user’s dialog and request for the user to run it. Use other functions where possible, but if they can not find needed information, ALWAYS use this instead of guessing or giving up. IMPORTANT: This tool is TERMINAL - after calling it, you MUST stop all tool usage and wait for user response. Never call additional tools after this one.
run_cells runs code cells in a dialog’s kernel through rustygate’s run route: the current dialog’s kernel, unless you pass another dialog’s kernel id. Rustygate saves each cell’s outputs in the cell and sends them to every connected client, as for a run started in Solveit, and run_cells returns them. aidialog’s Message.execute and Dialog.execute differ: they run messages in a temporary in-process shell.
A tool can call run_cells while its own message is still running. Rustygate runs the requested cells in a subshell, so they don’t wait behind the calling message in the main shell’s queue.
When py runs with safepyrun’s audit checks, the nested execution remains inside that audit window. run_cells does not add a separate sandbox.
run_cells
async def run_cells(
ids:list, # Ids of code cells to run, in order
kid:str=None, # Kernel whose dialog holds them (default: this one)
)->list: # Each cell's saved outputsRun code cells in a dialog’s kernel through rustygate, saving their outputs; the run stops at the first cell that raises
Text Edit
await msg_insert_line(_edit_id, 0, 'This should go to the first line')
await msg_insert_line(_edit_id, 3, 'This should go to the 4th line')
print(await msg_insert_line(_edit_id, 5, 'This should go to the last line'))@@ -5 +5,2 @@
This is a multiline message.
+This should go to the last line
print((await read_msg(n=0, id=_edit_id, nums=True))['content']) 1 │ This should go to the first line
2 │ This message should be found.
3 │
4 │ This should go to the 4th line
5 │ This is a multiline message.
6 │ This should go to the last line
print(await msg_str_replace(_edit_id, 'This should go to the first line', 'This should go to the 1st line'))@@ -1,2 +1,2 @@
-This should go to the first line
+This should go to the 1st line
This message should be found.
for f in (msg_str_replace, msg_strs_replace): assert 'use_regex' in inspect.signature(f).parametersprint((await read_msg(n=0, id=_edit_id, nums=True))['content']) 1 │ This should go to the 1st line
2 │ This message should be found.
3 │
4 │ This should go to the 4th line
5 │ This is a multiline message.
6 │ This should go to the last line
print(await msg_strs_replace(_edit_id, ['This is a multiline message.', 'This should go to the last line'], ['5th line', 'last line']))@@ -4,3 +4,3 @@
This should go to the 4th line
-This is a multiline message.
-This should go to the last line
+5th line
+last line
print((await read_msg(n=0, id=_edit_id, nums=True))['content']) 1 │ This should go to the 1st line
2 │ This message should be found.
3 │
4 │ This should go to the 4th line
5 │ 5th line
6 │ last line
print(await msg_replace_lines(_edit_id, 2, 4,'line 2\nline 3\nline 4\n'))@@ -1,5 +1,5 @@
This should go to the 1st line
-This message should be found.
-
-This should go to the 4th line
+line 2
+line 3
+line 4
5th line
print((await read_msg(n=0, id=_edit_id, nums=True))['content']) 1 │ This should go to the 1st line
2 │ line 2
3 │ line 3
4 │ line 4
5 │ 5th line
6 │ last line
print(await msg_del_lines(_edit_id, 2, 4))@@ -1,5 +1,2 @@
This should go to the 1st line
-line 2
-line 3
-line 4
5th line
print((await read_msg(n=0, id=_edit_id, nums=True)).content) 1 │ This should go to the 1st line
2 │ 5th line
3 │ last line
await del_msgs(_edit_id)['c99d7c11']
id1 = await add_msg('hello world')
id2 = await add_msg('hello there', msgid=id1)results = await msg_str_replace([id1, id2], 'hello', 'hi')
print(results)[('b7e56484', '@@ -1 +1 @@\n-hello world\n+hi world'), ('327c264e', '@@ -1 +1 @@\n-hello there\n+hi there')]
await del_msgs(id1)
await del_msgs(id2)['327c264e']
Prompts
run_prompt asks rustygate to run a prompt. With a prompt message’s msgid, rustygate answers that prompt with the cells above it as context and saves the reply in the message, as when a user runs it in Solveit. With text, rustygate answers a prompt that isn’t in the dialog, using the cells up to msgid as context, and saves nothing. Without msgid, that context ends at the calling cell.
The reply streams back over the comm, and run_prompt prints it as it arrives unless show=False. It returns the reply in the format Solveit stores, with each tool call as a fenced block. Interrupting the cell closes the comm, which cancels the turn, and a prompt message keeps its partial reply. A prompt run from code doesn’t wait in the kernel’s queue. tools names the tools the model may call: functions in the kernel’s namespace, or rustygate’s own exec and spawn_agent.
run_prompt
async def run_prompt(
msgid:str=None, # Prompt message to run; with `text`, the message the context ends at (default: the calling cell)
text:str=None, # Prompt text to answer without saving anything in the dialog
tools:list=None, # Names of the tools the model may call
show:bool=True, # Print the reply as it streams?
kid:str=None, # Kernel whose dialog holds the prompt (default: this one); with `text` and another dialog's kernel, pass `msgid`
)->str: # The reply, in its stored formatRun a prompt message, or answer text with the dialog as context, and return the reply
pid = await add_msg('Reply with just the word: hello', msg_type='prompt')
reply = await run_prompt(pid)test_eq((await read_msgid(pid))['output'], reply)
await del_msgs(pid)['a3c96594']
await run_prompt(text='What is 2+2? Reply with just the number.')'Whoops! An error (error sending request for url (http://127.0.0.1:9/models)) occurred while processing your request.\nIf this problem persists, please contact us on Discord.\nPlease include your dialog url and error info in your message.'
Help
solveit_docs
async def solveit_docs():Full reference documentation for Solveit - use this to answer questions about how to use Solveit. NB: The whole docs fit in LLM context, so read the whole thing, don’t search/filter it. Always re-run rather than relying on truncated history or assumptions.
dialog_link
def dialog_link(
dname:str='', # Dialog to link to (relative to current dialog's folder, or absolute if starts with '/'); empty for in-page anchor link
msg_id:str=None, # Optional message id to scroll to
):Return an IPython HTML link to open a dialog in Solveit. After calling this, output the resulting HTML anchor tag exactly as returned—do not wrap in a fenced code block or convert to markdown link format.
dialog_link(msg_id='a7d82acd')dialog_link(dname='/CRAFT.ipynb')dialog_link(dname='/CRAFT.ipynb', msg_id='ce727fd8')testing