feat(coding-agent): enabled docs:// protocol and custom tool discovery from .omp/tools/

- Added docs:// internal URL protocol handler for accessing embedded markdown documentation files with path traversal validation.
- Added support for executable tool files (.ts, .js, .sh, .bash, .py) in custom tools discovery from .omp/tools/ and .claude/tools/ directories.
- Added generate-docs-index build script to automatically scan and embed markdown documentation files at build time.
- Updated system prompt to document docs:// protocol usage and identify agent as operating within Oh My Pi harness.
This commit is contained in:
can1357
2026-02-19 22:19:00 +01:00
parent 7ce3bb6906
commit 51ec9bd05d
16 changed files with 307 additions and 15 deletions
+1
View File
@@ -44,6 +44,7 @@
"!**/node_modules/**/*",
"!**/test-sessions.ts",
"!**/template.generated.ts",
"!**/docs-index.generated.ts",
"!**/gen/agent_pb.ts",
"!.worktrees/**/*"
]
+3 -2
View File
@@ -20,7 +20,7 @@
"fmt:ts": "biome format --write .",
"fmt:rs": "cargo fmt --all",
"fix": "bun run fix:ts && bun run fix:rs",
"fix:ts": "biome check --write --unsafe . && bun --cwd=packages/coding-agent run format-prompts",
"fix:ts": "biome check --write --unsafe . && bun --cwd=packages/coding-agent run format-prompts && bun --cwd=packages/coding-agent run generate-docs-index",
"fix:rs": "cargo clippy --fix --allow-dirty --all-targets --no-deps --allow-staged --broken-code --allow-no-vcs && cargo fmt --all",
"build:native": "bun --cwd=packages/natives run build:native",
"dev:native": "bun --cwd=packages/natives run dev:native",
@@ -34,7 +34,8 @@
"publish": "bun run prepublishOnly && npm publish -ws --access public",
"publish:dry": "bun run prepublishOnly && npm publish -ws --access public --dry-run",
"release": "bun scripts/release.ts",
"generate-models": "bun --cwd=packages/ai scripts/generate-models.ts"
"generate-models": "bun --cwd=packages/ai scripts/generate-models.ts",
"generate-docs-index": "bun --cwd=packages/coding-agent run generate-docs-index",
},
"devDependencies": {
"@biomejs/biome": "^2.4.2",
+7
View File
@@ -1,8 +1,12 @@
# Changelog
## [Unreleased]
### Added
- Support for `docs://` internal URL protocol to access embedded documentation files (e.g., `docs://sdk.md`)
- Added `generate-docs-index` npm script to automatically index and embed documentation files at build time
- Support for executable tool files (.ts, .js, .sh, .bash, .py) in custom tools discovery alongside markdown files
- Display streamed tool intent in working message during agent execution
- Added `tools.intentTracing` setting to enable intent tracing, which asks the agent to describe the intent of each tool call before executing it
- Support for file deletion in hashline edit mode via `delete: true` parameter
@@ -12,6 +16,9 @@
### Changed
- System prompt now identifies agent as operating inside Oh My Pi harness and instructs reading docs:// URLs for omp/pi topics
- Tool discovery now accepts executable script extensions (.ts, .js, .sh, .bash, .py) in addition to .json and .md files
- Updated bash and read tool documentation to reference `docs://` URL support
- Hashline format separator changed from pipe (`|`) to colon (`:`) for improved readability (e.g., `LINE#ID:content` instead of `LINE#ID|content`)
- Hashline hash representation changed from 4-character base36 to 2-character hexadecimal for more compact line references
- Hashline edit API: renamed `delete` parameter to `rm` for consistency with standard file operations
+1
View File
@@ -78,6 +78,7 @@
"scripts": {
"check": "tsgo -p tsconfig.json",
"format-prompts": "bun scripts/format-prompts.ts",
"generate-docs-index": "bun scripts/generate-docs-index.ts",
"build:binary": "cd ../.. && bun --cwd=packages/stats scripts/generate-client-bundle.ts && bun --cwd=packages/natives run embed:native && bun build --compile --define PI_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset && bun --cwd=packages/stats scripts/generate-client-bundle.ts --reset",
"generate-template": "bun scripts/generate-template.ts",
"test": "bun test"
@@ -0,0 +1,56 @@
#!/usr/bin/env bun
import { Glob } from "bun";
import * as path from "node:path";
const docsDir = new URL("../../../docs/", import.meta.url).pathname;
const outputPath = new URL("../src/internal-urls/docs-index.generated.ts", import.meta.url).pathname;
const importBase = "../../../../docs";
function toIdentifier(relativePath: string): string {
const withoutExt = relativePath.replace(/\.md$/i, "");
const parts = withoutExt
.split(/[^a-zA-Z0-9]+/)
.filter(Boolean)
.map((part, index) => {
if (index === 0) {
return part.toLowerCase();
}
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
});
const base = parts.length > 0 ? parts.join("") : "doc";
const safeBase = /^[0-9]/.test(base) ? `doc${base}` : base;
return `${safeBase}Md`;
}
const glob = new Glob("**/*.md");
const entries: string[] = [];
for await (const relativePath of glob.scan(docsDir)) {
entries.push(relativePath.split(path.sep).join("/"));
}
entries.sort();
const usedIdentifiers = new Set<string>();
const docs = entries.map((relativePath) => {
let identifier = toIdentifier(relativePath);
let suffix = 2;
while (usedIdentifiers.has(identifier)) {
identifier = `${toIdentifier(relativePath)}${suffix}`;
suffix++;
}
usedIdentifiers.add(identifier);
return { relativePath, identifier };
});
docs.sort((a, b) => a.identifier.localeCompare(b.identifier) || a.relativePath.localeCompare(b.relativePath));
const imports = docs
.map(({ relativePath, identifier }) => `import ${identifier} from "${importBase}/${relativePath}" with { type: "text" };`)
.join("\n");
const mapEntries = docs.map(({ relativePath, identifier }) => `\t"${relativePath}": ${identifier},`).join("\n");
const output = `// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT\n\n${imports}\n\nexport const EMBEDDED_DOCS: Readonly<Record<string, string>> = {\n${mapEntries}\n};\n\nexport const EMBEDDED_DOC_FILENAMES = Object.keys(EMBEDDED_DOCS).sort();\n`;
await Bun.write(outputPath, output);
console.log(`Generated ${path.relative(process.cwd(), outputPath)} (${docs.length} docs)`);
+14 -4
View File
@@ -656,7 +656,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
fileLoadPromises.push(
loadFilesFromDir<CustomTool>(ctx, toolsDir, PROVIDER_ID, level, {
extensions: ["json", "md"],
extensions: ["json", "md", "ts", "js", "sh", "bash", "py"],
transform: (name, content, path, source) => {
if (name.endsWith(".json")) {
const data = parseJSON<{ name?: string; description?: string }>(content);
@@ -668,11 +668,21 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
_source: source,
};
}
const { frontmatter } = parseFrontmatter(content, { source: path });
if (name.endsWith(".md")) {
const { frontmatter } = parseFrontmatter(content, { source: path });
return {
name: (frontmatter.name as string) || name.replace(/\.md$/, ""),
path,
description: frontmatter.description as string | undefined,
level,
_source: source,
};
}
// Executable tool files (.ts, .js, .sh, .bash, .py)
const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
return {
name: (frontmatter.name as string) || name.replace(/\.md$/, ""),
name: toolName,
path,
description: frontmatter.description as string | undefined,
level,
_source: source,
};
@@ -0,0 +1,101 @@
// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT
import bashToolRuntimeMd from "../../../../docs/bash-tool-runtime.md" with { type: "text" };
import blobArtifactArchitectureMd from "../../../../docs/blob-artifact-architecture.md" with { type: "text" };
import compactionMd from "../../../../docs/compaction.md" with { type: "text" };
import configUsageMd from "../../../../docs/config-usage.md" with { type: "text" };
import customToolsMd from "../../../../docs/custom-tools.md" with { type: "text" };
import environmentVariablesMd from "../../../../docs/environment-variables.md" with { type: "text" };
import extensionLoadingMd from "../../../../docs/extension-loading.md" with { type: "text" };
import extensionsMd from "../../../../docs/extensions.md" with { type: "text" };
import fsScanCacheArchitectureMd from "../../../../docs/fs-scan-cache-architecture.md" with { type: "text" };
import geminiManifestExtensionsMd from "../../../../docs/gemini-manifest-extensions.md" with { type: "text" };
import handoffGenerationPipelineMd from "../../../../docs/handoff-generation-pipeline.md" with { type: "text" };
import hooksMd from "../../../../docs/hooks.md" with { type: "text" };
import mcpProtocolTransportsMd from "../../../../docs/mcp-protocol-transports.md" with { type: "text" };
import mcpRuntimeLifecycleMd from "../../../../docs/mcp-runtime-lifecycle.md" with { type: "text" };
import mcpServerToolAuthoringMd from "../../../../docs/mcp-server-tool-authoring.md" with { type: "text" };
import modelsMd from "../../../../docs/models.md" with { type: "text" };
import nativesAddonLoaderRuntimeMd from "../../../../docs/natives-addon-loader-runtime.md" with { type: "text" };
import nativesArchitectureMd from "../../../../docs/natives-architecture.md" with { type: "text" };
import nativesBindingContractMd from "../../../../docs/natives-binding-contract.md" with { type: "text" };
import nativesBuildReleaseDebuggingMd from "../../../../docs/natives-build-release-debugging.md" with { type: "text" };
import nativesMediaSystemUtilsMd from "../../../../docs/natives-media-system-utils.md" with { type: "text" };
import nativesRustTaskCancellationMd from "../../../../docs/natives-rust-task-cancellation.md" with { type: "text" };
import nativesShellPtyProcessMd from "../../../../docs/natives-shell-pty-process.md" with { type: "text" };
import nativesTextSearchPipelineMd from "../../../../docs/natives-text-search-pipeline.md" with { type: "text" };
import nonCompactionRetryPolicyMd from "../../../../docs/non-compaction-retry-policy.md" with { type: "text" };
import notebookToolRuntimeMd from "../../../../docs/notebook-tool-runtime.md" with { type: "text" };
import pluginManagerInstallerPlumbingMd from "../../../../docs/plugin-manager-installer-plumbing.md" with { type: "text" };
import portingFromPiMonoMd from "../../../../docs/porting-from-pi-mono.md" with { type: "text" };
import portingToNativesMd from "../../../../docs/porting-to-natives.md" with { type: "text" };
import providerStreamingInternalsMd from "../../../../docs/provider-streaming-internals.md" with { type: "text" };
import pythonReplMd from "../../../../docs/python-repl.md" with { type: "text" };
import rpcMd from "../../../../docs/rpc.md" with { type: "text" };
import rulebookMatchingPipelineMd from "../../../../docs/rulebook-matching-pipeline.md" with { type: "text" };
import sdkMd from "../../../../docs/sdk.md" with { type: "text" };
import secretsMd from "../../../../docs/secrets.md" with { type: "text" };
import sessionMd from "../../../../docs/session.md" with { type: "text" };
import sessionOperationsExportShareForkResumeMd from "../../../../docs/session-operations-export-share-fork-resume.md" with { type: "text" };
import sessionSwitchingAndRecentListingMd from "../../../../docs/session-switching-and-recent-listing.md" with { type: "text" };
import sessionTreePlanMd from "../../../../docs/session-tree-plan.md" with { type: "text" };
import skillsMd from "../../../../docs/skills.md" with { type: "text" };
import slashCommandInternalsMd from "../../../../docs/slash-command-internals.md" with { type: "text" };
import taskAgentDiscoveryMd from "../../../../docs/task-agent-discovery.md" with { type: "text" };
import themeMd from "../../../../docs/theme.md" with { type: "text" };
import treeMd from "../../../../docs/tree.md" with { type: "text" };
import ttsrInjectionLifecycleMd from "../../../../docs/ttsr-injection-lifecycle.md" with { type: "text" };
import tuiMd from "../../../../docs/tui.md" with { type: "text" };
import tuiRuntimeInternalsMd from "../../../../docs/tui-runtime-internals.md" with { type: "text" };
export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
"bash-tool-runtime.md": bashToolRuntimeMd,
"blob-artifact-architecture.md": blobArtifactArchitectureMd,
"compaction.md": compactionMd,
"config-usage.md": configUsageMd,
"custom-tools.md": customToolsMd,
"environment-variables.md": environmentVariablesMd,
"extension-loading.md": extensionLoadingMd,
"extensions.md": extensionsMd,
"fs-scan-cache-architecture.md": fsScanCacheArchitectureMd,
"gemini-manifest-extensions.md": geminiManifestExtensionsMd,
"handoff-generation-pipeline.md": handoffGenerationPipelineMd,
"hooks.md": hooksMd,
"mcp-protocol-transports.md": mcpProtocolTransportsMd,
"mcp-runtime-lifecycle.md": mcpRuntimeLifecycleMd,
"mcp-server-tool-authoring.md": mcpServerToolAuthoringMd,
"models.md": modelsMd,
"natives-addon-loader-runtime.md": nativesAddonLoaderRuntimeMd,
"natives-architecture.md": nativesArchitectureMd,
"natives-binding-contract.md": nativesBindingContractMd,
"natives-build-release-debugging.md": nativesBuildReleaseDebuggingMd,
"natives-media-system-utils.md": nativesMediaSystemUtilsMd,
"natives-rust-task-cancellation.md": nativesRustTaskCancellationMd,
"natives-shell-pty-process.md": nativesShellPtyProcessMd,
"natives-text-search-pipeline.md": nativesTextSearchPipelineMd,
"non-compaction-retry-policy.md": nonCompactionRetryPolicyMd,
"notebook-tool-runtime.md": notebookToolRuntimeMd,
"plugin-manager-installer-plumbing.md": pluginManagerInstallerPlumbingMd,
"porting-from-pi-mono.md": portingFromPiMonoMd,
"porting-to-natives.md": portingToNativesMd,
"provider-streaming-internals.md": providerStreamingInternalsMd,
"python-repl.md": pythonReplMd,
"rpc.md": rpcMd,
"rulebook-matching-pipeline.md": rulebookMatchingPipelineMd,
"sdk.md": sdkMd,
"secrets.md": secretsMd,
"session.md": sessionMd,
"session-operations-export-share-fork-resume.md": sessionOperationsExportShareForkResumeMd,
"session-switching-and-recent-listing.md": sessionSwitchingAndRecentListingMd,
"session-tree-plan.md": sessionTreePlanMd,
"skills.md": skillsMd,
"slash-command-internals.md": slashCommandInternalsMd,
"task-agent-discovery.md": taskAgentDiscoveryMd,
"theme.md": themeMd,
"tree.md": treeMd,
"ttsr-injection-lifecycle.md": ttsrInjectionLifecycleMd,
"tui.md": tuiMd,
"tui-runtime-internals.md": tuiRuntimeInternalsMd,
};
export const EMBEDDED_DOC_FILENAMES = Object.keys(EMBEDDED_DOCS).sort();
@@ -0,0 +1,84 @@
/**
* Protocol handler for docs:// URLs.
*
* Serves statically embedded documentation files bundled at build time.
*
* URL forms:
* - docs:// - Lists all available documentation files
* - docs://<file>.md - Reads a specific documentation file
*/
import * as path from "node:path";
import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
/**
* Handler for docs:// URLs.
*
* Resolves documentation file names to their content, or lists available docs.
*/
export class DocsProtocolHandler implements ProtocolHandler {
readonly scheme = "docs";
async resolve(url: InternalUrl): Promise<InternalResource> {
// Extract filename from host + path
const host = url.rawHost || url.hostname;
const pathname = url.rawPathname ?? url.pathname;
const filename = host ? (pathname && pathname !== "/" ? host + pathname : host) : "";
if (!filename) {
return this.#listDocs(url);
}
return this.#readDoc(filename, url);
}
async #listDocs(url: InternalUrl): Promise<InternalResource> {
if (EMBEDDED_DOC_FILENAMES.length === 0) {
throw new Error("No documentation files found");
}
const listing = EMBEDDED_DOC_FILENAMES.map(f => `- [${f}](docs://${f})`).join("\n");
const content = `# Documentation\n\n${EMBEDDED_DOC_FILENAMES.length} files available:\n\n${listing}\n`;
return {
url: url.href,
content,
contentType: "text/markdown",
size: Buffer.byteLength(content, "utf-8"),
sourcePath: "docs://",
};
}
async #readDoc(filename: string, url: InternalUrl): Promise<InternalResource> {
// Validate: no traversal, no absolute paths
if (path.isAbsolute(filename)) {
throw new Error("Absolute paths are not allowed in docs:// URLs");
}
const normalized = path.posix.normalize(filename.replaceAll("\\", "/"));
if (normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
throw new Error("Path traversal (..) is not allowed in docs:// URLs");
}
const content = EMBEDDED_DOCS[normalized];
if (content === undefined) {
const lookup = normalized.replace(/\.md$/, "");
const suggestions = EMBEDDED_DOC_FILENAMES.filter(
f => f.includes(lookup) || lookup.includes(f.replace(/\.md$/, "")),
).slice(0, 5);
const suffix =
suggestions.length > 0
? `\nDid you mean: ${suggestions.join(", ")}`
: "\nUse docs:// to list available files.";
throw new Error(`Documentation file not found: ${filename}${suffix}`);
}
return {
url: url.href,
content,
contentType: "text/markdown",
size: Buffer.byteLength(content, "utf-8"),
sourcePath: `docs://${normalized}`,
};
}
}
@@ -22,6 +22,7 @@
export { AgentProtocolHandler, type AgentProtocolOptions } from "./agent-protocol";
export { ArtifactProtocolHandler, type ArtifactProtocolOptions } from "./artifact-protocol";
export { DocsProtocolHandler } from "./docs-protocol";
export { applyQuery, parseQuery, pathToQuery } from "./json-query";
export { MemoryProtocolHandler, type MemoryProtocolOptions, resolveMemoryUrlToPath } from "./memory-protocol";
export { PlanProtocolHandler, type PlanProtocolOptions, resolvePlanUrlToPath } from "./plan-protocol";
@@ -1,5 +1,5 @@
/**
* Internal URL router for internal protocols (agent://, artifact://, plan://, memory://, skill://, rule://).
* Internal URL router for internal protocols (agent://, artifact://, plan://, memory://, skill://, rule://, docs://).
*/
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
@@ -1,5 +1,5 @@
<identity>
Distinguished Staff Engineer.
You are a distinguished staff engineer operating inside Oh My Pi, a Pi-based coding harness.
High-agency. Principled. Decisive.
Expertise: debugging, refactoring, system design.
@@ -172,6 +172,17 @@ Main branch: {{git.mainBranch}}
{{/if}}
</project>
<harness>
Oh My Pi ships internal documentation accessible via `docs://` URLs (resolved by tools like read/grep).
- Read `docs://` to list all available documentation files
- Read `docs://<file>.md` to read a specific doc
<critical>
- **ONLY** read docs when the user asks about omp/pi itself: its SDK, extensions, themes, skills, TUI, keybindings, or configuration.
- When working on omp/pi topics, read the relevant docs and follow .md cross-references before implementing.
</critical>
</harness>
{{#if skills.length}}
<skills>
Scan descriptions vs task domain. Skill covers output? Read `skill://<name>` first.
@@ -221,7 +232,7 @@ Notice the sequential habit:
- Comfort in doing one thing at a time
- Illusion that order = correctness
- Assumption that B depends on A
**Use Task tool when:**
**Use Task tool when:**
- Editing 4+ files with no dependencies between edits
- Investigating 2+ independent subsystems
- Work decomposes into pieces not needing each other's results
@@ -8,7 +8,7 @@ Executes bash command in shell session for terminal operations like git, bun, ca
- `skill://` URIs are auto-resolved to filesystem paths before execution
- `python skill://my-skill/scripts/init.py` runs the script from the skill directory
- `skill://<name>/<relative-path>` resolves within the skill's base directory
- `agent://`, `artifact://`, `plan://`, `memory://`, and `rule://` URIs are also auto-resolved to filesystem paths before execution
- `agent://`, `artifact://`, `plan://`, `memory://`, `rule://`, and `docs://` URIs are also auto-resolved to filesystem paths before execution
</instruction>
<output>
@@ -23,6 +23,8 @@ Reads files from local filesystem or internal URLs.
- `memory://root/<path>` - read relative path within project memory root
- `agent://<id>` - read agent output artifact
- `agent://<id>/<path>` or `agent://<id>?q=<query>` - extract JSON from agent output
- `docs://` - list available pi documentation files
- `docs://<file>.md` - read a specific pi documentation file
</instruction>
<output>
+16
View File
@@ -19,6 +19,7 @@ import {
type CustomCommandsLoadResult,
loadCustomCommands as loadCustomCommandsInternal,
} from "./extensibility/custom-commands";
import { discoverAndLoadCustomTools } from "./extensibility/custom-tools";
import type { CustomTool, CustomToolContext, CustomToolSessionEvent } from "./extensibility/custom-tools/types";
import { CustomToolAdapter } from "./extensibility/custom-tools/wrapper";
import {
@@ -39,6 +40,7 @@ import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal }
import {
AgentProtocolHandler,
ArtifactProtocolHandler,
DocsProtocolHandler,
InternalUrlRouter,
MemoryProtocolHandler,
PlanProtocolHandler,
@@ -770,6 +772,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
getRules: () => rulebookRules,
}),
);
internalRouter.register(new DocsProtocolHandler());
toolSession.internalRouter = internalRouter;
toolSession.getArtifactsDir = getArtifactsDir;
toolSession.agentOutputManager = new AgentOutputManager(
@@ -848,6 +851,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
time("getSearchTools");
}
debugStartup("sdk:discoverCustomTools:start");
// Discover and load custom tools from .omp/tools/, .claude/tools/, etc.
const builtInToolNames = builtinTools.map(t => t.name);
const discoveredCustomTools = await discoverAndLoadCustomTools([], cwd, builtInToolNames);
for (const { path, error } of discoveredCustomTools.errors) {
logger.error("Custom tool load failed", { path, error });
}
if (discoveredCustomTools.tools.length > 0) {
customTools.push(...discoveredCustomTools.tools.map(loaded => loaded.tool));
}
time("discoverAndLoadCustomTools");
debugStartup("sdk:discoverCustomTools:done");
const inlineExtensions: ExtensionFactory[] = options.extensions ? [...options.extensions] : [];
if (customTools.length > 0) {
inlineExtensions.push(createCustomToolsExtension(customTools));
+4 -3
View File
@@ -966,11 +966,12 @@ function countNonEmptyLines(text: string): number {
/** Render fetch call (URL preview) */
export function renderFetchCall(
args: { url: string; timeout?: number; raw?: boolean },
args: { url?: string; timeout?: number; raw?: boolean },
uiTheme: Theme = theme,
): Component {
const domain = getDomain(args.url);
const path = truncate(args.url.replace(/^https?:\/\/[^/]+/, ""), 50, "…");
const url = args.url ?? "";
const domain = getDomain(url);
const path = truncate(url.replace(/^https?:\/\/[^/]+/, ""), 50, "\u2026");
const description = `${domain}${path ? ` ${path}` : ""}`.trim();
const meta: string[] = [];
if (args.raw) meta.push("raw");
@@ -282,11 +282,11 @@ export function renderSearchResult(
/** Render web search call (query preview) */
export function renderSearchCall(
args: { query: string; provider?: string; [key: string]: unknown },
args: { query?: string; provider?: string; [key: string]: unknown },
theme: Theme,
): Component {
const provider = args.provider ?? "auto";
const query = truncateToWidth(args.query, 80);
const query = truncateToWidth(args.query ?? "", 80);
const text = renderStatusLine({ icon: "pending", title: "Web Search", description: query, meta: [provider] }, theme);
return new Text(text, 0, 0);
}