dialoghelper

from dialoghelper import *
from fastcore import tools
from fastcore.test import *

Basics

a = 1
find_var('a')
1

source

names_containing

def names_containing(
    s:str
):

Names containing s in IPython user namespace, or caller globals

names_containing('ipython')
['get_ipython', 'load_ipython_extension', 'ipython_shell', 'in_ipython']

source

find_dname

def find_dname(
    dname:NoneType=None, required:bool=True
):

*Get the dialog name by searching the call stack for __dialog_name, and resolving dname if supplied.*

find_dname()
'/aai-ws/dialoghelper/nbs/00_core'
async def repro_executor():
    return await asyncio.get_running_loop().run_in_executor(None, find_dname)
await repro_executor()
'/aai-ws/dialoghelper/nbs/00_core'
find_dname('index')
'/aai-ws/dialoghelper/nbs/index'
find_dname('../index')
'/aai-ws/dialoghelper/index'
find_dname('/foo/bar')
'/foo/bar'

source

xgeta

async def xgeta(
    url, **kwargs
):

source

xposta

async def xposta(
    url, **kwargs
):

source

DialogAPIError

def DialogAPIError(
    *args, **kwargs
):

A Solveit API endpoint reported an error (its JSON response carried an error key)


source

call_endpa

async def call_endpa(
    path, dname:str='', json:bool=False, raiseex:bool=False, id:NoneType=None, required:bool=True, timeout:int=10,
    audit:bool=False, chkerr:bool=True, **data
):

source

call_endp

def call_endp(
    path, dname:str='', json:bool=False, raiseex:bool=False, id:NoneType=None, required:bool=True, timeout:int=10,
    audit:bool=False, chkerr:bool=True, **data
):

source

curr_dialog

async def curr_dialog(
    with_messages:bool=False, # Unused; kept for signature compatibility
    dname:str='', # Dialog to get info for; defaults to current dialog
)->dict | str:

Get the current dialog info.


source

add_html

def add_html(
    content:str, # The HTML to send to the client (generally should include hx-swap-oob)
    dname:str='', # Dialog to get info for; defaults to current dialog
):

Send HTML to the browser to be swapped into the DOM


source

add_html_a

async def add_html_a(
    content:str, # The HTML to send to the client (generally should include hx-swap-oob)
    dname:str='', # Dialog to get info for; defaults to current dialog
):

Send HTML to the browser to be swapped into the DOM

from fasthtml.common import *
add_html(Div(P('Hi'), hx_swap_oob='beforeend:#dialog-container'))
{'success': 'Content added to DOM'}

source

add_scr

def add_scr(
    scr, oob:str='innerHTML:#ephemeral'
):

Swap a script element to the end of the ephemeral element


source

add_scr_a

async def add_scr_a(
    scr, oob:str='innerHTML:#ephemeral'
):

Swap a script element to the end of the ephemeral element


source

add_mod_a

async def add_mod_a(
    s:str
):

Wrap javascript code string in a js script module and add it via add_html


source

add_mod

def add_mod(
    s:str
):

Wrap javascript code string in a js script module and add it via add_html


source

iife

def iife(
    code:str
):

Wrap javascript code string in an IIFE and execute it via add_html


source

iife_a

async def iife_a(
    code:str
):

Wrap javascript code string in an IIFE and execute it via add_html


source

pop_data

def pop_data(
    idx, timeout:int=15
):

source

pop_data_a

async def pop_data_a(
    idx, timeout:int=15
):

source

fire_event

def fire_event(
    evt:str, **data
):

source

fire_event_a

async def fire_event_a(
    evt:str, **data
):

source

event_get

def event_get(
    evt:str, timeout:int=15, **data
):

Call fire_event and then pop_data to get a response


source

event_get_a

async def event_get_a(
    evt:str, timeout:int=15, **data
):

Call fire_event and then pop_data to get a response


source

trigger_now

def trigger_now(
    evt, data:NoneType=None, ttl:int=5000
):

Synchronously trigger a browser event, safe against replay


source

event_once_a

async def event_once_a(
    evt, timeout:int=15, ttl:int=5000, **data
):

Like event_get_a but with replay/dedup safety via trigger_now


source

event_once

def event_once(
    evt, timeout:int=15, ttl:int=5000, **data
):

Like event_get but with replay/dedup safety via trigger_now

iife("$('body').one('test_evt', e => pushData(e.detail.idx, {reply: 'it worked!'}));")
event_once('test_evt', data={'hello': 'world'})
{'data_id': 'f14d1951-7e8f-47a9-9189-6299b8bd0c87', 'reply': 'it worked!'}

source

js_run_a

async def js_run_a(
    code
):

Run JS code that calls done() when finished, and wait for result


source

js_run

def js_run(
    code
):

Run JS code that calls done() when finished, and wait for result

r = js_run("setTimeout(() => done({answer: 6*7}), 100)")
r['answer']

source

js_eval_a

async def js_eval_a(
    expr
):

