@truefoundry/trueforge-core 0.2.0-rc.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-session/ITurnResourceResolver.d.ts +6 -0
- package/dist/agent-session/ITurnResourceResolver.d.ts.map +1 -1
- package/dist/agent-session/ITurnResourceResolver.js.map +1 -1
- package/dist/agent-session/SessionHandle.d.ts +1 -0
- package/dist/agent-session/SessionHandle.d.ts.map +1 -1
- package/dist/agent-session/SessionHandle.js +3 -0
- package/dist/agent-session/SessionHandle.js.map +1 -1
- package/dist/agent-session/SessionHandle.mjs +3 -0
- package/dist/agent-session/SessionHandle.mjs.map +1 -1
- package/dist/agent-session/TurnResourceResolver.d.ts +8 -0
- package/dist/agent-session/TurnResourceResolver.d.ts.map +1 -1
- package/dist/agent-session/TurnResourceResolver.js +4 -0
- package/dist/agent-session/TurnResourceResolver.js.map +1 -1
- package/dist/agent-session/TurnResourceResolver.mjs +4 -0
- package/dist/agent-session/TurnResourceResolver.mjs.map +1 -1
- package/dist/agent-session/builtinsFromSpec.d.ts +2 -0
- package/dist/agent-session/builtinsFromSpec.d.ts.map +1 -1
- package/dist/agent-session/builtinsFromSpec.js +5 -1
- package/dist/agent-session/builtinsFromSpec.js.map +1 -1
- package/dist/agent-session/builtinsFromSpec.mjs +5 -1
- package/dist/agent-session/builtinsFromSpec.mjs.map +1 -1
- package/dist/agent-session/index.d.ts +2 -2
- package/dist/agent-session/index.d.ts.map +1 -1
- package/dist/agent-session/index.js +2 -0
- package/dist/agent-session/index.js.map +1 -1
- package/dist/agent-session/index.mjs +3 -1
- package/dist/agent-session/index.mjs.map +1 -1
- package/dist/agent-session/models/TurnRecord.d.ts +1 -0
- package/dist/agent-session/models/TurnRecord.d.ts.map +1 -1
- package/dist/agent-session/models/TurnRecord.js.map +1 -1
- package/dist/agent-session/models/TurnRecord.mjs.map +1 -1
- package/dist/agent-session/schemas/agentSpec.d.ts +6 -0
- package/dist/agent-session/schemas/agentSpec.d.ts.map +1 -1
- package/dist/agent-session/schemas/agentSpec.js +7 -3
- package/dist/agent-session/schemas/agentSpec.js.map +1 -1
- package/dist/agent-session/schemas/agentSpec.mjs +7 -3
- package/dist/agent-session/schemas/agentSpec.mjs.map +1 -1
- package/dist/agent-session/schemas/events.d.ts +65 -2
- package/dist/agent-session/schemas/events.d.ts.map +1 -1
- package/dist/agent-session/schemas/events.js +32 -2
- package/dist/agent-session/schemas/events.js.map +1 -1
- package/dist/agent-session/schemas/events.mjs +26 -1
- package/dist/agent-session/schemas/events.mjs.map +1 -1
- package/dist/agent-session/schemas/session.d.ts +9 -0
- package/dist/agent-session/schemas/session.d.ts.map +1 -1
- package/dist/core/capabilities/builtins/OpenUI.d.ts.map +1 -1
- package/dist/core/capabilities/builtins/OpenUI.js +4 -2
- package/dist/core/capabilities/builtins/OpenUI.js.map +1 -1
- package/dist/core/capabilities/builtins/OpenUI.mjs +4 -2
- package/dist/core/capabilities/builtins/OpenUI.mjs.map +1 -1
- package/dist/core/capabilities/builtins/WebSearch.d.ts +32 -0
- package/dist/core/capabilities/builtins/WebSearch.d.ts.map +1 -0
- package/dist/core/capabilities/builtins/WebSearch.js +137 -0
- package/dist/core/capabilities/builtins/WebSearch.js.map +1 -0
- package/dist/core/capabilities/builtins/WebSearch.mjs +96 -0
- package/dist/core/capabilities/builtins/WebSearch.mjs.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +22 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +20 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/mcp/RemoteMCP.d.ts +2 -0
- package/dist/core/mcp/RemoteMCP.d.ts.map +1 -1
- package/dist/core/mcp/RemoteMCP.js +3 -0
- package/dist/core/mcp/RemoteMCP.js.map +1 -1
- package/dist/core/mcp/RemoteMCP.mjs +4 -0
- package/dist/core/mcp/RemoteMCP.mjs.map +1 -1
- package/dist/core/mcp/ToolSelectorPolicy.d.ts +1 -1
- package/dist/core/mcp/ToolSelectorPolicy.js +1 -1
- package/dist/core/mcp/ToolSelectorPolicy.js.map +1 -1
- package/dist/core/mcp/ToolSelectorPolicy.mjs +1 -1
- package/dist/core/mcp/ToolSelectorPolicy.mjs.map +1 -1
- package/dist/core/mcp/remoteMcpClient.d.ts +5 -0
- package/dist/core/mcp/remoteMcpClient.d.ts.map +1 -1
- package/dist/core/mcp/remoteMcpClient.js +37 -6
- package/dist/core/mcp/remoteMcpClient.js.map +1 -1
- package/dist/core/mcp/remoteMcpClient.mjs +34 -5
- package/dist/core/mcp/remoteMcpClient.mjs.map +1 -1
- package/dist/core/mcp/toolSelectors.d.ts.map +1 -1
- package/dist/core/mcp/toolSelectors.js +1 -1
- package/dist/core/mcp/toolSelectors.js.map +1 -1
- package/dist/core/mcp/toolSelectors.mjs +1 -1
- package/dist/core/mcp/toolSelectors.mjs.map +1 -1
- package/dist/core/runtime/DeferredTool.d.ts.map +1 -1
- package/dist/core/runtime/DeferredTool.js +3 -1
- package/dist/core/runtime/DeferredTool.js.map +1 -1
- package/dist/core/runtime/DeferredTool.mjs +3 -1
- package/dist/core/runtime/DeferredTool.mjs.map +1 -1
- package/dist/core/sandbox/Sandbox.d.ts.map +1 -1
- package/dist/core/sandbox/Sandbox.js +4 -1
- package/dist/core/sandbox/Sandbox.js.map +1 -1
- package/dist/core/sandbox/Sandbox.mjs +4 -1
- package/dist/core/sandbox/Sandbox.mjs.map +1 -1
- package/dist/core/web-search/ParallelWebSearchProvider.d.ts +24 -0
- package/dist/core/web-search/ParallelWebSearchProvider.d.ts.map +1 -0
- package/dist/core/web-search/ParallelWebSearchProvider.js +95 -0
- package/dist/core/web-search/ParallelWebSearchProvider.js.map +1 -0
- package/dist/core/web-search/ParallelWebSearchProvider.mjs +62 -0
- package/dist/core/web-search/ParallelWebSearchProvider.mjs.map +1 -0
- package/dist/core/web-search/WebSearchProvider.d.ts +42 -0
- package/dist/core/web-search/WebSearchProvider.d.ts.map +1 -0
- package/dist/core/web-search/WebSearchProvider.js +34 -0
- package/dist/core/web-search/WebSearchProvider.js.map +1 -0
- package/dist/core/web-search/WebSearchProvider.mjs +9 -0
- package/dist/core/web-search/WebSearchProvider.mjs.map +1 -0
- package/package.json +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/core/capabilities/builtins/OpenUI.ts"],"sourcesContent":["import dedent from 'dedent';\nimport { z } from 'zod';\nimport { InstructionBuilder } from '../../InstructionBuilder';\nimport { toolResultResponse } from '../../mcp/IMCPServer';\nimport { defineTool, LocalToolMCP, type ToolDefinition } from '../../mcp/LocalToolMCP';\nimport type { AgentTracing } from '../../tracing/AgentTracing';\nimport { NOOP_AGENT_TRACING } from '../../tracing/NoopAgentTracing';\nimport type { AgentCapability } from '../AgentCapability';\n\nexport const OPENUI_SERVER_ID = 'openui';\nexport const GET_OPENUI_INSTRUCTIONS_TOOL_NAME = 'get_openui_instructions';\n\nconst getOpenUIInstructionsInputSchema = z.object({}).strict();\n\nexport const OPENUI_SECTION_TAG = 'openui';\nexport const OPENUI_MARKDOWN_FENCING_TAG = 'openui-markdown-fencing';\nexport const OPENUI_SYNTAX_RULES_TAG = 'openui-syntax-rules';\nexport const OPENUI_COMPONENT_SIGNATURES_TAG = 'openui-component-signatures';\nexport const OPENUI_BUILTIN_FUNCTIONS_TAG = 'openui-builtin-functions';\nexport const OPENUI_HOISTING_AND_STREAMING_TAG = 'openui-hoisting-and-streaming';\nexport const OPENUI_EXAMPLES_TAG = 'openui-examples';\nexport const OPENUI_IMPORTANT_RULES_TAG = 'openui-important-rules';\nexport const OPENUI_FINAL_VERIFICATION_TAG = 'openui-final-verification';\nexport const OPENUI_USER_INTERACTION_CHECKLIST_TAG = 'openui-user-interaction-checklist';\n\nexport function buildOpenUIInstruction(builder: InstructionBuilder): void {\n const openui = builder.beginSection(OPENUI_SECTION_TAG);\n\n openui.addSection(\n OPENUI_MARKDOWN_FENCING_TAG,\n dedent`\n All openui, code must be fenced within openui block.\n \\`\\`\\`openui\n code\n \\`\\`\\``,\n );\n\n openui.addSection(\n OPENUI_SYNTAX_RULES_TAG,\n dedent`\n 1. Each statement is on its own line: \\`identifier = Expression\\`\n 2. \\`root\\` is the entry point — every program must define \\`root = Stack(...)\\`\n 3. Expressions are: strings (\"...\"), numbers, booleans (true/false), null, arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...)\n 4. Use references for readability: define \\`name = ...\\` on one line, then use \\`name\\` later\n 5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array.\n 6. Arguments are POSITIONAL (order matters, not names). Write \\`Stack([children], \"row\", \"l\")\\` NOT \\`Stack([children], direction: \"row\", gap: \"l\")\\` — colon syntax is NOT supported and silently breaks\n 7. Optional arguments can be omitted from the end\n - Strings use double quotes with backslash escaping`,\n );\n\n openui.addSection(\n OPENUI_COMPONENT_SIGNATURES_TAG,\n dedent`\n Arguments marked with ? are optional. Sub-components can be inline or referenced; prefer references for better streaming.\n Props typed \\`ActionExpression\\` accept an Action([@steps...]) expression. See the Action section for available steps (@ToAssistant, @OpenUrl).\n Props marked \\`$binding<type>\\` accept a \\`$variable\\` reference for two-way binding.\n\n Layout:\n Stack([children], direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean) — Flex container. direction: \"row\"|\"column\" (default \"column\"). gap: \"none\"|\"xs\"|\"s\"|\"m\"|\"l\"|\"xl\"|\"2xl\" (default \"m\"). align: \"start\"|\"center\"|\"end\"|\"stretch\"|\"baseline\". justify: \"start\"|\"center\"|\"end\"|\"between\"|\"around\"|\"evenly\".\n Tabs(items: TabItem[]) — Tabbed container\n TabItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[]) — value is unique id, trigger is tab label, content is array of components\n Accordion(items: AccordionItem[]) — Collapsible sections\n AccordionItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[]) — value is unique id, trigger is section title\n Steps(items: StepsItem[]) — Step-by-step guide\n StepsItem(title: string, details: string) — title and details text for one step\n Carousel(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[][], variant?: \"card\" | \"sunk\") — Horizontal scrollable carousel\n Separator(orientation?: \"horizontal\" | \"vertical\", decorative?: boolean) — Visual divider between content sections\n Modal(title: string, open?: $binding<boolean>, children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[], size?: \"sm\" | \"md\" | \"lg\") — Modal dialog. open is a reactive $boolean binding — set to true to open, X/Escape/backdrop auto-closes. Put Form with buttons inside children.\n - For grid-like layouts, use Stack with direction \"row\" and wrap set to true.\n - Prefer justify \"start\" (or omit justify) with wrap=true for stable columns instead of uneven gutters.\n - Use nested Stacks when you need explicit rows/sections.\n - Show/hide sections: $editId != \"\" ? Card([editForm]) : null\n - Modal: Modal(\"Title\", $showModal, [content]) — $showModal is boolean, X/Escape auto-closes. Put Form with its own buttons inside children.\n - Use Tabs for alternative views (chart types, data sections) — no $variable needed\n - Shared filter across Tabs: same $days binding in Query args works across all TabItems\n\n Content:\n Card(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | Tabs | Carousel | Stack)[], variant?: \"card\" | \"sunk\" | \"clear\", direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean) — Styled container. variant: \"card\" (default, elevated) | \"sunk\" (recessed) | \"clear\" (transparent). Always full width. Accepts all Stack flex params (default: direction \"column\"). Cards flex to share space in row/wrap layouts.\n CardHeader(title?: string, subtitle?: string) — Header with optional title and subtitle\n TextContent(text: string, size?: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\") — Text block. Supports markdown. Optional size: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\".\n MarkDownRenderer(textMarkdown: string, variant?: \"clear\" | \"card\" | \"sunk\") — Renders markdown text with optional container variant\n Callout(variant: \"info\" | \"warning\" | \"error\" | \"success\" | \"neutral\", title: string, description: string, visible?: $binding<boolean>) — Callout banner. Optional visible is a reactive $boolean — auto-dismisses after 3s by setting $visible to false.\n TextCallout(variant?: \"neutral\" | \"info\" | \"warning\" | \"success\" | \"danger\", title?: string, description?: string) — Text callout with variant, title, and description\n Image(alt: string, src?: string) — Image with alt text and optional URL\n ImageBlock(src: string, alt?: string) — Image block with loading state\n ImageGallery(images: {src: string, alt?: string, details?: string}[]) — Gallery grid of images with modal preview\n CodeBlock(language: string, codeString: string) — Syntax-highlighted code block\n - Use Cards to group related KPIs or sections. Stack with direction \"row\" for side-by-side layouts.\n - Success toast: Callout(\"success\", \"Saved\", \"Done.\", $showSuccess) — use @Set($showSuccess, true) in save action, auto-dismisses after 3s. For errors: result.status == \"error\" ? Callout(\"error\", \"Failed\", result.error) : null\n - KPI card: Card([TextContent(\"Label\", \"small\"), TextContent(\"\" + @Count(@Filter(data.rows, \"field\", \"==\", \"value\")), \"large-heavy\")])\n\n Tables:\n Table(columns: Col[]) — Data table — column-oriented. Each Col holds its own data array.\n Col(label: string, data, type?: \"string\" | \"number\" | \"action\") — Column definition — holds label + data array\n - Table is COLUMN-oriented: Table([Col(\"Label\", dataArray), Col(\"Count\", countArray, \"number\")]). Use array pluck for data: data.rows.fieldName\n - Col data can be component arrays for styled cells: Col(\"Status\", @Each(data.rows, \"item\", Tag(item.status, null, \"sm\", item.status == \"open\" ? \"success\" : \"danger\")))\n - Row actions: Col(\"Actions\", @Each(data.rows, \"t\", Button(\"Edit\", Action([@Set($showEdit, true), @Set($editId, t.id)]))))\n - Sortable: sorted = @Sort(data.rows, $sortField, \"desc\"). Bind $sortField to Select. Use sorted.fieldName for Col data\n - Searchable: filtered = @Filter(data.rows, \"title\", \"contains\", $search). Bind $search to Input\n - Chain sort + filter: filtered = @Filter(...) then sorted = @Sort(filtered, ...) — use sorted for both Table and Charts\n - Empty state: @Count(data.rows) > 0 ? Table([...]) : TextContent(\"No data yet\")\n\n Charts (2D):\n BarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string) — Vertical bars; use for comparing values across categories with one or more series\n LineChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string) — Lines over categories; use for trends and continuous data over time\n AreaChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string) — Filled area under lines; use for cumulative totals or volume trends over time\n RadarChart(labels: string[], series: Series[]) — Spider/web chart; use for comparing multiple variables across one or more entities\n HorizontalBarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string) — Horizontal bars; prefer when category labels are long or for ranked lists\n Series(category: string, values: number[]) — One data series\n - Charts accept column arrays: LineChart(labels, [Series(\"Name\", values)]). Use array pluck: LineChart(data.rows.day, [Series(\"Views\", data.rows.views)])\n - Use Cards to wrap charts with CardHeader for titled sections\n - Chart + Table from same source: use @Sort or @Filter result for both LineChart and Table Col data\n - Multiple chart views: use Tabs — Tabs([TabItem(\"line\", \"Line\", [LineChart(...)]), TabItem(\"bar\", \"Bar\", [BarChart(...)])])\n\n Charts (1D):\n PieChart(labels: string[], values: number[], variant?: \"pie\" | \"donut\") — Circular slices; use plucked arrays: PieChart(data.categories, data.values)\n RadialChart(labels: string[], values: number[]) — Radial bars; use plucked arrays: RadialChart(data.categories, data.values)\n SingleStackedBarChart(labels: string[], values: number[]) — Single horizontal stacked bar; use plucked arrays: SingleStackedBarChart(data.categories, data.values)\n Slice(category: string, value: number) — One slice with label and numeric value\n - PieChart and BarChart need NUMBERS, not objects. For list data, use @Count(@Filter(...)) to aggregate:\n - PieChart from list: \\`PieChart([\"Low\", \"Med\", \"High\"], [@Count(@Filter(data.rows, \"priority\", \"==\", \"low\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"medium\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"high\"))], \"donut\")\\`\n - KPI from count: \\`TextContent(\"\" + @Count(@Filter(data.rows, \"status\", \"==\", \"open\")), \"large-heavy\")\\`\n\n Charts (Scatter):\n ScatterChart(datasets: ScatterSeries[], xLabel?: string, yLabel?: string) — X/Y scatter plot; use for correlations, distributions, and clustering\n ScatterSeries(name: string, points: Point[]) — Named dataset\n Point(x: number, y: number, z?: number) — Data point with numeric coordinates\n\n Data Display:\n TagBlock(tags: string[]) — tags is an array of strings\n Tag(text: string, icon?: string, size?: \"sm\" | \"md\" | \"lg\", variant?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\") — Styled tag/badge with optional icon and variant\n - Color-mapped Tag: Tag(value, null, \"sm\", value == \"high\" ? \"danger\" : value == \"medium\" ? \"warning\" : \"neutral\")`,\n );\n\n openui.addSection(\n OPENUI_BUILTIN_FUNCTIONS_TAG,\n dedent`\n Data functions prefixed with \\`@\\` to distinguish from components. These are the ONLY functions available — do NOT invent new ones.\n Use @-prefixed built-in functions (@Count, @Sum, @Avg, @Min, @Max, @Round) on Query results — do NOT hardcode computed values.\n\n @Count(array) → number — Returns array length\n @First(array) → element — Returns first element of array\n @Last(array) → element — Returns last element of array\n @Sum(numbers[]) → number — Sum of numeric array\n @Avg(numbers[]) → number — Average of numeric array\n @Min(numbers[]) → number — Minimum value in array\n @Max(numbers[]) → number — Maximum value in array\n @Sort(array, field, direction?) → sorted array — Sort array by field. Direction: \"asc\" (default) or \"desc\"\n @Filter(array, field, operator: \"==\" | \"!=\" | \">\" | \"<\" | \">=\" | \"<=\" | \"contains\", value) → filtered array — Filter array by field value\n @Round(number, decimals?) → number — Round to N decimal places (default 0)\n @Abs(number) → number — Absolute value\n @Floor(number) → number — Round down to nearest integer\n @Ceil(number) → number — Round up to nearest integer\n @Each(array, varName, template) — Evaluate template for each element. varName is the loop variable — use it ONLY inside the template expression (inline). Do NOT create a separate statement for the template.\n\n Builtins compose — output of one is input to the next:\n \\`@Count(@Filter(data.rows, \"field\", \"==\", \"val\"))\\` for KPIs/chart values, \\`@Round(@Avg(data.rows.score), 1)\\`, \\`@Each(data.rows, \"item\", Comp(item.field))\\` for per-item rendering.\n Array pluck: \\`data.rows.field\\` extracts a field from every row → use with @Sum, @Avg, charts, tables.\n\n IMPORTANT @Each rule: The loop variable (e.g. \"item\") is ONLY available inside the @Each template expression. Always inline the template — do NOT extract it to a separate statement.\n CORRECT: \\`Col(\"Actions\", @Each(rows, \"t\", Button(\"Edit\", Action([@Set($id, t.id)]))))\\`\n WRONG: \\`myBtn = Button(\"Edit\", Action([@Set($id, t.id)]))\\` then \\`Col(\"Actions\", @Each(rows, \"t\", myBtn))\\` — t is undefined in myBtn.`,\n );\n\n openui.addSection(\n OPENUI_HOISTING_AND_STREAMING_TAG,\n dedent`\n openui-lang supports hoisting: a reference can be used BEFORE it is defined. The parser resolves all references after the full input is parsed.\n\n During streaming, the output is re-parsed on every chunk. Undefined references are temporarily unresolved and appear once their definitions stream in. This creates a progressive top-down reveal — structure first, then data fills in.\n\n Recommended statement order for optimal streaming:\n 1. \\`root = Stack(...)\\` — UI shell appears immediately\n 2. $variable declarations — state ready for bindings\n 3. Query statements — defaults resolve immediately so components render with data\n 4. Component definitions — fill in with data already available\n 5. Data values — leaf content last\n\n Always write the root = Stack(...) statement first so the UI shell appears immediately, even before child data has streamed in.`,\n );\n\n openui.addSection(\n OPENUI_EXAMPLES_TAG,\n dedent`\n Example 1 — Table (column-oriented):\n\n root = Stack([title, tbl])\n title = TextContent(\"Top Languages\", \"large-heavy\")\n tbl = Table([Col(\"Language\", langs), Col(\"Users (M)\", users), Col(\"Year\", years)])\n langs = [\"Python\", \"JavaScript\", \"Java\", \"TypeScript\", \"Go\"]\n users = [15.7, 14.2, 12.1, 8.5, 5.2]\n years = [1991, 1995, 1995, 2012, 2009]\n\n Example 2 — Bar chart:\n\n root = Stack([title, chart])\n title = TextContent(\"Q4 Revenue\", \"large-heavy\")\n chart = BarChart(labels, [s1, s2], \"grouped\")\n labels = [\"Oct\", \"Nov\", \"Dec\"]\n s1 = Series(\"Product A\", [120, 150, 180])\n s2 = Series(\"Product B\", [90, 110, 140])\n\n Example 3 — Form with validation:\n\n root = Stack([title, form])\n title = TextContent(\"Contact Us\", \"large-heavy\")\n form = Form(\"contact\", btns, [nameField, emailField, countryField, msgField])\n nameField = FormControl(\"Name\", Input(\"name\", \"Your name\", \"text\", { required: true, minLength: 2 }))\n emailField = FormControl(\"Email\", Input(\"email\", \"you@example.com\", \"email\", { required: true, email: true }))\n countryField = FormControl(\"Country\", Select(\"country\", countryOpts, \"Select...\", { required: true }))\n msgField = FormControl(\"Message\", TextArea(\"message\", \"Tell us more...\", 4, { required: true, minLength: 10 }))\n countryOpts = [SelectItem(\"us\", \"United States\"), SelectItem(\"uk\", \"United Kingdom\"), SelectItem(\"de\", \"Germany\")]\n btns = Buttons([Button(\"Submit\", Action([@ToAssistant(\"Submit\")]), \"primary\"), Button(\"Cancel\", Action([@ToAssistant(\"Cancel\")]), \"secondary\")])\n\n Example 4 — Tabs with mixed content:\n\n root = Stack([title, tabs])\n title = TextContent(\"React vs Vue\", \"large-heavy\")\n tabs = Tabs([tabReact, tabVue])\n tabReact = TabItem(\"react\", \"React\", reactContent)\n tabVue = TabItem(\"vue\", \"Vue\", vueContent)\n reactContent = [TextContent(\"React is a library by Meta for building UIs.\"), Callout(\"info\", \"Note\", \"React uses JSX syntax.\")]\n vueContent = [TextContent(\"Vue is a progressive framework by Evan You.\"), Callout(\"success\", \"Tip\", \"Vue has a gentle learning curve.\")]`,\n );\n\n openui.addSection(\n OPENUI_IMPORTANT_RULES_TAG,\n dedent`\n - When asked about data, generate realistic/plausible data\n - Choose components that best represent the content (tables for comparisons, charts for trends, forms for input, etc.)\n - When you render data in an openui block (tables, charts, KPI cards), do NOT repeat the same numbers/facts in the markdown text outside the block.\n - Text outside the openui block can optionally include:\n 1. **Qualitative insights** — patterns, anomalies, or recommendations that aren't obvious from the visual (e.g. \"the high input-to-output ratio suggests document processing\")\n 2. **Actionable next steps** — what the user can do next\n 3. **Caveats/context** — things the data doesn't show\n - If all the information is already visible in the openui components, a brief one-line summary is sufficient — do not enumerate the same values again.`,\n );\n\n openui.addSection(\n OPENUI_FINAL_VERIFICATION_TAG,\n dedent`\n Before finishing, walk your output and verify:\n 1. root = Stack(...) is the FIRST line (for optimal streaming).\n 2. Every referenced name is defined. Every defined name (other than root) is reachable from root.\n\n - For grid-like layouts, use Stack with direction \"row\" and wrap=true. Avoid justify=\"between\" unless you specifically want large gutters.\n - For forms, define one FormControl reference per field so controls can stream progressively.\n - For forms, always provide the second Form argument with Buttons(...) actions: Form(name, buttons, fields).\n - Never nest Form inside Form.\n - Use @Reset($var1, $var2) after form submit to restore defaults — not @Set($var, \"\")\n - Multi-query refresh: Action([@Run(mutation), @Run(query1), @Run(query2), @Reset(...)])\n - $variables are reactive: changing via Select or @Set re-evaluates all Queries and expressions referencing them\n - Use existing components (Tabs, Accordion, Modal) before inventing ternary show/hide patterns`,\n );\n\n openui.addSection(\n OPENUI_USER_INTERACTION_CHECKLIST_TAG,\n dedent`\n 1. Will the response be UI heavy and will contain components which are not supported by markdown? Use openui if the answer is yes.\n 2. \\`\\`\\`openui\\`\\`\\` fencing must be closed.`,\n );\n}\n\n/** Renders the existing OpenUI prompt tree without duplicating section bodies. */\nexport function renderOpenUIPrompt(): string {\n const root = new InstructionBuilder('_openui_render');\n buildOpenUIInstruction(root);\n const wrapped = root.build();\n const openTag = `<${OPENUI_SECTION_TAG}>`;\n const closeTag = `</${OPENUI_SECTION_TAG}>`;\n const start = wrapped.indexOf(openTag);\n const end = wrapped.lastIndexOf(closeTag);\n if (start === -1 || end === -1) {\n throw new Error('Failed to render OpenUI prompt from buildOpenUIInstruction');\n }\n return wrapped.slice(start, end + closeTag.length);\n}\n\nfunction buildOpenUIDeferredInstruction(builder: InstructionBuilder): void {\n builder.addSection(\n OPENUI_SECTION_TAG,\n dedent`\n The Agent can render interactive UI that markdown cannot express by emitting a fenced \\`\\`\\`openui block.\n\n Before writing any \\`\\`\\`openui fence, the Agent MUST call ${GET_OPENUI_INSTRUCTIONS_TOOL_NAME} with arguments {}.\n Do not invent OpenUI syntax or component APIs without loading those instructions.\n\n Use OpenUI only when the response is UI-heavy and needs components markdown does not support.\n `.trim(),\n );\n}\n\nexport class OpenUIInstructions extends LocalToolMCP {\n readonly name = OPENUI_SERVER_ID;\n readonly displayName = 'OpenUI';\n\n constructor(tracing: AgentTracing) {\n super({ tracing });\n }\n\n private tools: ToolDefinition[] = [\n defineTool({\n name: GET_OPENUI_INSTRUCTIONS_TOOL_NAME,\n description: [\n 'Load the full OpenUI generative-UI authoring instructions (syntax, components, builtins, examples, rules).',\n 'Call this before emitting any ```openui fenced block. Pass an empty object as arguments: {}.',\n ].join(' '),\n schema: getOpenUIInstructionsInputSchema,\n handler: () => Promise.resolve(toolResultResponse({ text: renderOpenUIPrompt() })),\n }),\n ];\n\n protected getTools(): ToolDefinition[] {\n return this.tools;\n }\n}\n\nexport function openUI(options?: { preload?: boolean; tracing?: AgentTracing }): AgentCapability {\n const preload = options?.preload ?? true;\n if (preload) {\n return {\n instructionBuilders: [buildOpenUIInstruction],\n };\n }\n return {\n systemToolSets: [new OpenUIInstructions(options?.tracing ?? NOOP_AGENT_TRACING)],\n instructionBuilders: [buildOpenUIDeferredInstruction],\n };\n}\n"],"mappings":"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","names":["dedent"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/capabilities/builtins/OpenUI.ts"],"sourcesContent":["import dedent from 'dedent';\nimport { z } from 'zod';\nimport { InstructionBuilder } from '../../InstructionBuilder';\nimport { toolResultResponse } from '../../mcp/IMCPServer';\nimport { defineTool, LocalToolMCP, type ToolDefinition } from '../../mcp/LocalToolMCP';\nimport type { AgentTracing } from '../../tracing/AgentTracing';\nimport { NOOP_AGENT_TRACING } from '../../tracing/NoopAgentTracing';\nimport type { AgentCapability } from '../AgentCapability';\n\nexport const OPENUI_SERVER_ID = 'openui';\nexport const GET_OPENUI_INSTRUCTIONS_TOOL_NAME = 'get_openui_instructions';\n\nconst getOpenUIInstructionsInputSchema = z.object({}).strict();\n\nexport const OPENUI_SECTION_TAG = 'openui';\nexport const OPENUI_MARKDOWN_FENCING_TAG = 'openui-markdown-fencing';\nexport const OPENUI_SYNTAX_RULES_TAG = 'openui-syntax-rules';\nexport const OPENUI_COMPONENT_SIGNATURES_TAG = 'openui-component-signatures';\nexport const OPENUI_BUILTIN_FUNCTIONS_TAG = 'openui-builtin-functions';\nexport const OPENUI_HOISTING_AND_STREAMING_TAG = 'openui-hoisting-and-streaming';\nexport const OPENUI_EXAMPLES_TAG = 'openui-examples';\nexport const OPENUI_IMPORTANT_RULES_TAG = 'openui-important-rules';\nexport const OPENUI_FINAL_VERIFICATION_TAG = 'openui-final-verification';\nexport const OPENUI_USER_INTERACTION_CHECKLIST_TAG = 'openui-user-interaction-checklist';\n\nexport function buildOpenUIInstruction(builder: InstructionBuilder): void {\n const openui = builder.beginSection(OPENUI_SECTION_TAG);\n\n openui.addSection(\n OPENUI_MARKDOWN_FENCING_TAG,\n dedent`\n All openui, code must be fenced within openui block.\n \\`\\`\\`openui\n code\n \\`\\`\\``,\n );\n\n openui.addSection(\n OPENUI_SYNTAX_RULES_TAG,\n dedent`\n 1. Each statement is on its own line: \\`identifier = Expression\\`\n 2. \\`root\\` is the entry point — every program must define \\`root = Stack(...)\\`\n 3. Expressions are: strings (\"...\"), numbers, booleans (true/false), null, arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...)\n 4. Use references for readability: define \\`name = ...\\` on one line, then use \\`name\\` later\n 5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array.\n 6. Arguments are POSITIONAL (order matters, not names). Write \\`Stack([children], \"row\", \"l\")\\` NOT \\`Stack([children], direction: \"row\", gap: \"l\")\\` — colon syntax is NOT supported and silently breaks\n 7. Optional arguments can be omitted from the end\n - Strings use double quotes with backslash escaping`,\n );\n\n openui.addSection(\n OPENUI_COMPONENT_SIGNATURES_TAG,\n dedent`\n Arguments marked with ? are optional. Sub-components can be inline or referenced; prefer references for better streaming.\n Props typed \\`ActionExpression\\` accept an Action([@steps...]) expression. See the Action section for available steps (@ToAssistant, @OpenUrl).\n Props marked \\`$binding<type>\\` accept a \\`$variable\\` reference for two-way binding.\n\n Layout:\n Stack([children], direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean) — Flex container. direction: \"row\"|\"column\" (default \"column\"). gap: \"none\"|\"xs\"|\"s\"|\"m\"|\"l\"|\"xl\"|\"2xl\" (default \"m\"). align: \"start\"|\"center\"|\"end\"|\"stretch\"|\"baseline\". justify: \"start\"|\"center\"|\"end\"|\"between\"|\"around\"|\"evenly\".\n Tabs(items: TabItem[]) — Tabbed container\n TabItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[]) — value is unique id, trigger is tab label, content is array of components\n Accordion(items: AccordionItem[]) — Collapsible sections\n AccordionItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[]) — value is unique id, trigger is section title\n Steps(items: StepsItem[]) — Step-by-step guide\n StepsItem(title: string, details: string) — title and details text for one step\n Carousel(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[][], variant?: \"card\" | \"sunk\") — Horizontal scrollable carousel\n Separator(orientation?: \"horizontal\" | \"vertical\", decorative?: boolean) — Visual divider between content sections\n Modal(title: string, open?: $binding<boolean>, children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[], size?: \"sm\" | \"md\" | \"lg\") — Modal dialog. open is a reactive $boolean binding — set to true to open, X/Escape/backdrop auto-closes. Put Form with buttons inside children.\n - For grid-like layouts, use Stack with direction \"row\" and wrap set to true.\n - Prefer justify \"start\" (or omit justify) with wrap=true for stable columns instead of uneven gutters.\n - Use nested Stacks when you need explicit rows/sections.\n - Show/hide sections: $editId != \"\" ? Card([editForm]) : null\n - Modal: Modal(\"Title\", $showModal, [content]) — $showModal is boolean, X/Escape auto-closes. Put Form with its own buttons inside children.\n - Use Tabs for alternative views (chart types, data sections) — no $variable needed\n - Shared filter across Tabs: same $days binding in Query args works across all TabItems\n\n Content:\n Card(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | Tabs | Carousel | Stack)[], variant?: \"card\" | \"sunk\" | \"clear\", direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean) — Styled container. variant: \"card\" (default, elevated) | \"sunk\" (recessed) | \"clear\" (transparent). Always full width. Accepts all Stack flex params (default: direction \"column\"). Cards flex to share space in row/wrap layouts.\n CardHeader(title?: string, subtitle?: string) — Header with optional title and subtitle\n TextContent(text: string, size?: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\") — Text block. Supports markdown. Optional size: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\".\n MarkDownRenderer(textMarkdown: string, variant?: \"clear\" | \"card\" | \"sunk\") — Renders markdown text with optional container variant\n Callout(variant: \"info\" | \"warning\" | \"error\" | \"success\" | \"neutral\", title: string, description: string, visible?: $binding<boolean>) — Callout banner. Optional visible is a reactive $boolean — auto-dismisses after 3s by setting $visible to false.\n TextCallout(variant?: \"neutral\" | \"info\" | \"warning\" | \"success\" | \"danger\", title?: string, description?: string) — Text callout with variant, title, and description\n Image(alt: string, src?: string) — Image with alt text and optional URL\n ImageBlock(src: string, alt?: string) — Image block with loading state\n ImageGallery(images: {src: string, alt?: string, details?: string}[]) — Gallery grid of images with modal preview\n CodeBlock(language: string, codeString: string) — Syntax-highlighted code block\n - Use Cards to group related KPIs or sections. Stack with direction \"row\" for side-by-side layouts.\n - Success toast: Callout(\"success\", \"Saved\", \"Done.\", $showSuccess) — use @Set($showSuccess, true) in save action, auto-dismisses after 3s. For errors: result.status == \"error\" ? Callout(\"error\", \"Failed\", result.error) : null\n - KPI card: Card([TextContent(\"Label\", \"small\"), TextContent(\"\" + @Count(@Filter(data.rows, \"field\", \"==\", \"value\")), \"large-heavy\")])\n\n Tables:\n Table(columns: Col[]) — Data table — column-oriented. Each Col holds its own data array.\n Col(label: string, data, type?: \"string\" | \"number\" | \"action\") — Column definition — holds label + data array\n - Table is COLUMN-oriented: Table([Col(\"Label\", dataArray), Col(\"Count\", countArray, \"number\")]). Use array pluck for data: data.rows.fieldName\n - Col data can be component arrays for styled cells: Col(\"Status\", @Each(data.rows, \"item\", Tag(item.status, null, \"sm\", item.status == \"open\" ? \"success\" : \"danger\")))\n - Row actions: Col(\"Actions\", @Each(data.rows, \"t\", Button(\"Edit\", Action([@Set($showEdit, true), @Set($editId, t.id)]))))\n - Sortable: sorted = @Sort(data.rows, $sortField, \"desc\"). Bind $sortField to Select. Use sorted.fieldName for Col data\n - Searchable: filtered = @Filter(data.rows, \"title\", \"contains\", $search). Bind $search to Input\n - Chain sort + filter: filtered = @Filter(...) then sorted = @Sort(filtered, ...) — use sorted for both Table and Charts\n - Empty state: @Count(data.rows) > 0 ? Table([...]) : TextContent(\"No data yet\")\n\n Charts (2D):\n BarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string) — Vertical bars; use for comparing values across categories with one or more series\n LineChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string) — Lines over categories; use for trends and continuous data over time\n AreaChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string) — Filled area under lines; use for cumulative totals or volume trends over time\n RadarChart(labels: string[], series: Series[]) — Spider/web chart; use for comparing multiple variables across one or more entities\n HorizontalBarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string) — Horizontal bars; prefer when category labels are long or for ranked lists\n Series(category: string, values: number[]) — One data series\n - Charts accept column arrays: LineChart(labels, [Series(\"Name\", values)]). Use array pluck: LineChart(data.rows.day, [Series(\"Views\", data.rows.views)])\n - Use Cards to wrap charts with CardHeader for titled sections\n - Chart + Table from same source: use @Sort or @Filter result for both LineChart and Table Col data\n - Multiple chart views: use Tabs — Tabs([TabItem(\"line\", \"Line\", [LineChart(...)]), TabItem(\"bar\", \"Bar\", [BarChart(...)])])\n\n Charts (1D):\n PieChart(labels: string[], values: number[], variant?: \"pie\" | \"donut\") — Circular slices; use plucked arrays: PieChart(data.categories, data.values)\n RadialChart(labels: string[], values: number[]) — Radial bars; use plucked arrays: RadialChart(data.categories, data.values)\n SingleStackedBarChart(labels: string[], values: number[]) — Single horizontal stacked bar; use plucked arrays: SingleStackedBarChart(data.categories, data.values)\n Slice(category: string, value: number) — One slice with label and numeric value\n - PieChart and BarChart need NUMBERS, not objects. For list data, use @Count(@Filter(...)) to aggregate:\n - PieChart from list: \\`PieChart([\"Low\", \"Med\", \"High\"], [@Count(@Filter(data.rows, \"priority\", \"==\", \"low\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"medium\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"high\"))], \"donut\")\\`\n - KPI from count: \\`TextContent(\"\" + @Count(@Filter(data.rows, \"status\", \"==\", \"open\")), \"large-heavy\")\\`\n\n Charts (Scatter):\n ScatterChart(datasets: ScatterSeries[], xLabel?: string, yLabel?: string) — X/Y scatter plot; use for correlations, distributions, and clustering\n ScatterSeries(name: string, points: Point[]) — Named dataset\n Point(x: number, y: number, z?: number) — Data point with numeric coordinates\n\n Data Display:\n TagBlock(tags: string[]) — tags is an array of strings\n Tag(text: string, icon?: string, size?: \"sm\" | \"md\" | \"lg\", variant?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\") — Styled tag/badge with optional icon and variant\n - Color-mapped Tag: Tag(value, null, \"sm\", value == \"high\" ? \"danger\" : value == \"medium\" ? \"warning\" : \"neutral\")`,\n );\n\n openui.addSection(\n OPENUI_BUILTIN_FUNCTIONS_TAG,\n dedent`\n Data functions prefixed with \\`@\\` to distinguish from components. These are the ONLY functions available — do NOT invent new ones.\n Use @-prefixed built-in functions (@Count, @Sum, @Avg, @Min, @Max, @Round) on Query results — do NOT hardcode computed values.\n\n @Count(array) → number — Returns array length\n @First(array) → element — Returns first element of array\n @Last(array) → element — Returns last element of array\n @Sum(numbers[]) → number — Sum of numeric array\n @Avg(numbers[]) → number — Average of numeric array\n @Min(numbers[]) → number — Minimum value in array\n @Max(numbers[]) → number — Maximum value in array\n @Sort(array, field, direction?) → sorted array — Sort array by field. Direction: \"asc\" (default) or \"desc\"\n @Filter(array, field, operator: \"==\" | \"!=\" | \">\" | \"<\" | \">=\" | \"<=\" | \"contains\", value) → filtered array — Filter array by field value\n @Round(number, decimals?) → number — Round to N decimal places (default 0)\n @Abs(number) → number — Absolute value\n @Floor(number) → number — Round down to nearest integer\n @Ceil(number) → number — Round up to nearest integer\n @Each(array, varName, template) — Evaluate template for each element. varName is the loop variable — use it ONLY inside the template expression (inline). Do NOT create a separate statement for the template.\n\n Builtins compose — output of one is input to the next:\n \\`@Count(@Filter(data.rows, \"field\", \"==\", \"val\"))\\` for KPIs/chart values, \\`@Round(@Avg(data.rows.score), 1)\\`, \\`@Each(data.rows, \"item\", Comp(item.field))\\` for per-item rendering.\n Array pluck: \\`data.rows.field\\` extracts a field from every row → use with @Sum, @Avg, charts, tables.\n\n IMPORTANT @Each rule: The loop variable (e.g. \"item\") is ONLY available inside the @Each template expression. Always inline the template — do NOT extract it to a separate statement.\n CORRECT: \\`Col(\"Actions\", @Each(rows, \"t\", Button(\"Edit\", Action([@Set($id, t.id)]))))\\`\n WRONG: \\`myBtn = Button(\"Edit\", Action([@Set($id, t.id)]))\\` then \\`Col(\"Actions\", @Each(rows, \"t\", myBtn))\\` — t is undefined in myBtn.`,\n );\n\n openui.addSection(\n OPENUI_HOISTING_AND_STREAMING_TAG,\n dedent`\n openui-lang supports hoisting: a reference can be used BEFORE it is defined. The parser resolves all references after the full input is parsed.\n\n During streaming, the output is re-parsed on every chunk. Undefined references are temporarily unresolved and appear once their definitions stream in. This creates a progressive top-down reveal — structure first, then data fills in.\n\n Recommended statement order for optimal streaming:\n 1. \\`root = Stack(...)\\` — UI shell appears immediately\n 2. $variable declarations — state ready for bindings\n 3. Query statements — defaults resolve immediately so components render with data\n 4. Component definitions — fill in with data already available\n 5. Data values — leaf content last\n\n Always write the root = Stack(...) statement first so the UI shell appears immediately, even before child data has streamed in.`,\n );\n\n openui.addSection(\n OPENUI_EXAMPLES_TAG,\n dedent`\n Example 1 — Table (column-oriented):\n\n root = Stack([title, tbl])\n title = TextContent(\"Top Languages\", \"large-heavy\")\n tbl = Table([Col(\"Language\", langs), Col(\"Users (M)\", users), Col(\"Year\", years)])\n langs = [\"Python\", \"JavaScript\", \"Java\", \"TypeScript\", \"Go\"]\n users = [15.7, 14.2, 12.1, 8.5, 5.2]\n years = [1991, 1995, 1995, 2012, 2009]\n\n Example 2 — Bar chart:\n\n root = Stack([title, chart])\n title = TextContent(\"Q4 Revenue\", \"large-heavy\")\n chart = BarChart(labels, [s1, s2], \"grouped\")\n labels = [\"Oct\", \"Nov\", \"Dec\"]\n s1 = Series(\"Product A\", [120, 150, 180])\n s2 = Series(\"Product B\", [90, 110, 140])\n\n Example 3 — Form with validation:\n\n root = Stack([title, form])\n title = TextContent(\"Contact Us\", \"large-heavy\")\n form = Form(\"contact\", btns, [nameField, emailField, countryField, msgField])\n nameField = FormControl(\"Name\", Input(\"name\", \"Your name\", \"text\", { required: true, minLength: 2 }))\n emailField = FormControl(\"Email\", Input(\"email\", \"you@example.com\", \"email\", { required: true, email: true }))\n countryField = FormControl(\"Country\", Select(\"country\", countryOpts, \"Select...\", { required: true }))\n msgField = FormControl(\"Message\", TextArea(\"message\", \"Tell us more...\", 4, { required: true, minLength: 10 }))\n countryOpts = [SelectItem(\"us\", \"United States\"), SelectItem(\"uk\", \"United Kingdom\"), SelectItem(\"de\", \"Germany\")]\n btns = Buttons([Button(\"Submit\", Action([@ToAssistant(\"Submit\")]), \"primary\"), Button(\"Cancel\", Action([@ToAssistant(\"Cancel\")]), \"secondary\")])\n\n Example 4 — Tabs with mixed content:\n\n root = Stack([title, tabs])\n title = TextContent(\"React vs Vue\", \"large-heavy\")\n tabs = Tabs([tabReact, tabVue])\n tabReact = TabItem(\"react\", \"React\", reactContent)\n tabVue = TabItem(\"vue\", \"Vue\", vueContent)\n reactContent = [TextContent(\"React is a library by Meta for building UIs.\"), Callout(\"info\", \"Note\", \"React uses JSX syntax.\")]\n vueContent = [TextContent(\"Vue is a progressive framework by Evan You.\"), Callout(\"success\", \"Tip\", \"Vue has a gentle learning curve.\")]`,\n );\n\n openui.addSection(\n OPENUI_IMPORTANT_RULES_TAG,\n dedent`\n - When asked about data, generate realistic/plausible data\n - Prefer OpenUI charts for plot/chart/visualize; images only if downloadable\n - Choose components that best represent the content (tables for comparisons, charts for trends, forms for input, etc.)\n - When you render data in an openui block (tables, charts, KPI cards), do NOT repeat the same numbers/facts in the markdown text outside the block.\n - Text outside the openui block can optionally include:\n 1. **Qualitative insights** — patterns, anomalies, or recommendations that aren't obvious from the visual (e.g. \"the high input-to-output ratio suggests document processing\")\n 2. **Actionable next steps** — what the user can do next\n 3. **Caveats/context** — things the data doesn't show\n - If all the information is already visible in the openui components, a brief one-line summary is sufficient — do not enumerate the same values again.`,\n );\n\n openui.addSection(\n OPENUI_FINAL_VERIFICATION_TAG,\n dedent`\n Before finishing, walk your output and verify:\n 1. root = Stack(...) is the FIRST line (for optimal streaming).\n 2. Every referenced name is defined. Every defined name (other than root) is reachable from root.\n\n - For grid-like layouts, use Stack with direction \"row\" and wrap=true. Avoid justify=\"between\" unless you specifically want large gutters.\n - For forms, define one FormControl reference per field so controls can stream progressively.\n - For forms, always provide the second Form argument with Buttons(...) actions: Form(name, buttons, fields).\n - Never nest Form inside Form.\n - Use @Reset($var1, $var2) after form submit to restore defaults — not @Set($var, \"\")\n - Multi-query refresh: Action([@Run(mutation), @Run(query1), @Run(query2), @Reset(...)])\n - $variables are reactive: changing via Select or @Set re-evaluates all Queries and expressions referencing them\n - Use existing components (Tabs, Accordion, Modal) before inventing ternary show/hide patterns`,\n );\n\n openui.addSection(\n OPENUI_USER_INTERACTION_CHECKLIST_TAG,\n dedent`\n 1. Plot/chart/visualize or UI-heavy beyond markdown? Use openui.\n 2. \\`\\`\\`openui\\`\\`\\` fencing must be closed.`,\n );\n}\n\n/** Renders the existing OpenUI prompt tree without duplicating section bodies. */\nexport function renderOpenUIPrompt(): string {\n const root = new InstructionBuilder('_openui_render');\n buildOpenUIInstruction(root);\n const wrapped = root.build();\n const openTag = `<${OPENUI_SECTION_TAG}>`;\n const closeTag = `</${OPENUI_SECTION_TAG}>`;\n const start = wrapped.indexOf(openTag);\n const end = wrapped.lastIndexOf(closeTag);\n if (start === -1 || end === -1) {\n throw new Error('Failed to render OpenUI prompt from buildOpenUIInstruction');\n }\n return wrapped.slice(start, end + closeTag.length);\n}\n\nfunction buildOpenUIDeferredInstruction(builder: InstructionBuilder): void {\n builder.addSection(\n OPENUI_SECTION_TAG,\n dedent`\n The Agent can render interactive UI that markdown cannot express by emitting a fenced \\`\\`\\`openui block.\n\n Before writing any \\`\\`\\`openui fence, the Agent MUST call ${GET_OPENUI_INSTRUCTIONS_TOOL_NAME} with arguments {}.\n Do not invent OpenUI syntax or component APIs without loading those instructions.\n\n Prefer OpenUI for plot/chart/visualize over sandbox code; sandbox images only for downloads.\n Otherwise use OpenUI only when UI-heavy beyond markdown.\n `.trim(),\n );\n}\n\nexport class OpenUIInstructions extends LocalToolMCP {\n readonly name = OPENUI_SERVER_ID;\n readonly displayName = 'OpenUI';\n\n constructor(tracing: AgentTracing) {\n super({ tracing });\n }\n\n private tools: ToolDefinition[] = [\n defineTool({\n name: GET_OPENUI_INSTRUCTIONS_TOOL_NAME,\n description: [\n 'Load the full OpenUI generative-UI authoring instructions (syntax, components, builtins, examples, rules).',\n 'Call this before emitting any ```openui fenced block. Pass an empty object as arguments: {}.',\n ].join(' '),\n schema: getOpenUIInstructionsInputSchema,\n handler: () => Promise.resolve(toolResultResponse({ text: renderOpenUIPrompt() })),\n }),\n ];\n\n protected getTools(): ToolDefinition[] {\n return this.tools;\n }\n}\n\nexport function openUI(options?: { preload?: boolean; tracing?: AgentTracing }): AgentCapability {\n const preload = options?.preload ?? true;\n if (preload) {\n return {\n instructionBuilders: [buildOpenUIInstruction],\n };\n }\n return {\n systemToolSets: [new OpenUIInstructions(options?.tracing ?? NOOP_AGENT_TRACING)],\n instructionBuilders: [buildOpenUIDeferredInstruction],\n };\n}\n"],"mappings":"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","names":["dedent"]}
|
|
@@ -215,6 +215,7 @@ function buildOpenUIInstruction(builder) {
|
|
|
215
215
|
OPENUI_IMPORTANT_RULES_TAG,
|
|
216
216
|
dedent`
|
|
217
217
|
- When asked about data, generate realistic/plausible data
|
|
218
|
+
- Prefer OpenUI charts for plot/chart/visualize; images only if downloadable
|
|
218
219
|
- Choose components that best represent the content (tables for comparisons, charts for trends, forms for input, etc.)
|
|
219
220
|
- When you render data in an openui block (tables, charts, KPI cards), do NOT repeat the same numbers/facts in the markdown text outside the block.
|
|
220
221
|
- Text outside the openui block can optionally include:
|
|
@@ -242,7 +243,7 @@ function buildOpenUIInstruction(builder) {
|
|
|
242
243
|
openui.addSection(
|
|
243
244
|
OPENUI_USER_INTERACTION_CHECKLIST_TAG,
|
|
244
245
|
dedent`
|
|
245
|
-
1.
|
|
246
|
+
1. Plot/chart/visualize or UI-heavy beyond markdown? Use openui.
|
|
246
247
|
2. \`\`\`openui\`\`\` fencing must be closed.`
|
|
247
248
|
);
|
|
248
249
|
}
|
|
@@ -268,7 +269,8 @@ function buildOpenUIDeferredInstruction(builder) {
|
|
|
268
269
|
Before writing any \`\`\`openui fence, the Agent MUST call ${GET_OPENUI_INSTRUCTIONS_TOOL_NAME} with arguments {}.
|
|
269
270
|
Do not invent OpenUI syntax or component APIs without loading those instructions.
|
|
270
271
|
|
|
271
|
-
|
|
272
|
+
Prefer OpenUI for plot/chart/visualize over sandbox code; sandbox images only for downloads.
|
|
273
|
+
Otherwise use OpenUI only when UI-heavy beyond markdown.
|
|
272
274
|
`.trim()
|
|
273
275
|
);
|
|
274
276
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/core/capabilities/builtins/OpenUI.ts"],"sourcesContent":["import dedent from 'dedent';\nimport { z } from 'zod';\nimport { InstructionBuilder } from '../../InstructionBuilder';\nimport { toolResultResponse } from '../../mcp/IMCPServer';\nimport { defineTool, LocalToolMCP, type ToolDefinition } from '../../mcp/LocalToolMCP';\nimport type { AgentTracing } from '../../tracing/AgentTracing';\nimport { NOOP_AGENT_TRACING } from '../../tracing/NoopAgentTracing';\nimport type { AgentCapability } from '../AgentCapability';\n\nexport const OPENUI_SERVER_ID = 'openui';\nexport const GET_OPENUI_INSTRUCTIONS_TOOL_NAME = 'get_openui_instructions';\n\nconst getOpenUIInstructionsInputSchema = z.object({}).strict();\n\nexport const OPENUI_SECTION_TAG = 'openui';\nexport const OPENUI_MARKDOWN_FENCING_TAG = 'openui-markdown-fencing';\nexport const OPENUI_SYNTAX_RULES_TAG = 'openui-syntax-rules';\nexport const OPENUI_COMPONENT_SIGNATURES_TAG = 'openui-component-signatures';\nexport const OPENUI_BUILTIN_FUNCTIONS_TAG = 'openui-builtin-functions';\nexport const OPENUI_HOISTING_AND_STREAMING_TAG = 'openui-hoisting-and-streaming';\nexport const OPENUI_EXAMPLES_TAG = 'openui-examples';\nexport const OPENUI_IMPORTANT_RULES_TAG = 'openui-important-rules';\nexport const OPENUI_FINAL_VERIFICATION_TAG = 'openui-final-verification';\nexport const OPENUI_USER_INTERACTION_CHECKLIST_TAG = 'openui-user-interaction-checklist';\n\nexport function buildOpenUIInstruction(builder: InstructionBuilder): void {\n const openui = builder.beginSection(OPENUI_SECTION_TAG);\n\n openui.addSection(\n OPENUI_MARKDOWN_FENCING_TAG,\n dedent`\n All openui, code must be fenced within openui block.\n \\`\\`\\`openui\n code\n \\`\\`\\``,\n );\n\n openui.addSection(\n OPENUI_SYNTAX_RULES_TAG,\n dedent`\n 1. Each statement is on its own line: \\`identifier = Expression\\`\n 2. \\`root\\` is the entry point — every program must define \\`root = Stack(...)\\`\n 3. Expressions are: strings (\"...\"), numbers, booleans (true/false), null, arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...)\n 4. Use references for readability: define \\`name = ...\\` on one line, then use \\`name\\` later\n 5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array.\n 6. Arguments are POSITIONAL (order matters, not names). Write \\`Stack([children], \"row\", \"l\")\\` NOT \\`Stack([children], direction: \"row\", gap: \"l\")\\` — colon syntax is NOT supported and silently breaks\n 7. Optional arguments can be omitted from the end\n - Strings use double quotes with backslash escaping`,\n );\n\n openui.addSection(\n OPENUI_COMPONENT_SIGNATURES_TAG,\n dedent`\n Arguments marked with ? are optional. Sub-components can be inline or referenced; prefer references for better streaming.\n Props typed \\`ActionExpression\\` accept an Action([@steps...]) expression. See the Action section for available steps (@ToAssistant, @OpenUrl).\n Props marked \\`$binding<type>\\` accept a \\`$variable\\` reference for two-way binding.\n\n Layout:\n Stack([children], direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean) — Flex container. direction: \"row\"|\"column\" (default \"column\"). gap: \"none\"|\"xs\"|\"s\"|\"m\"|\"l\"|\"xl\"|\"2xl\" (default \"m\"). align: \"start\"|\"center\"|\"end\"|\"stretch\"|\"baseline\". justify: \"start\"|\"center\"|\"end\"|\"between\"|\"around\"|\"evenly\".\n Tabs(items: TabItem[]) — Tabbed container\n TabItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[]) — value is unique id, trigger is tab label, content is array of components\n Accordion(items: AccordionItem[]) — Collapsible sections\n AccordionItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[]) — value is unique id, trigger is section title\n Steps(items: StepsItem[]) — Step-by-step guide\n StepsItem(title: string, details: string) — title and details text for one step\n Carousel(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[][], variant?: \"card\" | \"sunk\") — Horizontal scrollable carousel\n Separator(orientation?: \"horizontal\" | \"vertical\", decorative?: boolean) — Visual divider between content sections\n Modal(title: string, open?: $binding<boolean>, children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[], size?: \"sm\" | \"md\" | \"lg\") — Modal dialog. open is a reactive $boolean binding — set to true to open, X/Escape/backdrop auto-closes. Put Form with buttons inside children.\n - For grid-like layouts, use Stack with direction \"row\" and wrap set to true.\n - Prefer justify \"start\" (or omit justify) with wrap=true for stable columns instead of uneven gutters.\n - Use nested Stacks when you need explicit rows/sections.\n - Show/hide sections: $editId != \"\" ? Card([editForm]) : null\n - Modal: Modal(\"Title\", $showModal, [content]) — $showModal is boolean, X/Escape auto-closes. Put Form with its own buttons inside children.\n - Use Tabs for alternative views (chart types, data sections) — no $variable needed\n - Shared filter across Tabs: same $days binding in Query args works across all TabItems\n\n Content:\n Card(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | Tabs | Carousel | Stack)[], variant?: \"card\" | \"sunk\" | \"clear\", direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean) — Styled container. variant: \"card\" (default, elevated) | \"sunk\" (recessed) | \"clear\" (transparent). Always full width. Accepts all Stack flex params (default: direction \"column\"). Cards flex to share space in row/wrap layouts.\n CardHeader(title?: string, subtitle?: string) — Header with optional title and subtitle\n TextContent(text: string, size?: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\") — Text block. Supports markdown. Optional size: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\".\n MarkDownRenderer(textMarkdown: string, variant?: \"clear\" | \"card\" | \"sunk\") — Renders markdown text with optional container variant\n Callout(variant: \"info\" | \"warning\" | \"error\" | \"success\" | \"neutral\", title: string, description: string, visible?: $binding<boolean>) — Callout banner. Optional visible is a reactive $boolean — auto-dismisses after 3s by setting $visible to false.\n TextCallout(variant?: \"neutral\" | \"info\" | \"warning\" | \"success\" | \"danger\", title?: string, description?: string) — Text callout with variant, title, and description\n Image(alt: string, src?: string) — Image with alt text and optional URL\n ImageBlock(src: string, alt?: string) — Image block with loading state\n ImageGallery(images: {src: string, alt?: string, details?: string}[]) — Gallery grid of images with modal preview\n CodeBlock(language: string, codeString: string) — Syntax-highlighted code block\n - Use Cards to group related KPIs or sections. Stack with direction \"row\" for side-by-side layouts.\n - Success toast: Callout(\"success\", \"Saved\", \"Done.\", $showSuccess) — use @Set($showSuccess, true) in save action, auto-dismisses after 3s. For errors: result.status == \"error\" ? Callout(\"error\", \"Failed\", result.error) : null\n - KPI card: Card([TextContent(\"Label\", \"small\"), TextContent(\"\" + @Count(@Filter(data.rows, \"field\", \"==\", \"value\")), \"large-heavy\")])\n\n Tables:\n Table(columns: Col[]) — Data table — column-oriented. Each Col holds its own data array.\n Col(label: string, data, type?: \"string\" | \"number\" | \"action\") — Column definition — holds label + data array\n - Table is COLUMN-oriented: Table([Col(\"Label\", dataArray), Col(\"Count\", countArray, \"number\")]). Use array pluck for data: data.rows.fieldName\n - Col data can be component arrays for styled cells: Col(\"Status\", @Each(data.rows, \"item\", Tag(item.status, null, \"sm\", item.status == \"open\" ? \"success\" : \"danger\")))\n - Row actions: Col(\"Actions\", @Each(data.rows, \"t\", Button(\"Edit\", Action([@Set($showEdit, true), @Set($editId, t.id)]))))\n - Sortable: sorted = @Sort(data.rows, $sortField, \"desc\"). Bind $sortField to Select. Use sorted.fieldName for Col data\n - Searchable: filtered = @Filter(data.rows, \"title\", \"contains\", $search). Bind $search to Input\n - Chain sort + filter: filtered = @Filter(...) then sorted = @Sort(filtered, ...) — use sorted for both Table and Charts\n - Empty state: @Count(data.rows) > 0 ? Table([...]) : TextContent(\"No data yet\")\n\n Charts (2D):\n BarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string) — Vertical bars; use for comparing values across categories with one or more series\n LineChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string) — Lines over categories; use for trends and continuous data over time\n AreaChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string) — Filled area under lines; use for cumulative totals or volume trends over time\n RadarChart(labels: string[], series: Series[]) — Spider/web chart; use for comparing multiple variables across one or more entities\n HorizontalBarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string) — Horizontal bars; prefer when category labels are long or for ranked lists\n Series(category: string, values: number[]) — One data series\n - Charts accept column arrays: LineChart(labels, [Series(\"Name\", values)]). Use array pluck: LineChart(data.rows.day, [Series(\"Views\", data.rows.views)])\n - Use Cards to wrap charts with CardHeader for titled sections\n - Chart + Table from same source: use @Sort or @Filter result for both LineChart and Table Col data\n - Multiple chart views: use Tabs — Tabs([TabItem(\"line\", \"Line\", [LineChart(...)]), TabItem(\"bar\", \"Bar\", [BarChart(...)])])\n\n Charts (1D):\n PieChart(labels: string[], values: number[], variant?: \"pie\" | \"donut\") — Circular slices; use plucked arrays: PieChart(data.categories, data.values)\n RadialChart(labels: string[], values: number[]) — Radial bars; use plucked arrays: RadialChart(data.categories, data.values)\n SingleStackedBarChart(labels: string[], values: number[]) — Single horizontal stacked bar; use plucked arrays: SingleStackedBarChart(data.categories, data.values)\n Slice(category: string, value: number) — One slice with label and numeric value\n - PieChart and BarChart need NUMBERS, not objects. For list data, use @Count(@Filter(...)) to aggregate:\n - PieChart from list: \\`PieChart([\"Low\", \"Med\", \"High\"], [@Count(@Filter(data.rows, \"priority\", \"==\", \"low\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"medium\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"high\"))], \"donut\")\\`\n - KPI from count: \\`TextContent(\"\" + @Count(@Filter(data.rows, \"status\", \"==\", \"open\")), \"large-heavy\")\\`\n\n Charts (Scatter):\n ScatterChart(datasets: ScatterSeries[], xLabel?: string, yLabel?: string) — X/Y scatter plot; use for correlations, distributions, and clustering\n ScatterSeries(name: string, points: Point[]) — Named dataset\n Point(x: number, y: number, z?: number) — Data point with numeric coordinates\n\n Data Display:\n TagBlock(tags: string[]) — tags is an array of strings\n Tag(text: string, icon?: string, size?: \"sm\" | \"md\" | \"lg\", variant?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\") — Styled tag/badge with optional icon and variant\n - Color-mapped Tag: Tag(value, null, \"sm\", value == \"high\" ? \"danger\" : value == \"medium\" ? \"warning\" : \"neutral\")`,\n );\n\n openui.addSection(\n OPENUI_BUILTIN_FUNCTIONS_TAG,\n dedent`\n Data functions prefixed with \\`@\\` to distinguish from components. These are the ONLY functions available — do NOT invent new ones.\n Use @-prefixed built-in functions (@Count, @Sum, @Avg, @Min, @Max, @Round) on Query results — do NOT hardcode computed values.\n\n @Count(array) → number — Returns array length\n @First(array) → element — Returns first element of array\n @Last(array) → element — Returns last element of array\n @Sum(numbers[]) → number — Sum of numeric array\n @Avg(numbers[]) → number — Average of numeric array\n @Min(numbers[]) → number — Minimum value in array\n @Max(numbers[]) → number — Maximum value in array\n @Sort(array, field, direction?) → sorted array — Sort array by field. Direction: \"asc\" (default) or \"desc\"\n @Filter(array, field, operator: \"==\" | \"!=\" | \">\" | \"<\" | \">=\" | \"<=\" | \"contains\", value) → filtered array — Filter array by field value\n @Round(number, decimals?) → number — Round to N decimal places (default 0)\n @Abs(number) → number — Absolute value\n @Floor(number) → number — Round down to nearest integer\n @Ceil(number) → number — Round up to nearest integer\n @Each(array, varName, template) — Evaluate template for each element. varName is the loop variable — use it ONLY inside the template expression (inline). Do NOT create a separate statement for the template.\n\n Builtins compose — output of one is input to the next:\n \\`@Count(@Filter(data.rows, \"field\", \"==\", \"val\"))\\` for KPIs/chart values, \\`@Round(@Avg(data.rows.score), 1)\\`, \\`@Each(data.rows, \"item\", Comp(item.field))\\` for per-item rendering.\n Array pluck: \\`data.rows.field\\` extracts a field from every row → use with @Sum, @Avg, charts, tables.\n\n IMPORTANT @Each rule: The loop variable (e.g. \"item\") is ONLY available inside the @Each template expression. Always inline the template — do NOT extract it to a separate statement.\n CORRECT: \\`Col(\"Actions\", @Each(rows, \"t\", Button(\"Edit\", Action([@Set($id, t.id)]))))\\`\n WRONG: \\`myBtn = Button(\"Edit\", Action([@Set($id, t.id)]))\\` then \\`Col(\"Actions\", @Each(rows, \"t\", myBtn))\\` — t is undefined in myBtn.`,\n );\n\n openui.addSection(\n OPENUI_HOISTING_AND_STREAMING_TAG,\n dedent`\n openui-lang supports hoisting: a reference can be used BEFORE it is defined. The parser resolves all references after the full input is parsed.\n\n During streaming, the output is re-parsed on every chunk. Undefined references are temporarily unresolved and appear once their definitions stream in. This creates a progressive top-down reveal — structure first, then data fills in.\n\n Recommended statement order for optimal streaming:\n 1. \\`root = Stack(...)\\` — UI shell appears immediately\n 2. $variable declarations — state ready for bindings\n 3. Query statements — defaults resolve immediately so components render with data\n 4. Component definitions — fill in with data already available\n 5. Data values — leaf content last\n\n Always write the root = Stack(...) statement first so the UI shell appears immediately, even before child data has streamed in.`,\n );\n\n openui.addSection(\n OPENUI_EXAMPLES_TAG,\n dedent`\n Example 1 — Table (column-oriented):\n\n root = Stack([title, tbl])\n title = TextContent(\"Top Languages\", \"large-heavy\")\n tbl = Table([Col(\"Language\", langs), Col(\"Users (M)\", users), Col(\"Year\", years)])\n langs = [\"Python\", \"JavaScript\", \"Java\", \"TypeScript\", \"Go\"]\n users = [15.7, 14.2, 12.1, 8.5, 5.2]\n years = [1991, 1995, 1995, 2012, 2009]\n\n Example 2 — Bar chart:\n\n root = Stack([title, chart])\n title = TextContent(\"Q4 Revenue\", \"large-heavy\")\n chart = BarChart(labels, [s1, s2], \"grouped\")\n labels = [\"Oct\", \"Nov\", \"Dec\"]\n s1 = Series(\"Product A\", [120, 150, 180])\n s2 = Series(\"Product B\", [90, 110, 140])\n\n Example 3 — Form with validation:\n\n root = Stack([title, form])\n title = TextContent(\"Contact Us\", \"large-heavy\")\n form = Form(\"contact\", btns, [nameField, emailField, countryField, msgField])\n nameField = FormControl(\"Name\", Input(\"name\", \"Your name\", \"text\", { required: true, minLength: 2 }))\n emailField = FormControl(\"Email\", Input(\"email\", \"you@example.com\", \"email\", { required: true, email: true }))\n countryField = FormControl(\"Country\", Select(\"country\", countryOpts, \"Select...\", { required: true }))\n msgField = FormControl(\"Message\", TextArea(\"message\", \"Tell us more...\", 4, { required: true, minLength: 10 }))\n countryOpts = [SelectItem(\"us\", \"United States\"), SelectItem(\"uk\", \"United Kingdom\"), SelectItem(\"de\", \"Germany\")]\n btns = Buttons([Button(\"Submit\", Action([@ToAssistant(\"Submit\")]), \"primary\"), Button(\"Cancel\", Action([@ToAssistant(\"Cancel\")]), \"secondary\")])\n\n Example 4 — Tabs with mixed content:\n\n root = Stack([title, tabs])\n title = TextContent(\"React vs Vue\", \"large-heavy\")\n tabs = Tabs([tabReact, tabVue])\n tabReact = TabItem(\"react\", \"React\", reactContent)\n tabVue = TabItem(\"vue\", \"Vue\", vueContent)\n reactContent = [TextContent(\"React is a library by Meta for building UIs.\"), Callout(\"info\", \"Note\", \"React uses JSX syntax.\")]\n vueContent = [TextContent(\"Vue is a progressive framework by Evan You.\"), Callout(\"success\", \"Tip\", \"Vue has a gentle learning curve.\")]`,\n );\n\n openui.addSection(\n OPENUI_IMPORTANT_RULES_TAG,\n dedent`\n - When asked about data, generate realistic/plausible data\n - Choose components that best represent the content (tables for comparisons, charts for trends, forms for input, etc.)\n - When you render data in an openui block (tables, charts, KPI cards), do NOT repeat the same numbers/facts in the markdown text outside the block.\n - Text outside the openui block can optionally include:\n 1. **Qualitative insights** — patterns, anomalies, or recommendations that aren't obvious from the visual (e.g. \"the high input-to-output ratio suggests document processing\")\n 2. **Actionable next steps** — what the user can do next\n 3. **Caveats/context** — things the data doesn't show\n - If all the information is already visible in the openui components, a brief one-line summary is sufficient — do not enumerate the same values again.`,\n );\n\n openui.addSection(\n OPENUI_FINAL_VERIFICATION_TAG,\n dedent`\n Before finishing, walk your output and verify:\n 1. root = Stack(...) is the FIRST line (for optimal streaming).\n 2. Every referenced name is defined. Every defined name (other than root) is reachable from root.\n\n - For grid-like layouts, use Stack with direction \"row\" and wrap=true. Avoid justify=\"between\" unless you specifically want large gutters.\n - For forms, define one FormControl reference per field so controls can stream progressively.\n - For forms, always provide the second Form argument with Buttons(...) actions: Form(name, buttons, fields).\n - Never nest Form inside Form.\n - Use @Reset($var1, $var2) after form submit to restore defaults — not @Set($var, \"\")\n - Multi-query refresh: Action([@Run(mutation), @Run(query1), @Run(query2), @Reset(...)])\n - $variables are reactive: changing via Select or @Set re-evaluates all Queries and expressions referencing them\n - Use existing components (Tabs, Accordion, Modal) before inventing ternary show/hide patterns`,\n );\n\n openui.addSection(\n OPENUI_USER_INTERACTION_CHECKLIST_TAG,\n dedent`\n 1. Will the response be UI heavy and will contain components which are not supported by markdown? Use openui if the answer is yes.\n 2. \\`\\`\\`openui\\`\\`\\` fencing must be closed.`,\n );\n}\n\n/** Renders the existing OpenUI prompt tree without duplicating section bodies. */\nexport function renderOpenUIPrompt(): string {\n const root = new InstructionBuilder('_openui_render');\n buildOpenUIInstruction(root);\n const wrapped = root.build();\n const openTag = `<${OPENUI_SECTION_TAG}>`;\n const closeTag = `</${OPENUI_SECTION_TAG}>`;\n const start = wrapped.indexOf(openTag);\n const end = wrapped.lastIndexOf(closeTag);\n if (start === -1 || end === -1) {\n throw new Error('Failed to render OpenUI prompt from buildOpenUIInstruction');\n }\n return wrapped.slice(start, end + closeTag.length);\n}\n\nfunction buildOpenUIDeferredInstruction(builder: InstructionBuilder): void {\n builder.addSection(\n OPENUI_SECTION_TAG,\n dedent`\n The Agent can render interactive UI that markdown cannot express by emitting a fenced \\`\\`\\`openui block.\n\n Before writing any \\`\\`\\`openui fence, the Agent MUST call ${GET_OPENUI_INSTRUCTIONS_TOOL_NAME} with arguments {}.\n Do not invent OpenUI syntax or component APIs without loading those instructions.\n\n Use OpenUI only when the response is UI-heavy and needs components markdown does not support.\n `.trim(),\n );\n}\n\nexport class OpenUIInstructions extends LocalToolMCP {\n readonly name = OPENUI_SERVER_ID;\n readonly displayName = 'OpenUI';\n\n constructor(tracing: AgentTracing) {\n super({ tracing });\n }\n\n private tools: ToolDefinition[] = [\n defineTool({\n name: GET_OPENUI_INSTRUCTIONS_TOOL_NAME,\n description: [\n 'Load the full OpenUI generative-UI authoring instructions (syntax, components, builtins, examples, rules).',\n 'Call this before emitting any ```openui fenced block. Pass an empty object as arguments: {}.',\n ].join(' '),\n schema: getOpenUIInstructionsInputSchema,\n handler: () => Promise.resolve(toolResultResponse({ text: renderOpenUIPrompt() })),\n }),\n ];\n\n protected getTools(): ToolDefinition[] {\n return this.tools;\n }\n}\n\nexport function openUI(options?: { preload?: boolean; tracing?: AgentTracing }): AgentCapability {\n const preload = options?.preload ?? true;\n if (preload) {\n return {\n instructionBuilders: [buildOpenUIInstruction],\n };\n }\n return {\n systemToolSets: [new OpenUIInstructions(options?.tracing ?? NOOP_AGENT_TRACING)],\n instructionBuilders: [buildOpenUIDeferredInstruction],\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/capabilities/builtins/OpenUI.ts"],"sourcesContent":["import dedent from 'dedent';\nimport { z } from 'zod';\nimport { InstructionBuilder } from '../../InstructionBuilder';\nimport { toolResultResponse } from '../../mcp/IMCPServer';\nimport { defineTool, LocalToolMCP, type ToolDefinition } from '../../mcp/LocalToolMCP';\nimport type { AgentTracing } from '../../tracing/AgentTracing';\nimport { NOOP_AGENT_TRACING } from '../../tracing/NoopAgentTracing';\nimport type { AgentCapability } from '../AgentCapability';\n\nexport const OPENUI_SERVER_ID = 'openui';\nexport const GET_OPENUI_INSTRUCTIONS_TOOL_NAME = 'get_openui_instructions';\n\nconst getOpenUIInstructionsInputSchema = z.object({}).strict();\n\nexport const OPENUI_SECTION_TAG = 'openui';\nexport const OPENUI_MARKDOWN_FENCING_TAG = 'openui-markdown-fencing';\nexport const OPENUI_SYNTAX_RULES_TAG = 'openui-syntax-rules';\nexport const OPENUI_COMPONENT_SIGNATURES_TAG = 'openui-component-signatures';\nexport const OPENUI_BUILTIN_FUNCTIONS_TAG = 'openui-builtin-functions';\nexport const OPENUI_HOISTING_AND_STREAMING_TAG = 'openui-hoisting-and-streaming';\nexport const OPENUI_EXAMPLES_TAG = 'openui-examples';\nexport const OPENUI_IMPORTANT_RULES_TAG = 'openui-important-rules';\nexport const OPENUI_FINAL_VERIFICATION_TAG = 'openui-final-verification';\nexport const OPENUI_USER_INTERACTION_CHECKLIST_TAG = 'openui-user-interaction-checklist';\n\nexport function buildOpenUIInstruction(builder: InstructionBuilder): void {\n const openui = builder.beginSection(OPENUI_SECTION_TAG);\n\n openui.addSection(\n OPENUI_MARKDOWN_FENCING_TAG,\n dedent`\n All openui, code must be fenced within openui block.\n \\`\\`\\`openui\n code\n \\`\\`\\``,\n );\n\n openui.addSection(\n OPENUI_SYNTAX_RULES_TAG,\n dedent`\n 1. Each statement is on its own line: \\`identifier = Expression\\`\n 2. \\`root\\` is the entry point — every program must define \\`root = Stack(...)\\`\n 3. Expressions are: strings (\"...\"), numbers, booleans (true/false), null, arrays ([...]), objects ({...}), or component calls TypeName(arg1, arg2, ...)\n 4. Use references for readability: define \\`name = ...\\` on one line, then use \\`name\\` later\n 5. EVERY variable (except root) MUST be referenced by at least one other variable. Unreferenced variables are silently dropped and will NOT render. Always include defined variables in their parent's children/items array.\n 6. Arguments are POSITIONAL (order matters, not names). Write \\`Stack([children], \"row\", \"l\")\\` NOT \\`Stack([children], direction: \"row\", gap: \"l\")\\` — colon syntax is NOT supported and silently breaks\n 7. Optional arguments can be omitted from the end\n - Strings use double quotes with backslash escaping`,\n );\n\n openui.addSection(\n OPENUI_COMPONENT_SIGNATURES_TAG,\n dedent`\n Arguments marked with ? are optional. Sub-components can be inline or referenced; prefer references for better streaming.\n Props typed \\`ActionExpression\\` accept an Action([@steps...]) expression. See the Action section for available steps (@ToAssistant, @OpenUrl).\n Props marked \\`$binding<type>\\` accept a \\`$variable\\` reference for two-way binding.\n\n Layout:\n Stack([children], direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean) — Flex container. direction: \"row\"|\"column\" (default \"column\"). gap: \"none\"|\"xs\"|\"s\"|\"m\"|\"l\"|\"xl\"|\"2xl\" (default \"m\"). align: \"start\"|\"center\"|\"end\"|\"stretch\"|\"baseline\". justify: \"start\"|\"center\"|\"end\"|\"between\"|\"around\"|\"evenly\".\n Tabs(items: TabItem[]) — Tabbed container\n TabItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[]) — value is unique id, trigger is tab label, content is array of components\n Accordion(items: AccordionItem[]) — Collapsible sections\n AccordionItem(value: string, trigger: string, content: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[]) — value is unique id, trigger is section title\n Steps(items: StepsItem[]) — Step-by-step guide\n StepsItem(title: string, details: string) — title and details text for one step\n Carousel(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[][], variant?: \"card\" | \"sunk\") — Horizontal scrollable carousel\n Separator(orientation?: \"horizontal\" | \"vertical\", decorative?: boolean) — Visual divider between content sections\n Modal(title: string, open?: $binding<boolean>, children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps)[], size?: \"sm\" | \"md\" | \"lg\") — Modal dialog. open is a reactive $boolean binding — set to true to open, X/Escape/backdrop auto-closes. Put Form with buttons inside children.\n - For grid-like layouts, use Stack with direction \"row\" and wrap set to true.\n - Prefer justify \"start\" (or omit justify) with wrap=true for stable columns instead of uneven gutters.\n - Use nested Stacks when you need explicit rows/sections.\n - Show/hide sections: $editId != \"\" ? Card([editForm]) : null\n - Modal: Modal(\"Title\", $showModal, [content]) — $showModal is boolean, X/Escape auto-closes. Put Form with its own buttons inside children.\n - Use Tabs for alternative views (chart types, data sections) — no $variable needed\n - Shared filter across Tabs: same $days binding in Query args works across all TabItems\n\n Content:\n Card(children: (TextContent | MarkDownRenderer | CardHeader | Callout | TextCallout | CodeBlock | Image | ImageBlock | ImageGallery | Separator | HorizontalBarChart | RadarChart | PieChart | RadialChart | SingleStackedBarChart | ScatterChart | AreaChart | BarChart | LineChart | Table | TagBlock | Form | Buttons | Steps | Tabs | Carousel | Stack)[], variant?: \"card\" | \"sunk\" | \"clear\", direction?: \"row\" | \"column\", gap?: \"none\" | \"xs\" | \"s\" | \"m\" | \"l\" | \"xl\" | \"2xl\", align?: \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\", justify?: \"start\" | \"center\" | \"end\" | \"between\" | \"around\" | \"evenly\", wrap?: boolean) — Styled container. variant: \"card\" (default, elevated) | \"sunk\" (recessed) | \"clear\" (transparent). Always full width. Accepts all Stack flex params (default: direction \"column\"). Cards flex to share space in row/wrap layouts.\n CardHeader(title?: string, subtitle?: string) — Header with optional title and subtitle\n TextContent(text: string, size?: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\") — Text block. Supports markdown. Optional size: \"small\" | \"default\" | \"large\" | \"small-heavy\" | \"large-heavy\".\n MarkDownRenderer(textMarkdown: string, variant?: \"clear\" | \"card\" | \"sunk\") — Renders markdown text with optional container variant\n Callout(variant: \"info\" | \"warning\" | \"error\" | \"success\" | \"neutral\", title: string, description: string, visible?: $binding<boolean>) — Callout banner. Optional visible is a reactive $boolean — auto-dismisses after 3s by setting $visible to false.\n TextCallout(variant?: \"neutral\" | \"info\" | \"warning\" | \"success\" | \"danger\", title?: string, description?: string) — Text callout with variant, title, and description\n Image(alt: string, src?: string) — Image with alt text and optional URL\n ImageBlock(src: string, alt?: string) — Image block with loading state\n ImageGallery(images: {src: string, alt?: string, details?: string}[]) — Gallery grid of images with modal preview\n CodeBlock(language: string, codeString: string) — Syntax-highlighted code block\n - Use Cards to group related KPIs or sections. Stack with direction \"row\" for side-by-side layouts.\n - Success toast: Callout(\"success\", \"Saved\", \"Done.\", $showSuccess) — use @Set($showSuccess, true) in save action, auto-dismisses after 3s. For errors: result.status == \"error\" ? Callout(\"error\", \"Failed\", result.error) : null\n - KPI card: Card([TextContent(\"Label\", \"small\"), TextContent(\"\" + @Count(@Filter(data.rows, \"field\", \"==\", \"value\")), \"large-heavy\")])\n\n Tables:\n Table(columns: Col[]) — Data table — column-oriented. Each Col holds its own data array.\n Col(label: string, data, type?: \"string\" | \"number\" | \"action\") — Column definition — holds label + data array\n - Table is COLUMN-oriented: Table([Col(\"Label\", dataArray), Col(\"Count\", countArray, \"number\")]). Use array pluck for data: data.rows.fieldName\n - Col data can be component arrays for styled cells: Col(\"Status\", @Each(data.rows, \"item\", Tag(item.status, null, \"sm\", item.status == \"open\" ? \"success\" : \"danger\")))\n - Row actions: Col(\"Actions\", @Each(data.rows, \"t\", Button(\"Edit\", Action([@Set($showEdit, true), @Set($editId, t.id)]))))\n - Sortable: sorted = @Sort(data.rows, $sortField, \"desc\"). Bind $sortField to Select. Use sorted.fieldName for Col data\n - Searchable: filtered = @Filter(data.rows, \"title\", \"contains\", $search). Bind $search to Input\n - Chain sort + filter: filtered = @Filter(...) then sorted = @Sort(filtered, ...) — use sorted for both Table and Charts\n - Empty state: @Count(data.rows) > 0 ? Table([...]) : TextContent(\"No data yet\")\n\n Charts (2D):\n BarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string) — Vertical bars; use for comparing values across categories with one or more series\n LineChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string) — Lines over categories; use for trends and continuous data over time\n AreaChart(labels: string[], series: Series[], variant?: \"linear\" | \"natural\" | \"step\", xLabel?: string, yLabel?: string) — Filled area under lines; use for cumulative totals or volume trends over time\n RadarChart(labels: string[], series: Series[]) — Spider/web chart; use for comparing multiple variables across one or more entities\n HorizontalBarChart(labels: string[], series: Series[], variant?: \"grouped\" | \"stacked\", xLabel?: string, yLabel?: string) — Horizontal bars; prefer when category labels are long or for ranked lists\n Series(category: string, values: number[]) — One data series\n - Charts accept column arrays: LineChart(labels, [Series(\"Name\", values)]). Use array pluck: LineChart(data.rows.day, [Series(\"Views\", data.rows.views)])\n - Use Cards to wrap charts with CardHeader for titled sections\n - Chart + Table from same source: use @Sort or @Filter result for both LineChart and Table Col data\n - Multiple chart views: use Tabs — Tabs([TabItem(\"line\", \"Line\", [LineChart(...)]), TabItem(\"bar\", \"Bar\", [BarChart(...)])])\n\n Charts (1D):\n PieChart(labels: string[], values: number[], variant?: \"pie\" | \"donut\") — Circular slices; use plucked arrays: PieChart(data.categories, data.values)\n RadialChart(labels: string[], values: number[]) — Radial bars; use plucked arrays: RadialChart(data.categories, data.values)\n SingleStackedBarChart(labels: string[], values: number[]) — Single horizontal stacked bar; use plucked arrays: SingleStackedBarChart(data.categories, data.values)\n Slice(category: string, value: number) — One slice with label and numeric value\n - PieChart and BarChart need NUMBERS, not objects. For list data, use @Count(@Filter(...)) to aggregate:\n - PieChart from list: \\`PieChart([\"Low\", \"Med\", \"High\"], [@Count(@Filter(data.rows, \"priority\", \"==\", \"low\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"medium\")), @Count(@Filter(data.rows, \"priority\", \"==\", \"high\"))], \"donut\")\\`\n - KPI from count: \\`TextContent(\"\" + @Count(@Filter(data.rows, \"status\", \"==\", \"open\")), \"large-heavy\")\\`\n\n Charts (Scatter):\n ScatterChart(datasets: ScatterSeries[], xLabel?: string, yLabel?: string) — X/Y scatter plot; use for correlations, distributions, and clustering\n ScatterSeries(name: string, points: Point[]) — Named dataset\n Point(x: number, y: number, z?: number) — Data point with numeric coordinates\n\n Data Display:\n TagBlock(tags: string[]) — tags is an array of strings\n Tag(text: string, icon?: string, size?: \"sm\" | \"md\" | \"lg\", variant?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"danger\") — Styled tag/badge with optional icon and variant\n - Color-mapped Tag: Tag(value, null, \"sm\", value == \"high\" ? \"danger\" : value == \"medium\" ? \"warning\" : \"neutral\")`,\n );\n\n openui.addSection(\n OPENUI_BUILTIN_FUNCTIONS_TAG,\n dedent`\n Data functions prefixed with \\`@\\` to distinguish from components. These are the ONLY functions available — do NOT invent new ones.\n Use @-prefixed built-in functions (@Count, @Sum, @Avg, @Min, @Max, @Round) on Query results — do NOT hardcode computed values.\n\n @Count(array) → number — Returns array length\n @First(array) → element — Returns first element of array\n @Last(array) → element — Returns last element of array\n @Sum(numbers[]) → number — Sum of numeric array\n @Avg(numbers[]) → number — Average of numeric array\n @Min(numbers[]) → number — Minimum value in array\n @Max(numbers[]) → number — Maximum value in array\n @Sort(array, field, direction?) → sorted array — Sort array by field. Direction: \"asc\" (default) or \"desc\"\n @Filter(array, field, operator: \"==\" | \"!=\" | \">\" | \"<\" | \">=\" | \"<=\" | \"contains\", value) → filtered array — Filter array by field value\n @Round(number, decimals?) → number — Round to N decimal places (default 0)\n @Abs(number) → number — Absolute value\n @Floor(number) → number — Round down to nearest integer\n @Ceil(number) → number — Round up to nearest integer\n @Each(array, varName, template) — Evaluate template for each element. varName is the loop variable — use it ONLY inside the template expression (inline). Do NOT create a separate statement for the template.\n\n Builtins compose — output of one is input to the next:\n \\`@Count(@Filter(data.rows, \"field\", \"==\", \"val\"))\\` for KPIs/chart values, \\`@Round(@Avg(data.rows.score), 1)\\`, \\`@Each(data.rows, \"item\", Comp(item.field))\\` for per-item rendering.\n Array pluck: \\`data.rows.field\\` extracts a field from every row → use with @Sum, @Avg, charts, tables.\n\n IMPORTANT @Each rule: The loop variable (e.g. \"item\") is ONLY available inside the @Each template expression. Always inline the template — do NOT extract it to a separate statement.\n CORRECT: \\`Col(\"Actions\", @Each(rows, \"t\", Button(\"Edit\", Action([@Set($id, t.id)]))))\\`\n WRONG: \\`myBtn = Button(\"Edit\", Action([@Set($id, t.id)]))\\` then \\`Col(\"Actions\", @Each(rows, \"t\", myBtn))\\` — t is undefined in myBtn.`,\n );\n\n openui.addSection(\n OPENUI_HOISTING_AND_STREAMING_TAG,\n dedent`\n openui-lang supports hoisting: a reference can be used BEFORE it is defined. The parser resolves all references after the full input is parsed.\n\n During streaming, the output is re-parsed on every chunk. Undefined references are temporarily unresolved and appear once their definitions stream in. This creates a progressive top-down reveal — structure first, then data fills in.\n\n Recommended statement order for optimal streaming:\n 1. \\`root = Stack(...)\\` — UI shell appears immediately\n 2. $variable declarations — state ready for bindings\n 3. Query statements — defaults resolve immediately so components render with data\n 4. Component definitions — fill in with data already available\n 5. Data values — leaf content last\n\n Always write the root = Stack(...) statement first so the UI shell appears immediately, even before child data has streamed in.`,\n );\n\n openui.addSection(\n OPENUI_EXAMPLES_TAG,\n dedent`\n Example 1 — Table (column-oriented):\n\n root = Stack([title, tbl])\n title = TextContent(\"Top Languages\", \"large-heavy\")\n tbl = Table([Col(\"Language\", langs), Col(\"Users (M)\", users), Col(\"Year\", years)])\n langs = [\"Python\", \"JavaScript\", \"Java\", \"TypeScript\", \"Go\"]\n users = [15.7, 14.2, 12.1, 8.5, 5.2]\n years = [1991, 1995, 1995, 2012, 2009]\n\n Example 2 — Bar chart:\n\n root = Stack([title, chart])\n title = TextContent(\"Q4 Revenue\", \"large-heavy\")\n chart = BarChart(labels, [s1, s2], \"grouped\")\n labels = [\"Oct\", \"Nov\", \"Dec\"]\n s1 = Series(\"Product A\", [120, 150, 180])\n s2 = Series(\"Product B\", [90, 110, 140])\n\n Example 3 — Form with validation:\n\n root = Stack([title, form])\n title = TextContent(\"Contact Us\", \"large-heavy\")\n form = Form(\"contact\", btns, [nameField, emailField, countryField, msgField])\n nameField = FormControl(\"Name\", Input(\"name\", \"Your name\", \"text\", { required: true, minLength: 2 }))\n emailField = FormControl(\"Email\", Input(\"email\", \"you@example.com\", \"email\", { required: true, email: true }))\n countryField = FormControl(\"Country\", Select(\"country\", countryOpts, \"Select...\", { required: true }))\n msgField = FormControl(\"Message\", TextArea(\"message\", \"Tell us more...\", 4, { required: true, minLength: 10 }))\n countryOpts = [SelectItem(\"us\", \"United States\"), SelectItem(\"uk\", \"United Kingdom\"), SelectItem(\"de\", \"Germany\")]\n btns = Buttons([Button(\"Submit\", Action([@ToAssistant(\"Submit\")]), \"primary\"), Button(\"Cancel\", Action([@ToAssistant(\"Cancel\")]), \"secondary\")])\n\n Example 4 — Tabs with mixed content:\n\n root = Stack([title, tabs])\n title = TextContent(\"React vs Vue\", \"large-heavy\")\n tabs = Tabs([tabReact, tabVue])\n tabReact = TabItem(\"react\", \"React\", reactContent)\n tabVue = TabItem(\"vue\", \"Vue\", vueContent)\n reactContent = [TextContent(\"React is a library by Meta for building UIs.\"), Callout(\"info\", \"Note\", \"React uses JSX syntax.\")]\n vueContent = [TextContent(\"Vue is a progressive framework by Evan You.\"), Callout(\"success\", \"Tip\", \"Vue has a gentle learning curve.\")]`,\n );\n\n openui.addSection(\n OPENUI_IMPORTANT_RULES_TAG,\n dedent`\n - When asked about data, generate realistic/plausible data\n - Prefer OpenUI charts for plot/chart/visualize; images only if downloadable\n - Choose components that best represent the content (tables for comparisons, charts for trends, forms for input, etc.)\n - When you render data in an openui block (tables, charts, KPI cards), do NOT repeat the same numbers/facts in the markdown text outside the block.\n - Text outside the openui block can optionally include:\n 1. **Qualitative insights** — patterns, anomalies, or recommendations that aren't obvious from the visual (e.g. \"the high input-to-output ratio suggests document processing\")\n 2. **Actionable next steps** — what the user can do next\n 3. **Caveats/context** — things the data doesn't show\n - If all the information is already visible in the openui components, a brief one-line summary is sufficient — do not enumerate the same values again.`,\n );\n\n openui.addSection(\n OPENUI_FINAL_VERIFICATION_TAG,\n dedent`\n Before finishing, walk your output and verify:\n 1. root = Stack(...) is the FIRST line (for optimal streaming).\n 2. Every referenced name is defined. Every defined name (other than root) is reachable from root.\n\n - For grid-like layouts, use Stack with direction \"row\" and wrap=true. Avoid justify=\"between\" unless you specifically want large gutters.\n - For forms, define one FormControl reference per field so controls can stream progressively.\n - For forms, always provide the second Form argument with Buttons(...) actions: Form(name, buttons, fields).\n - Never nest Form inside Form.\n - Use @Reset($var1, $var2) after form submit to restore defaults — not @Set($var, \"\")\n - Multi-query refresh: Action([@Run(mutation), @Run(query1), @Run(query2), @Reset(...)])\n - $variables are reactive: changing via Select or @Set re-evaluates all Queries and expressions referencing them\n - Use existing components (Tabs, Accordion, Modal) before inventing ternary show/hide patterns`,\n );\n\n openui.addSection(\n OPENUI_USER_INTERACTION_CHECKLIST_TAG,\n dedent`\n 1. Plot/chart/visualize or UI-heavy beyond markdown? Use openui.\n 2. \\`\\`\\`openui\\`\\`\\` fencing must be closed.`,\n );\n}\n\n/** Renders the existing OpenUI prompt tree without duplicating section bodies. */\nexport function renderOpenUIPrompt(): string {\n const root = new InstructionBuilder('_openui_render');\n buildOpenUIInstruction(root);\n const wrapped = root.build();\n const openTag = `<${OPENUI_SECTION_TAG}>`;\n const closeTag = `</${OPENUI_SECTION_TAG}>`;\n const start = wrapped.indexOf(openTag);\n const end = wrapped.lastIndexOf(closeTag);\n if (start === -1 || end === -1) {\n throw new Error('Failed to render OpenUI prompt from buildOpenUIInstruction');\n }\n return wrapped.slice(start, end + closeTag.length);\n}\n\nfunction buildOpenUIDeferredInstruction(builder: InstructionBuilder): void {\n builder.addSection(\n OPENUI_SECTION_TAG,\n dedent`\n The Agent can render interactive UI that markdown cannot express by emitting a fenced \\`\\`\\`openui block.\n\n Before writing any \\`\\`\\`openui fence, the Agent MUST call ${GET_OPENUI_INSTRUCTIONS_TOOL_NAME} with arguments {}.\n Do not invent OpenUI syntax or component APIs without loading those instructions.\n\n Prefer OpenUI for plot/chart/visualize over sandbox code; sandbox images only for downloads.\n Otherwise use OpenUI only when UI-heavy beyond markdown.\n `.trim(),\n );\n}\n\nexport class OpenUIInstructions extends LocalToolMCP {\n readonly name = OPENUI_SERVER_ID;\n readonly displayName = 'OpenUI';\n\n constructor(tracing: AgentTracing) {\n super({ tracing });\n }\n\n private tools: ToolDefinition[] = [\n defineTool({\n name: GET_OPENUI_INSTRUCTIONS_TOOL_NAME,\n description: [\n 'Load the full OpenUI generative-UI authoring instructions (syntax, components, builtins, examples, rules).',\n 'Call this before emitting any ```openui fenced block. Pass an empty object as arguments: {}.',\n ].join(' '),\n schema: getOpenUIInstructionsInputSchema,\n handler: () => Promise.resolve(toolResultResponse({ text: renderOpenUIPrompt() })),\n }),\n ];\n\n protected getTools(): ToolDefinition[] {\n return this.tools;\n }\n}\n\nexport function openUI(options?: { preload?: boolean; tracing?: AgentTracing }): AgentCapability {\n const preload = options?.preload ?? true;\n if (preload) {\n return {\n instructionBuilders: [buildOpenUIInstruction],\n };\n }\n return {\n systemToolSets: [new OpenUIInstructions(options?.tracing ?? NOOP_AGENT_TRACING)],\n instructionBuilders: [buildOpenUIDeferredInstruction],\n };\n}\n"],"mappings":";AAAA,OAAO,YAAY;AACnB,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,0BAA0B;AACnC,SAAS,YAAY,oBAAyC;AAE9D,SAAS,0BAA0B;AAG5B,IAAM,mBAAmB;AACzB,IAAM,oCAAoC;AAEjD,IAAM,mCAAmC,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO;AAEtD,IAAM,qBAAqB;AAC3B,IAAM,8BAA8B;AACpC,IAAM,0BAA0B;AAChC,IAAM,kCAAkC;AACxC,IAAM,+BAA+B;AACrC,IAAM,oCAAoC;AAC1C,IAAM,sBAAsB;AAC5B,IAAM,6BAA6B;AACnC,IAAM,gCAAgC;AACtC,IAAM,wCAAwC;AAE9C,SAAS,uBAAuB,SAAmC;AACxE,QAAM,SAAS,QAAQ,aAAa,kBAAkB;AAEtD,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA,EAKF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgFF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0BF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwCF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaF;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA;AAAA;AAAA,EAGF;AACF;AAGO,SAAS,qBAA6B;AAC3C,QAAM,OAAO,IAAI,mBAAmB,gBAAgB;AACpD,yBAAuB,IAAI;AAC3B,QAAM,UAAU,KAAK,MAAM;AAC3B,QAAM,UAAU,IAAI,kBAAkB;AACtC,QAAM,WAAW,KAAK,kBAAkB;AACxC,QAAM,QAAQ,QAAQ,QAAQ,OAAO;AACrC,QAAM,MAAM,QAAQ,YAAY,QAAQ;AACxC,MAAI,UAAU,MAAM,QAAQ,IAAI;AAC9B,UAAM,IAAI,MAAM,4DAA4D;AAAA,EAC9E;AACA,SAAO,QAAQ,MAAM,OAAO,MAAM,SAAS,MAAM;AACnD;AAEA,SAAS,+BAA+B,SAAmC;AACzE,UAAQ;AAAA,IACN;AAAA,IACA;AAAA;AAAA;AAAA,mEAG+D,iCAAiC;AAAA;AAAA;AAAA;AAAA;AAAA,MAK9F,KAAK;AAAA,EACT;AACF;AAEO,IAAM,qBAAN,cAAiC,aAAa;AAAA,EAC1C,OAAO;AAAA,EACP,cAAc;AAAA,EAEvB,YAAY,SAAuB;AACjC,UAAM,EAAE,QAAQ,CAAC;AAAA,EACnB;AAAA,EAEQ,QAA0B;AAAA,IAChC,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa;AAAA,QACX;AAAA,QACA;AAAA,MACF,EAAE,KAAK,GAAG;AAAA,MACV,QAAQ;AAAA,MACR,SAAS,MAAM,QAAQ,QAAQ,mBAAmB,EAAE,MAAM,mBAAmB,EAAE,CAAC,CAAC;AAAA,IACnF,CAAC;AAAA,EACH;AAAA,EAEU,WAA6B;AACrC,WAAO,KAAK;AAAA,EACd;AACF;AAEO,SAAS,OAAO,SAA0E;AAC/F,QAAM,UAAU,SAAS,WAAW;AACpC,MAAI,SAAS;AACX,WAAO;AAAA,MACL,qBAAqB,CAAC,sBAAsB;AAAA,IAC9C;AAAA,EACF;AACA,SAAO;AAAA,IACL,gBAAgB,CAAC,IAAI,mBAAmB,SAAS,WAAW,kBAAkB,CAAC;AAAA,IAC/E,qBAAqB,CAAC,8BAA8B;AAAA,EACtD;AACF;","names":[]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { InstructionBuilder } from '../../InstructionBuilder';
|
|
2
|
+
import { LocalToolMCP, type ToolDefinition } from '../../mcp/LocalToolMCP';
|
|
3
|
+
import type { AgentTracing } from '../../tracing/AgentTracing';
|
|
4
|
+
import type { IWebSearchProvider } from '../../web-search/WebSearchProvider';
|
|
5
|
+
import type { AgentCapability } from '../AgentCapability';
|
|
6
|
+
export declare const WEB_SEARCH_SERVER_ID = "web-search";
|
|
7
|
+
export declare const WEB_SEARCH_TOOL_NAME = "web_search";
|
|
8
|
+
export declare const WEB_FETCH_TOOL_NAME = "web_fetch";
|
|
9
|
+
export declare const WEB_SEARCH_REMINDER_TAG = "web-search";
|
|
10
|
+
/**
|
|
11
|
+
* Cross-tool policy (when / how to combine search + fetch).
|
|
12
|
+
* Per-tool behavior and params stay on tool descriptions / schema describes.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildWebSearchInstruction(builder: InstructionBuilder): void;
|
|
15
|
+
export declare class WebSearchTools extends LocalToolMCP {
|
|
16
|
+
#private;
|
|
17
|
+
readonly name = "web-search";
|
|
18
|
+
readonly displayName = "WebSearch";
|
|
19
|
+
constructor(options: {
|
|
20
|
+
provider: IWebSearchProvider;
|
|
21
|
+
tracing: AgentTracing;
|
|
22
|
+
});
|
|
23
|
+
private tools;
|
|
24
|
+
protected getTools(): ToolDefinition[];
|
|
25
|
+
private runSearch;
|
|
26
|
+
private runFetch;
|
|
27
|
+
}
|
|
28
|
+
export declare function webSearch(options: {
|
|
29
|
+
provider: IWebSearchProvider;
|
|
30
|
+
tracing: AgentTracing;
|
|
31
|
+
}): AgentCapability;
|
|
32
|
+
//# sourceMappingURL=WebSearch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebSearch.d.ts","sourceRoot":"","sources":["../../../../src/core/capabilities/builtins/WebSearch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAc,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,oBAAoB,eAAe,CAAC;AACjD,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAC/C,eAAO,MAAM,uBAAuB,eAAe,CAAC;AAyCpD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAiB3E;AAED,qBAAa,cAAe,SAAQ,YAAY;;IAC9C,QAAQ,CAAC,IAAI,gBAAwB;IACrC,QAAQ,CAAC,WAAW,eAAe;IAGnC,YAAY,OAAO,EAAE;QAAE,QAAQ,EAAE,kBAAkB,CAAC;QAAC,OAAO,EAAE,YAAY,CAAA;KAAE,EAG3E;IAED,OAAO,CAAC,KAAK,CAaX;IAEF,SAAS,CAAC,QAAQ,IAAI,cAAc,EAAE,CAErC;YAEa,SAAS;YAQT,QAAQ;CAOvB;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE;IAAE,QAAQ,EAAE,kBAAkB,CAAC;IAAC,OAAO,EAAE,YAAY,CAAA;CAAE,GAAG,eAAe,CAK3G"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/core/capabilities/builtins/WebSearch.ts
|
|
31
|
+
var WebSearch_exports = {};
|
|
32
|
+
__export(WebSearch_exports, {
|
|
33
|
+
WEB_FETCH_TOOL_NAME: () => WEB_FETCH_TOOL_NAME,
|
|
34
|
+
WEB_SEARCH_REMINDER_TAG: () => WEB_SEARCH_REMINDER_TAG,
|
|
35
|
+
WEB_SEARCH_SERVER_ID: () => WEB_SEARCH_SERVER_ID,
|
|
36
|
+
WEB_SEARCH_TOOL_NAME: () => WEB_SEARCH_TOOL_NAME,
|
|
37
|
+
WebSearchTools: () => WebSearchTools,
|
|
38
|
+
buildWebSearchInstruction: () => buildWebSearchInstruction,
|
|
39
|
+
webSearch: () => webSearch
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(WebSearch_exports);
|
|
42
|
+
var import_dedent = __toESM(require("dedent"), 1);
|
|
43
|
+
var import_zod = require("zod");
|
|
44
|
+
var import_InstructionBuilder = require("../../InstructionBuilder.js");
|
|
45
|
+
var import_IMCPServer = require("../../mcp/IMCPServer.js");
|
|
46
|
+
var import_LocalToolMCP = require("../../mcp/LocalToolMCP.js");
|
|
47
|
+
var WEB_SEARCH_SERVER_ID = "web-search";
|
|
48
|
+
var WEB_SEARCH_TOOL_NAME = "web_search";
|
|
49
|
+
var WEB_FETCH_TOOL_NAME = "web_fetch";
|
|
50
|
+
var WEB_SEARCH_REMINDER_TAG = "web-search";
|
|
51
|
+
var webSearchInputSchema = import_zod.z.object({
|
|
52
|
+
search_queries: import_zod.z.array(import_zod.z.string().min(1)).min(1).max(5).describe(
|
|
53
|
+
'Keyword search queries (prefer 3\u20136 words each; 2\u20133 queries often beat one long query). Operators such as site:domain, filetype:pdf, intitle:word, -term, and "exact phrase" may work when the backend supports them.'
|
|
54
|
+
),
|
|
55
|
+
objective: import_zod.z.string().min(1).optional().describe("Natural-language goal for the search. Helps the backend focus hits when provided.")
|
|
56
|
+
}).strict();
|
|
57
|
+
var webFetchInputSchema = import_zod.z.object({
|
|
58
|
+
urls: import_zod.z.array(import_zod.z.url()).min(1).max(5).describe("Page URLs to extract (up to 5). Prefer specific hit URLs from web_search."),
|
|
59
|
+
objective: import_zod.z.string().min(1).optional().describe("Optional goal used to focus extracted excerpts when the page is large.")
|
|
60
|
+
}).strict();
|
|
61
|
+
var WEB_SEARCH_TOOL_DESCRIPTION = "Search the web. Returns ranked hits with titles, URLs, and snippets. Prefer concise keyword queries; use multiple related queries in one call when useful.";
|
|
62
|
+
var WEB_FETCH_TOOL_DESCRIPTION = `Extract page content from URLs as markdown/text (no LLM summarization). Prefer specific hit URLs from ${WEB_SEARCH_TOOL_NAME} when snippets are not enough.`;
|
|
63
|
+
function buildWebSearchInstruction(builder) {
|
|
64
|
+
builder.addSection(
|
|
65
|
+
WEB_SEARCH_REMINDER_TAG,
|
|
66
|
+
import_dedent.default`
|
|
67
|
+
The Agent has two system tools for live web access: ${WEB_SEARCH_TOOL_NAME} (search) and ${WEB_FETCH_TOOL_NAME} (extract page content).
|
|
68
|
+
|
|
69
|
+
When to use them:
|
|
70
|
+
- The user asks to search, browse, verify, look up, or get latest information.
|
|
71
|
+
- Facts may have changed recently (news, prices, laws, schedules, product specs, software APIs/docs, people in roles, rates, scores).
|
|
72
|
+
- The answer needs direct quotes, links, or precise source attribution.
|
|
73
|
+
- A specific page, paper, dataset, or site is referenced and its contents were not provided.
|
|
74
|
+
|
|
75
|
+
How to use them:
|
|
76
|
+
- Start with ${WEB_SEARCH_TOOL_NAME} for discovery; follow with ${WEB_FETCH_TOOL_NAME} on the best URLs when snippets are not enough.
|
|
77
|
+
- Prefer primary and authoritative sources.
|
|
78
|
+
`.trim()
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
var WebSearchTools = class extends import_LocalToolMCP.LocalToolMCP {
|
|
82
|
+
name = WEB_SEARCH_SERVER_ID;
|
|
83
|
+
displayName = "WebSearch";
|
|
84
|
+
#provider;
|
|
85
|
+
constructor(options) {
|
|
86
|
+
super({ tracing: options.tracing });
|
|
87
|
+
this.#provider = options.provider;
|
|
88
|
+
}
|
|
89
|
+
tools = [
|
|
90
|
+
(0, import_LocalToolMCP.defineTool)({
|
|
91
|
+
name: WEB_SEARCH_TOOL_NAME,
|
|
92
|
+
description: WEB_SEARCH_TOOL_DESCRIPTION,
|
|
93
|
+
schema: webSearchInputSchema,
|
|
94
|
+
handler: (input) => this.runSearch(input)
|
|
95
|
+
}),
|
|
96
|
+
(0, import_LocalToolMCP.defineTool)({
|
|
97
|
+
name: WEB_FETCH_TOOL_NAME,
|
|
98
|
+
description: WEB_FETCH_TOOL_DESCRIPTION,
|
|
99
|
+
schema: webFetchInputSchema,
|
|
100
|
+
handler: (input) => this.runFetch(input)
|
|
101
|
+
})
|
|
102
|
+
];
|
|
103
|
+
getTools() {
|
|
104
|
+
return this.tools;
|
|
105
|
+
}
|
|
106
|
+
async runSearch(input) {
|
|
107
|
+
const result = await this.#provider.search({
|
|
108
|
+
search_queries: input.search_queries,
|
|
109
|
+
objective: input.objective
|
|
110
|
+
});
|
|
111
|
+
return (0, import_IMCPServer.toolResultResponse)({ text: JSON.stringify(result) });
|
|
112
|
+
}
|
|
113
|
+
async runFetch(input) {
|
|
114
|
+
const result = await this.#provider.fetch({
|
|
115
|
+
urls: input.urls,
|
|
116
|
+
objective: input.objective
|
|
117
|
+
});
|
|
118
|
+
return (0, import_IMCPServer.toolResultResponse)({ text: JSON.stringify(result) });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
function webSearch(options) {
|
|
122
|
+
return {
|
|
123
|
+
systemToolSets: [new WebSearchTools(options)],
|
|
124
|
+
instructionBuilders: [buildWebSearchInstruction]
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
128
|
+
0 && (module.exports = {
|
|
129
|
+
WEB_FETCH_TOOL_NAME,
|
|
130
|
+
WEB_SEARCH_REMINDER_TAG,
|
|
131
|
+
WEB_SEARCH_SERVER_ID,
|
|
132
|
+
WEB_SEARCH_TOOL_NAME,
|
|
133
|
+
WebSearchTools,
|
|
134
|
+
buildWebSearchInstruction,
|
|
135
|
+
webSearch
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=WebSearch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/capabilities/builtins/WebSearch.ts"],"sourcesContent":["import dedent from 'dedent';\nimport { z } from 'zod';\nimport { InstructionBuilder } from '../../InstructionBuilder';\nimport { type CallToolResponse, toolResultResponse } from '../../mcp/IMCPServer';\nimport { defineTool, LocalToolMCP, type ToolDefinition } from '../../mcp/LocalToolMCP';\nimport type { AgentTracing } from '../../tracing/AgentTracing';\nimport type { IWebSearchProvider } from '../../web-search/WebSearchProvider';\nimport type { AgentCapability } from '../AgentCapability';\n\nexport const WEB_SEARCH_SERVER_ID = 'web-search';\nexport const WEB_SEARCH_TOOL_NAME = 'web_search';\nexport const WEB_FETCH_TOOL_NAME = 'web_fetch';\nexport const WEB_SEARCH_REMINDER_TAG = 'web-search';\n\nconst webSearchInputSchema = z\n .object({\n search_queries: z\n .array(z.string().min(1))\n .min(1)\n .max(5)\n .describe(\n 'Keyword search queries (prefer 3–6 words each; 2–3 queries often beat one long query). Operators such as site:domain, filetype:pdf, intitle:word, -term, and \"exact phrase\" may work when the backend supports them.',\n ),\n objective: z\n .string()\n .min(1)\n .optional()\n .describe('Natural-language goal for the search. Helps the backend focus hits when provided.'),\n })\n .strict();\n\nconst webFetchInputSchema = z\n .object({\n urls: z\n .array(z.url())\n .min(1)\n .max(5)\n .describe('Page URLs to extract (up to 5). Prefer specific hit URLs from web_search.'),\n objective: z\n .string()\n .min(1)\n .optional()\n .describe('Optional goal used to focus extracted excerpts when the page is large.'),\n })\n .strict();\n\n/** What the tool does — param details live on the Zod schema `.describe`s. */\nconst WEB_SEARCH_TOOL_DESCRIPTION =\n 'Search the web. Returns ranked hits with titles, URLs, and snippets. Prefer concise keyword queries; use multiple related queries in one call when useful.';\n\n/** What the tool does — param details live on the Zod schema `.describe`s. */\nconst WEB_FETCH_TOOL_DESCRIPTION = `Extract page content from URLs as markdown/text (no LLM summarization). Prefer specific hit URLs from ${WEB_SEARCH_TOOL_NAME} when snippets are not enough.`;\n\n/**\n * Cross-tool policy (when / how to combine search + fetch).\n * Per-tool behavior and params stay on tool descriptions / schema describes.\n */\nexport function buildWebSearchInstruction(builder: InstructionBuilder): void {\n builder.addSection(\n WEB_SEARCH_REMINDER_TAG,\n dedent`\n The Agent has two system tools for live web access: ${WEB_SEARCH_TOOL_NAME} (search) and ${WEB_FETCH_TOOL_NAME} (extract page content).\n\n When to use them:\n - The user asks to search, browse, verify, look up, or get latest information.\n - Facts may have changed recently (news, prices, laws, schedules, product specs, software APIs/docs, people in roles, rates, scores).\n - The answer needs direct quotes, links, or precise source attribution.\n - A specific page, paper, dataset, or site is referenced and its contents were not provided.\n\n How to use them:\n - Start with ${WEB_SEARCH_TOOL_NAME} for discovery; follow with ${WEB_FETCH_TOOL_NAME} on the best URLs when snippets are not enough.\n - Prefer primary and authoritative sources.\n `.trim(),\n );\n}\n\nexport class WebSearchTools extends LocalToolMCP {\n readonly name = WEB_SEARCH_SERVER_ID;\n readonly displayName = 'WebSearch';\n readonly #provider: IWebSearchProvider;\n\n constructor(options: { provider: IWebSearchProvider; tracing: AgentTracing }) {\n super({ tracing: options.tracing });\n this.#provider = options.provider;\n }\n\n private tools: ToolDefinition[] = [\n defineTool({\n name: WEB_SEARCH_TOOL_NAME,\n description: WEB_SEARCH_TOOL_DESCRIPTION,\n schema: webSearchInputSchema,\n handler: input => this.runSearch(input),\n }),\n defineTool({\n name: WEB_FETCH_TOOL_NAME,\n description: WEB_FETCH_TOOL_DESCRIPTION,\n schema: webFetchInputSchema,\n handler: input => this.runFetch(input),\n }),\n ];\n\n protected getTools(): ToolDefinition[] {\n return this.tools;\n }\n\n private async runSearch(input: z.infer<typeof webSearchInputSchema>): Promise<CallToolResponse> {\n const result = await this.#provider.search({\n search_queries: input.search_queries,\n objective: input.objective,\n });\n return toolResultResponse({ text: JSON.stringify(result) });\n }\n\n private async runFetch(input: z.infer<typeof webFetchInputSchema>): Promise<CallToolResponse> {\n const result = await this.#provider.fetch({\n urls: input.urls,\n objective: input.objective,\n });\n return toolResultResponse({ text: JSON.stringify(result) });\n }\n}\n\nexport function webSearch(options: { provider: IWebSearchProvider; tracing: AgentTracing }): AgentCapability {\n return {\n systemToolSets: [new WebSearchTools(options)],\n instructionBuilders: [buildWebSearchInstruction],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAmB;AACnB,iBAAkB;AAClB,gCAAmC;AACnC,wBAA0D;AAC1D,0BAA8D;AAKvD,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAC5B,IAAM,0BAA0B;AAEvC,IAAM,uBAAuB,aAC1B,OAAO;AAAA,EACN,gBAAgB,aACb,MAAM,aAAE,OAAO,EAAE,IAAI,CAAC,CAAC,EACvB,IAAI,CAAC,EACL,IAAI,CAAC,EACL;AAAA,IACC;AAAA,EACF;AAAA,EACF,WAAW,aACR,OAAO,EACP,IAAI,CAAC,EACL,SAAS,EACT,SAAS,mFAAmF;AACjG,CAAC,EACA,OAAO;AAEV,IAAM,sBAAsB,aACzB,OAAO;AAAA,EACN,MAAM,aACH,MAAM,aAAE,IAAI,CAAC,EACb,IAAI,CAAC,EACL,IAAI,CAAC,EACL,SAAS,2EAA2E;AAAA,EACvF,WAAW,aACR,OAAO,EACP,IAAI,CAAC,EACL,SAAS,EACT,SAAS,wEAAwE;AACtF,CAAC,EACA,OAAO;AAGV,IAAM,8BACJ;AAGF,IAAM,6BAA6B,yGAAyG,oBAAoB;AAMzJ,SAAS,0BAA0B,SAAmC;AAC3E,UAAQ;AAAA,IACN;AAAA,IACA,cAAAA;AAAA,4DACwD,oBAAoB,iBAAiB,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qBAS/F,oBAAoB,+BAA+B,mBAAmB;AAAA;AAAA,MAErF,KAAK;AAAA,EACT;AACF;AAEO,IAAM,iBAAN,cAA6B,iCAAa;AAAA,EACtC,OAAO;AAAA,EACP,cAAc;AAAA,EACd;AAAA,EAET,YAAY,SAAkE;AAC5E,UAAM,EAAE,SAAS,QAAQ,QAAQ,CAAC;AAClC,SAAK,YAAY,QAAQ;AAAA,EAC3B;AAAA,EAEQ,QAA0B;AAAA,QAChC,gCAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS,WAAS,KAAK,UAAU,KAAK;AAAA,IACxC,CAAC;AAAA,QACD,gCAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS,WAAS,KAAK,SAAS,KAAK;AAAA,IACvC,CAAC;AAAA,EACH;AAAA,EAEU,WAA6B;AACrC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,UAAU,OAAwE;AAC9F,UAAM,SAAS,MAAM,KAAK,UAAU,OAAO;AAAA,MACzC,gBAAgB,MAAM;AAAA,MACtB,WAAW,MAAM;AAAA,IACnB,CAAC;AACD,eAAO,sCAAmB,EAAE,MAAM,KAAK,UAAU,MAAM,EAAE,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAc,SAAS,OAAuE;AAC5F,UAAM,SAAS,MAAM,KAAK,UAAU,MAAM;AAAA,MACxC,MAAM,MAAM;AAAA,MACZ,WAAW,MAAM;AAAA,IACnB,CAAC;AACD,eAAO,sCAAmB,EAAE,MAAM,KAAK,UAAU,MAAM,EAAE,CAAC;AAAA,EAC5D;AACF;AAEO,SAAS,UAAU,SAAmF;AAC3G,SAAO;AAAA,IACL,gBAAgB,CAAC,IAAI,eAAe,OAAO,CAAC;AAAA,IAC5C,qBAAqB,CAAC,yBAAyB;AAAA,EACjD;AACF;","names":["dedent"]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// src/core/capabilities/builtins/WebSearch.ts
|
|
2
|
+
import dedent from "dedent";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import "../../InstructionBuilder.mjs";
|
|
5
|
+
import { toolResultResponse } from "../../mcp/IMCPServer.mjs";
|
|
6
|
+
import { defineTool, LocalToolMCP } from "../../mcp/LocalToolMCP.mjs";
|
|
7
|
+
var WEB_SEARCH_SERVER_ID = "web-search";
|
|
8
|
+
var WEB_SEARCH_TOOL_NAME = "web_search";
|
|
9
|
+
var WEB_FETCH_TOOL_NAME = "web_fetch";
|
|
10
|
+
var WEB_SEARCH_REMINDER_TAG = "web-search";
|
|
11
|
+
var webSearchInputSchema = z.object({
|
|
12
|
+
search_queries: z.array(z.string().min(1)).min(1).max(5).describe(
|
|
13
|
+
'Keyword search queries (prefer 3\u20136 words each; 2\u20133 queries often beat one long query). Operators such as site:domain, filetype:pdf, intitle:word, -term, and "exact phrase" may work when the backend supports them.'
|
|
14
|
+
),
|
|
15
|
+
objective: z.string().min(1).optional().describe("Natural-language goal for the search. Helps the backend focus hits when provided.")
|
|
16
|
+
}).strict();
|
|
17
|
+
var webFetchInputSchema = z.object({
|
|
18
|
+
urls: z.array(z.url()).min(1).max(5).describe("Page URLs to extract (up to 5). Prefer specific hit URLs from web_search."),
|
|
19
|
+
objective: z.string().min(1).optional().describe("Optional goal used to focus extracted excerpts when the page is large.")
|
|
20
|
+
}).strict();
|
|
21
|
+
var WEB_SEARCH_TOOL_DESCRIPTION = "Search the web. Returns ranked hits with titles, URLs, and snippets. Prefer concise keyword queries; use multiple related queries in one call when useful.";
|
|
22
|
+
var WEB_FETCH_TOOL_DESCRIPTION = `Extract page content from URLs as markdown/text (no LLM summarization). Prefer specific hit URLs from ${WEB_SEARCH_TOOL_NAME} when snippets are not enough.`;
|
|
23
|
+
function buildWebSearchInstruction(builder) {
|
|
24
|
+
builder.addSection(
|
|
25
|
+
WEB_SEARCH_REMINDER_TAG,
|
|
26
|
+
dedent`
|
|
27
|
+
The Agent has two system tools for live web access: ${WEB_SEARCH_TOOL_NAME} (search) and ${WEB_FETCH_TOOL_NAME} (extract page content).
|
|
28
|
+
|
|
29
|
+
When to use them:
|
|
30
|
+
- The user asks to search, browse, verify, look up, or get latest information.
|
|
31
|
+
- Facts may have changed recently (news, prices, laws, schedules, product specs, software APIs/docs, people in roles, rates, scores).
|
|
32
|
+
- The answer needs direct quotes, links, or precise source attribution.
|
|
33
|
+
- A specific page, paper, dataset, or site is referenced and its contents were not provided.
|
|
34
|
+
|
|
35
|
+
How to use them:
|
|
36
|
+
- Start with ${WEB_SEARCH_TOOL_NAME} for discovery; follow with ${WEB_FETCH_TOOL_NAME} on the best URLs when snippets are not enough.
|
|
37
|
+
- Prefer primary and authoritative sources.
|
|
38
|
+
`.trim()
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
var WebSearchTools = class extends LocalToolMCP {
|
|
42
|
+
name = WEB_SEARCH_SERVER_ID;
|
|
43
|
+
displayName = "WebSearch";
|
|
44
|
+
#provider;
|
|
45
|
+
constructor(options) {
|
|
46
|
+
super({ tracing: options.tracing });
|
|
47
|
+
this.#provider = options.provider;
|
|
48
|
+
}
|
|
49
|
+
tools = [
|
|
50
|
+
defineTool({
|
|
51
|
+
name: WEB_SEARCH_TOOL_NAME,
|
|
52
|
+
description: WEB_SEARCH_TOOL_DESCRIPTION,
|
|
53
|
+
schema: webSearchInputSchema,
|
|
54
|
+
handler: (input) => this.runSearch(input)
|
|
55
|
+
}),
|
|
56
|
+
defineTool({
|
|
57
|
+
name: WEB_FETCH_TOOL_NAME,
|
|
58
|
+
description: WEB_FETCH_TOOL_DESCRIPTION,
|
|
59
|
+
schema: webFetchInputSchema,
|
|
60
|
+
handler: (input) => this.runFetch(input)
|
|
61
|
+
})
|
|
62
|
+
];
|
|
63
|
+
getTools() {
|
|
64
|
+
return this.tools;
|
|
65
|
+
}
|
|
66
|
+
async runSearch(input) {
|
|
67
|
+
const result = await this.#provider.search({
|
|
68
|
+
search_queries: input.search_queries,
|
|
69
|
+
objective: input.objective
|
|
70
|
+
});
|
|
71
|
+
return toolResultResponse({ text: JSON.stringify(result) });
|
|
72
|
+
}
|
|
73
|
+
async runFetch(input) {
|
|
74
|
+
const result = await this.#provider.fetch({
|
|
75
|
+
urls: input.urls,
|
|
76
|
+
objective: input.objective
|
|
77
|
+
});
|
|
78
|
+
return toolResultResponse({ text: JSON.stringify(result) });
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
function webSearch(options) {
|
|
82
|
+
return {
|
|
83
|
+
systemToolSets: [new WebSearchTools(options)],
|
|
84
|
+
instructionBuilders: [buildWebSearchInstruction]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
WEB_FETCH_TOOL_NAME,
|
|
89
|
+
WEB_SEARCH_REMINDER_TAG,
|
|
90
|
+
WEB_SEARCH_SERVER_ID,
|
|
91
|
+
WEB_SEARCH_TOOL_NAME,
|
|
92
|
+
WebSearchTools,
|
|
93
|
+
buildWebSearchInstruction,
|
|
94
|
+
webSearch
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=WebSearch.mjs.map
|