Extending
One section per extension point the repo actually exposes.
Pluggable text renderer (text_renderer=)#
panel(), render_transcript(), and render_output_field() (streamlit_coco/ui.py, streamlit_coco/display.py) accept text_renderer= so an app can render assistant/user text with any Streamlit text API, or a custom callable, instead of the hardcoded st.markdown default.
Named values: "markdown" (default), "write", "text", "caption", "code" (also accepts st.*-prefixed names). Passing an unknown name raises a ValueError listing the known names.
Callable form: pass any Callable[[str], None]-shaped function — it is invoked with the message text.
Files to touch:
streamlit_coco/text_renderer.py— the name → callable resolution table.Add a new named value here if extending the built-in set.
- Call sites in
streamlit_coco/ui.py(panel) andstreamlit_coco/display.py(
render_transcript,render_output_field) already threadtext_rendererthrough; a new renderer only needs to be registered once.
Regenerate / verify step: run examples/chat_app.py (make chat) with the new renderer passed explicitly and confirm assistant/user text renders as expected; no build step is needed since this is pure Python.
Tests to add: extend doc/features/text-renderer/test-checklist.md with the new named value, and add a case to the relevant unit test file covering renderer resolution (there is no dedicated test_text_renderer.py in the current tree — check tests/ for the closest existing coverage before adding a new file).
Tool family / tool name dispatch#
streamlit_coco/tool_names.py maps a raw tool name to a ToolFamily enum (ASK_USER, SQL, READ, WRITE, EDIT, BASH, GLOB, GREP, EXIT_PLAN, GENERIC) via tool_family(name), using a normalized (lowercased, separator-stripped) lookup table. Any tool name that does not match a known family — including MCP tools — falls through to GENERIC and gets a generic tool card instead of a family-specific one.
Files to touch:
streamlit_coco/tool_names.py— add the tool's normalized name(s) to_FAMILY_BY_NORMALIZED, and add a case tofamily_label()if it needs a dedicated header label instead of the raw tool name.streamlit_coco/tool_cards.py/streamlit_coco/tool_extract.py— addthe family-specific card rendering and any input/diff/SQL extraction the new family needs. A family left unregistered here still renders (via the
GENERICfallback); registering it only changes how it is displayed.
Regenerate / verify step: run a chat demo (make chat) with a prompt that exercises the new tool and confirm the tool card renders with the expected family label and preview instead of the generic fallback.
Tests to add: a case in the unit tests covering tool_family() / family_label() for the new tool name (see the existing tool-family coverage pattern in tests/), and an NDJSON fixture under tests/fixtures/ndjson/ if the new family needs event-normalization coverage in test_ndjson_fixtures.py.
App Builder types/<id>/ catalog and skill packs#
Each App Builder app type is a folder under examples/app_builder/types/:
types/<id>/
type.json catalog card: id, name, icon, users, needs,
needs_snowflake, topics, grounding_kind, brief
(context, enables, does_not, questions)
SKILL.md optional — type-specific layout + data rules,
read by CoCo via add_dirs when generating the app
reference/ optional — supporting reference material for SKILL.md
CHECKLIST.md optional — self-check list the generate prompt
runs against once after a Write, before stopping
types/shared/SKILL.md generation rules for every type (no type.json —
not a catalog card); every Build/regenerate prompt
tells CoCo to read this file plus the type's own
SKILL.md (both mounted via add_dirs)This is a content-level extension point: adding a type does not require a Python code change, only a new types/<id>/ folder (or using the Admin screen's type editor, which writes the same files).
Files to touch:
examples/app_builder/types/<id>/type.json— required; follow the shapeof an existing type (e.g.
types/call-transcription/type.json) for thebrief.questionsstructure and thegrounding_kindvalues (tabular/semantic_view/documents/urls) that decide whether the profile step is a deterministic pandas parser (examples/app_builder/engine/profile.py) or a CoCo job.examples/app_builder/types/<id>/SKILL.md— optional but expected for areal type; without it, CoCo generates only against the shared rules.
examples/app_builder/engine/paths.py— no change needed;TYPES_DIRalready points at
types/, so a new folder is picked up automatically.
Regenerate / verify step: make app-builder, open Library, confirm the new type card appears (filtered correctly by its topics), fill a brief, and Build — confirm the generated streamlit_app.py follows the new SKILL.md's rules and (if a CHECKLIST.md is present) that the generate prompt self-checks against it once.
Tests to add: a case in test_app_builder_catalog.py covering the new type.json parses and validates against the catalog card fields the Library/Brief/Admin pages read; a case in test_app_builder_profile.py if grounding_kind triggers a new deterministic profile path.
Adding a new example app#
The established pattern for a rail-based example (as in examples/bi_to_semantic/ and examples/app_builder/) composes copilot_rail() for the Copilot and app_viewer() for a live preview of whatever CoCo writes, wired together through on_fix / job queueing — see architecture.
Files to touch:
examples/<name>/— new folder with its ownapp.py(or multipageapp_pages/), following the composition pattern of an existing example closest to the new one's shape (backlog_desk/for a no-SQL rail example;bi_to_semantic/orapp_builder/for a rail + preview example).Makefile— add a<name>:target that runs the new example (mirror anexisting target, e.g.
bi-semantic:), add it to.PHONY, and add a line tohelp.doc/deployment/local.md§5 anddoc/testing.md— add the newmaketarget to the examples table so it is discoverable from the docs that already list them.
Regenerate / verify step: make <name> runs and reaches a usable screen; if the example uses app_viewer(), confirm the preview port does not collide with an existing example's fixed port (chat/backlog examples do not use app_viewer; bi_to_semantic uses :8511/:8512; app_builder uses :8513).
Tests to add: at minimum a smoke test parallel to test_smoke.py's coverage of the existing examples, and a feature-doc checklist under doc/features/<feature>/test-checklist.md if the example exercises new UI behavior rather than only recomposing existing primitives.