Evaluate a JS expression in the browser and return the result


source

js_eval

def js_eval(
    expr
):

Evaluate a JS expression in the browser and return the result

js_eval("return {width: window.innerWidth}")
{'data_id': 'ee43ab10-480d-4469-9c5a-b3a63eb6f3f8', 'result': {'width': 1374}}
js_eval("return document.querySelectorAll('.msg').length")
{'data_id': 'ad5692cc-097e-498c-9adb-fca12fb12b94', 'result': 0}
js_eval("a=1")
js_eval("return a+1")
{'data_id': '3b056d62-bc76-4b23-a0be-394a336edc7b', 'result': 2}

The helpers above make one-shot round trips through the browser page, and remain the right choice for collecting single values. Channel instead holds open a persistent two-way connection, for long-lived protocols and event streams where per-message HTTP overhead would hurt.


source

Channel

def Channel(
    chan, url:NoneType=None
):

Duplex JSON messaging with the other peers on a named /wsx relay channel

A Channel connects to a named channel on solveit’s /wsx relay, which forwards every frame verbatim to all other peers on the same name. Frames are JSON dicts: pass fields as kwargs to send, and receive them as AttrDicts. Frames that answer a pending request are matched up by their id; everything else goes to the events queue. The main peer today is the solveit-chrome extension, which solvecdp drives through this class.

Two peers on one channel: a plain send arrives in the other peer’s events.

ch1,ch2 = await Channel.connect('test'),await Channel.connect('test')
await ch1.send(hello='world')
test_eq((await ch2.events.get()).hello, 'world')

request adds an id to the frame and waits for the reply with the same id, so several requests can be in flight at once. Here the second peer answers, echoing the id back with a result:

async def _responder():
    msg = await ch2.events.get()
    await ch2.send(id=msg.id, result=msg.x*2)

task = asyncio.create_task(_responder())
test_eq((await ch1.request(x=21)).result, 42)

is_open shows whether the connection is still up:

assert ch1.is_open
await ch1.close()
await ch2.close()
assert not ch1.is_open

source

display_response

def display_response(
    display:str, result:str=None
):

Return a special response where display is added as markdown/HTML to the prompt output, and result is returned to the LLM


source

realpath

async def realpath(
    subpath:str='/', # Path under data root (absolute with `/`, else relative to current dialog's folder)
)->str:

Get the real on-disk path to solveit subpath. ‘/’ gets on-disk base path.

await realpath()
await realpath() # run twice to check acache works
'/Users/jhoward'

source

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', '07_test', 'data/', 'index']
(await list_dialogs('/'))['items'][:5]
['Applications/', 'CRAFT', 'CRAFTs/', 'Desktop/', 'Documents/']

Dialog model

Kernel-side code works with dialogs through aidialog’s Dialog/Message. These subclasses declare solveit’s persisted fields (meta_attrs), so reads promote them to attributes and writes serialize them back; a solveit kernel passes Dialog to dsk.set_dlg(path, cls=...) at startup, making every ambient dsk read construct them.


source

Dialog

def Dialog(
    messages:NoneType=None, # Initial `Message`s, shared not copied: a wrap of live messages is a view over the originals
    name:str='', # Dialog name, usually the file stem
    meta:NoneType=None, # Notebook-level metadata dict, carried verbatim through save/load
):

aidialog Dialog producing dialoghelper Messages


source

Message

def Message(
    content:str='', # The message text: markdown, code, or a prompt's request
    dlg:NoneType=None, # The `Dialog` this message belongs to
    output:str='', # Jupyter-style output list (code and prompt messages), or ''
    id:NoneType=None, # Message id; 4 random hex bytes if None
    msg_type:str='code', # One of `smsg_types`: code, note, prompt, or raw
    attachments:NoneType=None, # `Attachment`s carried by the message
    meta:NoneType=None, # Remaining cell metadata, carried verbatim through save/load
    **xtras
):

aidialog Message with solveit’s persisted fields declared


source

dlg_path

def dlg_path(
    dname:str=''
):

The .ipynb file for dname (default: the current dialog)


source

data_root

def data_root():

The data directory dialog names are relative to

The tools below edit the dialog through the gateway’s cells API: each call sends the op batch its name describes (an add, deletes, merges and toggles for flags), or reads the one cell it rewrites and sends an unconditional update. Writes are blind, last one wins per field, matching the old file semantics, and solveit receives each one as a broadcast rather than a watcher diff. RUSTYGATE_URL, stamped into every gateway kernel’s environment, addresses the gateway; dh_settings['rusty'] overrides it for tests and dev. data_root still anchors dialog names to the filesystem for file-level work.


source

cells_client

def cells_client(
    dname:str=''
):

A throwaway cells client bound to dname’s notebook on the gateway, whose root is the data root

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)

source

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:

Get the message indexed in the current dialog. 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. {dname}


source

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=False, # Only return messages that have errors?
    only_exp:bool=False, # Only return messages that are exported?
    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 information

Often 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. {dname} 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+1
2
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},
 {'id': '372c363d',
  'content': "_id = await add_msg('1+1', run=True, msg_type='code')",
  'output': '',
  '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><code id="372c363d">_id = await add_msg('1+1', run=True, msg_type='code')</code></msgs>

source

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</markdown><code id="4dd4b925">     1 │ #| export
     2 │ import os,re,inspect,ast,collections,time,asyncio,json,linecache,importlib,uuid,builtins,subprocess,sys
     3 │ import websockets
     4 │ 
     5 │ from typing import Dict
     6 │ from tempfile import TemporaryDirectory
     7 │ from ipykernel_helper import *
     8 │

source

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'
    run:bool=False, # Run the message?
    *,
    placement:str='', # Location to place message. Can be 'at_start' or 'at_end', and if id provided or in curr dlg can also be 'add_after' or 'add_before'. Defaults to 'at_end' if no id and not targeting curr dlg
    id:str=None, # id of message that placement is relative to (if None, uses current message)
    dname:str='', # Dialog to add to; defaults to current dialog (`run` only has an effect if dialog is currently running)
    output:str='', # Prompt/code output; Code outputs must be .ipynb-compatible JSON array
    exported:int | None=0, # Mark message as exported (stored as nbdev `export` metadata)?
    skipped:int | None=0, # Hide message from prompt?
    i_collapsed:int | None=0, # Collapse input?
    o_collapsed:int | None=0, # Collapse output?
    heading_collapsed:int | None=0, # Collapse heading section?
    pinned:int | None=0, # Pin to context?
)->str: # Message ID of newly created message

Add/update a message to the queue to show after code execution completes, and optionally run it. Code messages are run using python’s restricted sandbox. NB: when creating multiple messages in a row, after the 1st message set id to the result of the last add_msg call, otherwise messages will appear in the dialog in REVERSE order. {dname}

_id = await add_msg('testing')
_id
'ee150d1b'

source

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:

Get message id. 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)
testing

read_msg (and all endpoints that return json) wrap responses in dict2obj, so you can use either dict or object syntax.

bmsg = await add_msg('at bottom', placement='at_end')
test_eq((await read_msg(-1, relative=False))['id'], bmsg)  # at_end put it last

source

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 │ testing
# dh_settings['dname'] = 'tmp'
# _id = await add_msg('testing', placement='at_end')
# print(_id)
# del(dh_settings['dname'])

source

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)


source

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(bmsg)
await del_msgs(_id)
['ee150d1b']
['4bd0fdaf']
_id = await add_msg('1+1', run=True, msg_type='code')
await del_msgs(_id)
['b001fd04']
_id = await add_msg('Hi', run=True, msg_type='prompt')
await del_msgs(_id)
['4561db1c']

source

run_and_prompt

async def run_and_prompt(
    code:str, # Python code to run
    prompt:str='Continue.', # Prompt to add after code execution
)->str:

Run code and then run prompt, returning the resulting message ID.


source

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. {dname}

_id = await add_msg('testing')
_id = await update_msg(_id, content='toasting')
_id = await update_msg(_id, skipped=1)

Meta updates follow Message.update semantics – meta= replaces wholesale, mergemeta= deep-merges with a None value deleting its key – and travel to the gateway as merge ops (JSON merge-patch, where null deletes):

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)
'd8addd7b'
await del_msgs(_id)
---------------------------------------------------------------------------
CancelledError                            Traceback (most recent call last)
Cell In[177], line 1
----> 1 await del_msgs(_id)

Cell In[149], line 14, in del_msgs(ids, dname, log_changed)
     10     logs = []
     11     if log_changed:
     12         by = {c['id']: c for c in await fc.cells(ids=ids)}
     13         logs = [f"> Deleted {msg_ref(i, dname)}\n\n```\n{_cell2dict(by[i])['content']}\n```" for i in ids if i in by]
---> 14     await fc.apply([dict(op='delete', id=i) for i in ids])
     15     for l in logs: await add_msg(l)
     16     return ids

File ~/aai-ws/jupyasyncclient/jupyasyncclient/files.py:166, in apply(self, ops)
    163 @patch
    164 async def apply(self:JupyAsyncCellsClient, ops):
    165     "Apply `ops` atomically, returning ids of added cells."
--> 166     m = await self._op(self.api.cells.post_cells, path=self.path, ops=ops)
    167     self.hash = m['hash']
    168     return m['added_ids']

File ~/aai-ws/jupyasyncclient/jupyasyncclient/files.py:37, in _op(self, op, **kw)
     35 "Call spec op `op` with `session_id` attached and None values dropped; a conditional-write 409 raises `HashMismatch`."
     36 kw = {k:v for k,v in dict(kw, session_id=self.session_id).items() if v is not None}
---> 37 try: return await op(**kw)
     38 except APIError as e:
     39     if e.status_code==409 and isinstance(e.raw, dict) and 'hash' in e.raw: raise HashMismatch(e.raw['hash']) from e

File ~/aai-ws/fastspec/fastspec/oapi.py:148, in __call__(self, *args, **kwargs)
    146 stream, url, headers, query, route, kw = self._prep(args, kwargs)
    147 if stream: return self._stream(url, headers=headers, query=query, route=route, **kw)
--> 148 return await self._request(url, headers=headers, query=query, route=route, **kw)

File ~/aai-ws/fastspec/fastspec/oapi.py:120, in _request(self, url, headers, query, body, route, **kwargs)
    116 @patch
    117 @delegates(AsyncTransport.request) # files, raw
    118 async def _request(self:OpFunc, url, *, headers=None, query=None, body=None, route=None, **kwargs):
    119     "Execute an HTTP request and return decoded response."
--> 120     try: return dict2obj(await self.client.request(self.verb, url, headers=headers, params=query, json=body, **kwargs))
    121     except Exception as e: self._raise_with_context(e, endpoint='', route=route, query=query, body=body)

File ~/aai-ws/fasttransport/fasttransport/core.py:58, in AsyncTransport.request(self, method, url, headers, params, json, data, files, content, raw)
     56 "Execute a request and decode JSON/text/binary response."
     57 async with self._client() as client:
---> 58     resp = await client.request(method, url, headers=self._request_headers(headers, files=files),
     59         params=params, json=json, data=data, files=files, content=content)
     60     try: resp.raise_for_status()
     61     except httpx2.HTTPStatusError as e:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1635, in AsyncClient.request(self, method, url, content, data, files, json, params, headers, cookies, auth, follow_redirects, timeout, extensions)
   1620     warnings.warn(message, DeprecationWarning, stacklevel=2)
   1622 request = self.build_request(
   1623     method=method,
   1624     url=url,
   (...)   1633     extensions=extensions,
   1634 )
-> 1635 return await self.send(request, auth=auth, follow_redirects=follow_redirects)

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1818, in AsyncClient.send(self, request, stream, auth, follow_redirects)
   1814 self._set_timeout(request)
   1816 auth = self._build_request_auth(request, auth)
-> 1818 response = await self._send_handling_auth(
   1819     request,
   1820     auth=auth,
   1821     follow_redirects=follow_redirects,
   1822     history=[],
   1823 )
   1824 try:
   1825     if not stream:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1846, in AsyncClient._send_handling_auth(self, request, auth, follow_redirects, history)
   1843 request = await auth_flow.__anext__()
   1845 while True:
-> 1846     response = await self._send_handling_redirects(
   1847         request,
   1848         follow_redirects=follow_redirects,
   1849         history=history,
   1850     )
   1851     try:
   1852         try:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1881, in AsyncClient._send_handling_redirects(self, request, follow_redirects, history)
   1878 for hook in self._event_hooks["request"]:
   1879     await hook(request)
-> 1881 response = await self._send_single_request(request)
   1882 try:
   1883     for hook in self._event_hooks["response"]:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1915, in AsyncClient._send_single_request(self, request)
   1912     raise RuntimeError("Attempted to send a sync request with an AsyncClient instance.")
   1914 with request_context(request=request):
-> 1915     response = await transport.handle_async_request(request)
   1917 assert isinstance(response.stream, AsyncByteStream)
   1918 response.request = request

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_transports/default.py:388, in AsyncHTTPTransport.handle_async_request(self, request)
    375 req = httpcore2.Request(
    376     method=request.method,
    377     url=httpcore2.URL(
   (...)    385     extensions=request.extensions,
    386 )
    387 with map_httpcore_exceptions():
--> 388     resp = await self._pool.handle_async_request(req)
    390 assert isinstance(resp.stream, typing.AsyncIterable)
    392 return Response(
    393     status_code=resp.status,
    394     headers=resp.headers,
    395     stream=AsyncResponseStream(resp.stream),
    396     extensions=resp.extensions,
    397 )

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/connection_pool.py:242, in AsyncConnectionPool.handle_async_request(self, request)
    239         closing = self._assign_requests_to_connections()
    241     await self._close_connections(closing)
--> 242     raise exc from None
    244 # Return the response. Note that in this case we still have to manage
    245 # the point at which the response is closed.
    246 assert isinstance(response.stream, typing.AsyncIterable)

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/connection_pool.py:224, in AsyncConnectionPool.handle_async_request(self, request)
    220 connection = await pool_request.wait_for_connection(timeout=timeout)
    222 try:
    223     # Send the request on the assigned connection.
--> 224     response = await connection.handle_async_request(pool_request.request)
    225 except ConnectionNotAvailable:
    226     # In some cases a connection may initially be available to
    227     # handle a request, but then become unavailable.
    228     #
    229     # In this case we clear the connection and try again.
    230     pool_request.clear_connection()

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/connection.py:96, in AsyncHTTPConnection.handle_async_request(self, request)
     93     self._connect_failed = True
     94     raise exc
---> 96 return await self._connection.handle_async_request(request)

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/http11.py:125, in AsyncHTTP11Connection.handle_async_request(self, request)
    123     async with Trace("response_closed", logger, request) as trace:
    124         await self._response_closed()
--> 125 raise exc

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/http11.py:97, in AsyncHTTP11Connection.handle_async_request(self, request)
     88     pass
     90 async with Trace("receive_response_headers", logger, request, kwargs) as trace:
     91     (
     92         http_version,
     93         status,
     94         reason_phrase,
     95         headers,
     96         trailing_data,
---> 97     ) = await self._receive_response_headers(**kwargs)
     98     trace.return_value = (
     99         http_version,
    100         status,
    101         reason_phrase,
    102         headers,
    103     )
    105 network_stream = self._network_stream

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/http11.py:167, in AsyncHTTP11Connection._receive_response_headers(self, request)
    164 timeout = timeouts.get("read", None)
    166 while True:
--> 167     event = await self._receive_event(timeout=timeout)
    168     if isinstance(event, h11.Response):
    169         break

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/http11.py:200, in AsyncHTTP11Connection._receive_event(self, timeout)
    197     event = self._h11_state.next_event()
    199 if event is h11.NEED_DATA:
--> 200     data = await self._network_stream.read(self.READ_NUM_BYTES, timeout=timeout)
    202     # If we feed this case through h11 we'll raise an exception like:
    203     #
    204     #     httpcore2.RemoteProtocolError: can't handle event type
   (...)    208     # perspective. Instead we handle this case distinctly and treat
    209     # it as a ConnectError.
    210     if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_backends/anyio.py:37, in AnyIOStream.read(self, max_bytes, timeout)
     35 with anyio.fail_after(timeout):
     36     try:
---> 37         return await self._stream.receive(max_bytes=max_bytes)
     38     except anyio.EndOfStream:  # pragma: no cover
     39         return b""

File ~/aai-ws/.venv/lib/python3.13/site-packages/anyio/_backends/_asyncio.py:1337, in SocketStream.receive(self, max_bytes)
   1331 if (
   1332     not self._protocol.read_event.is_set()
   1333     and not self._transport.is_closing()
   1334     and not self._protocol.is_at_eof
   1335 ):
   1336     self._transport.resume_reading()
-> 1337     await self._protocol.read_event.wait()
   1338     self._transport.pause_reading()
   1339 else:

File ~/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/asyncio/locks.py:213, in Event.wait(self)
    211 self._waiters.append(fut)
    212 try:
--> 213     await fut
    214     return True
    215 finally:

CancelledError: 
_edit_id = await add_msg('This message should be found.\n\nThis is a multiline message.')
_edit_id
'ca8a365a'

This message should be found.

This is a multiline message.

This message should be found.

This is a multiline message.

This message should be found.

This is a multiline message.

This message should be found.

This is a multiline message.

print((await read_msg())['content'])
---------------------------------------------------------------------------
CancelledError                            Traceback (most recent call last)
Cell In[181], line 1
----> 1 print((await read_msg())['content'])

Cell In[107], line 24, in read_msg(n, relative, id, start_line, end_line, nums, lnhashs, dname)
     20     fc = cells_client(dname)
     21     if relative:
     22         if not id: id = aidialog.dlgskill.cur_msgid
     23         if not id: return {'msg':None}
---> 24         cells = await fc.cells(ids=id, context=abs(n) or None)
     25         anchor = first(i for i,c in enumerate(cells) if c['id']==id)
     26         if anchor is None: return {'msg':None}
     27         if not 0<=anchor+n<len(cells): return {'msg':None}

File ~/aai-ws/jupyasyncclient/jupyasyncclient/files.py:147, in cells(self, ids, idx, q, cell_type, meta, meta_not, limit, context, fields)
    134 @patch
    135 async def cells(self:JupyAsyncCellsClient,
    136     ids=None, # Cell ids to keep: comma-separated str, or a list
   (...)    144     fields=None, # Comma-separated extras: 'hashes', 'meta', 'attachments'
    145 ):
    146     "The notebook's cells in document order, optionally selected and filtered (the gateway's cells GET stages)."
--> 147     m = await self._op(self.api.cells.get_cells, path=self.path, ids=_cs(ids), idx=_cs(idx), q=q, cell_type=cell_type,
    148         meta=None if meta is None else json.dumps(meta), meta_not=None if meta_not is None else json.dumps(meta_not),
    149         limit=limit, context=context, fields=fields)
    150     self.hash = m['hash']
    151     self.matched = m.get('matched')

File ~/aai-ws/jupyasyncclient/jupyasyncclient/files.py:37, in _op(self, op, **kw)
     35 "Call spec op `op` with `session_id` attached and None values dropped; a conditional-write 409 raises `HashMismatch`."
     36 kw = {k:v for k,v in dict(kw, session_id=self.session_id).items() if v is not None}
---> 37 try: return await op(**kw)
     38 except APIError as e:
     39     if e.status_code==409 and isinstance(e.raw, dict) and 'hash' in e.raw: raise HashMismatch(e.raw['hash']) from e

File ~/aai-ws/fastspec/fastspec/oapi.py:148, in __call__(self, *args, **kwargs)
    146 stream, url, headers, query, route, kw = self._prep(args, kwargs)
    147 if stream: return self._stream(url, headers=headers, query=query, route=route, **kw)
--> 148 return await self._request(url, headers=headers, query=query, route=route, **kw)

File ~/aai-ws/fastspec/fastspec/oapi.py:120, in _request(self, url, headers, query, body, route, **kwargs)
    116 @patch
    117 @delegates(AsyncTransport.request) # files, raw
    118 async def _request(self:OpFunc, url, *, headers=None, query=None, body=None, route=None, **kwargs):
    119     "Execute an HTTP request and return decoded response."
--> 120     try: return dict2obj(await self.client.request(self.verb, url, headers=headers, params=query, json=body, **kwargs))
    121     except Exception as e: self._raise_with_context(e, endpoint='', route=route, query=query, body=body)

File ~/aai-ws/fasttransport/fasttransport/core.py:58, in AsyncTransport.request(self, method, url, headers, params, json, data, files, content, raw)
     56 "Execute a request and decode JSON/text/binary response."
     57 async with self._client() as client:
---> 58     resp = await client.request(method, url, headers=self._request_headers(headers, files=files),
     59         params=params, json=json, data=data, files=files, content=content)
     60     try: resp.raise_for_status()
     61     except httpx2.HTTPStatusError as e:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1635, in AsyncClient.request(self, method, url, content, data, files, json, params, headers, cookies, auth, follow_redirects, timeout, extensions)
   1620     warnings.warn(message, DeprecationWarning, stacklevel=2)
   1622 request = self.build_request(
   1623     method=method,
   1624     url=url,
   (...)   1633     extensions=extensions,
   1634 )
-> 1635 return await self.send(request, auth=auth, follow_redirects=follow_redirects)

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1818, in AsyncClient.send(self, request, stream, auth, follow_redirects)
   1814 self._set_timeout(request)
   1816 auth = self._build_request_auth(request, auth)
-> 1818 response = await self._send_handling_auth(
   1819     request,
   1820     auth=auth,
   1821     follow_redirects=follow_redirects,
   1822     history=[],
   1823 )
   1824 try:
   1825     if not stream:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1846, in AsyncClient._send_handling_auth(self, request, auth, follow_redirects, history)
   1843 request = await auth_flow.__anext__()
   1845 while True:
-> 1846     response = await self._send_handling_redirects(
   1847         request,
   1848         follow_redirects=follow_redirects,
   1849         history=history,
   1850     )
   1851     try:
   1852         try:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1881, in AsyncClient._send_handling_redirects(self, request, follow_redirects, history)
   1878 for hook in self._event_hooks["request"]:
   1879     await hook(request)
-> 1881 response = await self._send_single_request(request)
   1882 try:
   1883     for hook in self._event_hooks["response"]:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_client.py:1915, in AsyncClient._send_single_request(self, request)
   1912     raise RuntimeError("Attempted to send a sync request with an AsyncClient instance.")
   1914 with request_context(request=request):
-> 1915     response = await transport.handle_async_request(request)
   1917 assert isinstance(response.stream, AsyncByteStream)
   1918 response.request = request

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpx2/_transports/default.py:388, in AsyncHTTPTransport.handle_async_request(self, request)
    375 req = httpcore2.Request(
    376     method=request.method,
    377     url=httpcore2.URL(
   (...)    385     extensions=request.extensions,
    386 )
    387 with map_httpcore_exceptions():
--> 388     resp = await self._pool.handle_async_request(req)
    390 assert isinstance(resp.stream, typing.AsyncIterable)
    392 return Response(
    393     status_code=resp.status,
    394     headers=resp.headers,
    395     stream=AsyncResponseStream(resp.stream),
    396     extensions=resp.extensions,
    397 )

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/connection_pool.py:242, in AsyncConnectionPool.handle_async_request(self, request)
    239         closing = self._assign_requests_to_connections()
    241     await self._close_connections(closing)
--> 242     raise exc from None
    244 # Return the response. Note that in this case we still have to manage
    245 # the point at which the response is closed.
    246 assert isinstance(response.stream, typing.AsyncIterable)

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/connection_pool.py:224, in AsyncConnectionPool.handle_async_request(self, request)
    220 connection = await pool_request.wait_for_connection(timeout=timeout)
    222 try:
    223     # Send the request on the assigned connection.
--> 224     response = await connection.handle_async_request(pool_request.request)
    225 except ConnectionNotAvailable:
    226     # In some cases a connection may initially be available to
    227     # handle a request, but then become unavailable.
    228     #
    229     # In this case we clear the connection and try again.
    230     pool_request.clear_connection()

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/connection.py:96, in AsyncHTTPConnection.handle_async_request(self, request)
     93     self._connect_failed = True
     94     raise exc
---> 96 return await self._connection.handle_async_request(request)

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/http11.py:125, in AsyncHTTP11Connection.handle_async_request(self, request)
    123     async with Trace("response_closed", logger, request) as trace:
    124         await self._response_closed()
--> 125 raise exc

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/http11.py:97, in AsyncHTTP11Connection.handle_async_request(self, request)
     88     pass
     90 async with Trace("receive_response_headers", logger, request, kwargs) as trace:
     91     (
     92         http_version,
     93         status,
     94         reason_phrase,
     95         headers,
     96         trailing_data,
---> 97     ) = await self._receive_response_headers(**kwargs)
     98     trace.return_value = (
     99         http_version,
    100         status,
    101         reason_phrase,
    102         headers,
    103     )
    105 network_stream = self._network_stream

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/http11.py:167, in AsyncHTTP11Connection._receive_response_headers(self, request)
    164 timeout = timeouts.get("read", None)
    166 while True:
--> 167     event = await self._receive_event(timeout=timeout)
    168     if isinstance(event, h11.Response):
    169         break

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_async/http11.py:200, in AsyncHTTP11Connection._receive_event(self, timeout)
    197     event = self._h11_state.next_event()
    199 if event is h11.NEED_DATA:
--> 200     data = await self._network_stream.read(self.READ_NUM_BYTES, timeout=timeout)
    202     # If we feed this case through h11 we'll raise an exception like:
    203     #
    204     #     httpcore2.RemoteProtocolError: can't handle event type
   (...)    208     # perspective. Instead we handle this case distinctly and treat
    209     # it as a ConnectError.
    210     if data == b"" and self._h11_state.their_state == h11.SEND_RESPONSE:

File ~/aai-ws/.venv/lib/python3.13/site-packages/httpcore2/_backends/anyio.py:37, in AnyIOStream.read(self, max_bytes, timeout)
     35 with anyio.fail_after(timeout):
     36     try:
---> 37         return await self._stream.receive(max_bytes=max_bytes)
     38     except anyio.EndOfStream:  # pragma: no cover
     39         return b""

File ~/aai-ws/.venv/lib/python3.13/site-packages/anyio/_backends/_asyncio.py:1337, in SocketStream.receive(self, max_bytes)
   1331 if (
   1332     not self._protocol.read_event.is_set()
   1333     and not self._transport.is_closing()
   1334     and not self._protocol.is_at_eof
   1335 ):
   1336     self._transport.resume_reading()
-> 1337     await self._protocol.read_event.wait()
   1338     self._transport.pause_reading()
   1339 else:

File ~/.local/share/uv/python/cpython-3.13.15-macos-aarch64-none/lib/python3.13/asyncio/locks.py:213, in Event.wait(self)
    211 self._waiters.append(fut)
    212 try:
--> 213     await fut
    214     return True
    215 finally:

CancelledError: 
print((await read_msg(n=0, id=_edit_id, nums=True))['content'])
print((await read_msg(n=0, id=_edit_id, nums=True, start_line=2, end_line=3))['content'])

source

run_msg

async def run_msg(
    ids:str=None, # Comma-separated ids of message(s) to execute
    dname:str='', # Running dialog to get info for; defaults to current dialog. (Note dialog *must* be running for this function)
):

Adds a message to the run queue. Use read_msg to see the output once it runs.

codeid = (await read_msg())['id']
await run_msg(codeid)

source

copy_msgs

async def copy_msgs(
    ids:str=None, # Comma-separated ids of message(s) to copy
    cut:bool=False, # Cut message(s)? (If not, copies)
    dname:str='', # Running dialog to copy messages from; defaults to current dialog. (Note dialog *must* be running for this function)
)->dict:

Add ids to clipboard.


source

paste_msgs

async def paste_msgs(
    id:str=None, # Message id to paste next to
    after:bool=True, # Paste after id? (If not, pastes before)
    dname:str='', # Running dialog to copy messages from; defaults to current dialog. (Note dialog *must* be running for this function)
)->dict:

Paste clipboard msg(s) after/before the current selected msg (id).

await copy_msgs(codeid)
with expect_fail(DialogAPIError, contains='Dialog /dlg/nonexistent may not be running'):
    await copy_msgs('_fake', dname='/dlg/nonexistent')
tgt = (await read_msg())['id']
await paste_msgs(tgt)
# TODO fix so this passes
# with expect_fail(DialogAPIError, contains='Dialog /dlg/nonexistent may not be running'):
#     await paste_msgs('_fake', dname='/dlg/nonexistent')
newmsg = await read_msg(1, id=tgt)
newmsg['content']
await del_msgs(newmsg['id'])

source

enable_mermaid

def enable_mermaid():
enable_mermaid()

source

mermaid

def mermaid(
    code, cls:str='mermaid', **kwargs
):

A mermaid diagram

mermaid('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]

source

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


source

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


source

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


source

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
with expect_fail(contains='such file'):
    await toggle_comment('_fake', dname='/dlg/nonexistent')

test header

header end

hdid = (await read_msg())['id']
await toggle_header(hdid)
with expect_fail(APIError, contains='no such file'):
    await toggle_header('_fake', dname='/dlg/nonexistent')

Dlg conveniences


source

url2note

async def url2note(
    url:str, # URL to read
    extract_section:bool=True, # If url has an anchor, return only that section
    selector:str=None, # Select section(s) using BeautifulSoup.select (overrides extract_section)
    ai_img:bool=True, # Make images visible to the AI
    split_re:str='', # Regex to split content into multiple notes, set to '' for single note
):

Read URL as markdown, and add note(s) below current message with the result

_id = await url2note('https://docs.python.org')
await del_msgs(_id)

source

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 '/')
    template:bool=True, # Include TEMPLATE.ipynb files when creating a new dialog
):

Create a new dialog, or set an existing one running

await create_or_run_dialog('test_dialog')

source

restart_dialog

async def restart_dialog(
    name:str, # Name/path of the dialog (relative to current dialog's folder, or absolute if starts with '/')
):

Restart a dialog’s kernel, starting the dialog first if needed

await restart_dialog('test_dialog')

source

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 running dialog kernel

await stop_dialog('test_dialog')

source

load_dialog

async def load_dialog(
    src_dname:str, # Dialog to load code from (path relative to solveit data dir, no .ipynb)
    dname:str='', # Target dialog; defaults to current dialog
):

Run all code messages from src_dname into the target dialog’s kernel and return dialog contents. Do not call from python; use directly as an LLM tool.


source

rm_dialog

async def rm_dialog(
    name:str, # Name/path of the dialog to delete (relative to current dialog's folder, or absolute if starts with '/')
):

Delete a dialog (or folder) and associated records, stopping the kernel if running

await rm_dialog('test_dialog')

source

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.

execute on these classes replaces aidialog’s CaptureShell runner: in a solveit kernel there is no point running captured and thrown away, so it asks the server to run the messages the normal way – same attribution, spinner, streaming card updates, and disk write as a user-initiated run. A call from inside a tool would deadlock on the main shell’s serial queue, so the kernel’s persistent sidecar subshell gives those nested executions their own serial lane while the calling cell awaits; the awaited response returns each message’s output. Called through the py tool, the whole run happens inside safepyrun’s audit window, so AI-initiated runs stay sandboxed with no extra machinery.


source

Dialog.execute

async def execute(
    *ids, above:bool=False, # Include each matched cell and all cells above it?
    below:bool=False, # Include each matched cell and all cells below it?
    all:bool=False, # Include all code cells (ignores `msgids`)?
    exported:bool=False, # Only cells with `#| export` or `#| exports`?
    default_eval:bool=True, # Participation default when neither the cell nor the notebook has an `eval` directive
    ignore_eval:bool=False, # Skip `eval` filtering entirely: every selected cell runs
):

Run code messages in the current dialog’s kernel, with normal run semantics; returns their outputs


source

Message.execute

async def execute():

Run this message in the current dialog’s kernel, with normal run semantics; returns its output

d = Dialog(name='mm')
m = d.mk_message('x', msg_type='note', meta=dict(hide_input=True, bookmark=3))
test_eq((m.i_collapsed, m.bookmark), (True, 3))  # declared fields promote at construction
m.i_collapsed = 1
test_eq(m.cell_meta(), dict(hide_input=True, bookmark=3))  # ints demote as booleans, keys per the file convention

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'))
print((await read_msg(n=0, id=_edit_id, nums=True))['content'])
print(await msg_str_replace(_edit_id, 'This should go to the first line', 'This should go to the 1st line'))
for f in (msg_str_replace, msg_strs_replace):
    assert 'use_regex' in inspect.signature(f).parameters
print((await read_msg(n=0, id=_edit_id, nums=True))['content'])
print(await msg_strs_replace(_edit_id, ['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'])
print(await msg_replace_lines(_edit_id, 2, 4,'line 2\nline 3\nline 4\n'))
print((await read_msg(n=0, id=_edit_id, nums=True))['content'])
print(await msg_del_lines(_edit_id, 2, 4))
print((await read_msg(n=0, id=_edit_id, nums=True)).content)
await del_msgs(_edit_id)
id1 = await add_msg('hello world')
id2 = await add_msg('hello there', id=id1)
results = await msg_str_replace([id1, id2], 'hello', 'hi')
print(results)
await del_msgs(id1)
await del_msgs(id2)

Help


source

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.


source

dialog_link(msg_id='a7d82acd')
dialog_link(dname='/CRAFT')
dialog_link(dname='/CRAFT', msg_id='ce727fd8')

source

spawn_agent

async def spawn_agent(
    prompt:str
):

Spawn a subagent to complete a task defined by prompt. Must be run as a tool - not from Python. The subagent’s context and tools is defined by the parent prompt’s history