feat(fs): add a search render-intent card for grep and glob results
grep and glob returned only model-facing text; the structured matches/paths
never reached the client. Add a card:'search' result view with a kind
discriminant ('matches' grouped by file for grep, 'paths' for glob), projected
through each tool's output.presentationMeta and read back in presentResult. The
projections re-apply the same inline cap and per-line budget as the render text
and report total + truncated, so a UI never presents a capped page as complete.
A UI without the search card falls back to content; the TUI is unchanged. The
web consumer is a follow-up.
This commit is contained in:
@@ -82,6 +82,11 @@ export type {
|
||||
GenericResultView,
|
||||
TerminalResultView,
|
||||
DiffResultView,
|
||||
SearchResultView,
|
||||
SearchMatchesResultView,
|
||||
SearchPathsResultView,
|
||||
SearchFileMatches,
|
||||
SearchLineMatch,
|
||||
} from './presentation.ts'
|
||||
|
||||
declare module 'cordis' {
|
||||
|
||||
@@ -125,7 +125,7 @@ export interface DiffCallView {
|
||||
* `ToolDefinition.presentResult`; omitting the method keeps the pending
|
||||
* title and renders the raw result content.
|
||||
*/
|
||||
export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView
|
||||
export type ToolResultView = GenericResultView | TerminalResultView | DiffResultView | SearchResultView
|
||||
|
||||
/**
|
||||
* The default completed card: an optional replacement title and reformatted
|
||||
@@ -176,3 +176,90 @@ export interface DiffResultView {
|
||||
/** The change to show, in file order — applied contextual hunks, or a whole-file diff when there is no before-image. */
|
||||
diffs: FileDiff[]
|
||||
}
|
||||
|
||||
/** One matched line inside a {@link SearchFileMatches} group: its 1-based line number and text. */
|
||||
export interface SearchLineMatch {
|
||||
/** 1-based line number of the match within its file. */
|
||||
lineNumber: number
|
||||
/** The matched line text, as the tool surfaced it (the per-line preview budget already applied). */
|
||||
line: string
|
||||
}
|
||||
|
||||
/** One file's grouped content matches for a {@link SearchMatchesResultView}, in first-seen file order. */
|
||||
export interface SearchFileMatches {
|
||||
/** The file the matches belong to (the model-facing display path). */
|
||||
path: string
|
||||
/** The file's matched lines, in output order. */
|
||||
matches: SearchLineMatch[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A completed content search (`grep`) rendered as a search card whose matches are
|
||||
* grouped by file, so a capable UI can list each file as an expandable group of
|
||||
* its matched lines. `kind: 'matches'` discriminates this shape from the path
|
||||
* shape ({@link SearchPathsResultView}) within {@link SearchResultView}.
|
||||
*/
|
||||
export interface SearchMatchesResultView {
|
||||
card: 'search'
|
||||
kind: 'matches'
|
||||
/** Replacement title for the completed call. Omit to keep the pending-state title. */
|
||||
title?: string
|
||||
/** Matched lines grouped by file, in first-seen file order. */
|
||||
files: SearchFileMatches[]
|
||||
/**
|
||||
* Whether the tool capped the inline result: `files` carries only the retained
|
||||
* matches, not every match the search found. A UI shows a capped indicator so it
|
||||
* never presents a partial group as complete.
|
||||
*/
|
||||
truncated: boolean
|
||||
/** Total matches the search found before capping (equals the retained count when not `truncated`). */
|
||||
total: number
|
||||
/**
|
||||
* UI-facing content blocks reproducing the model-facing result text, so a UI
|
||||
* without a dedicated search card renders it as text. Omit to let the UI render
|
||||
* the raw result content.
|
||||
*/
|
||||
content?: ContentBlock[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A completed path search (`glob`) rendered as a search card whose result is a flat
|
||||
* path list. `kind: 'paths'` discriminates this shape from the grouped-matches
|
||||
* shape ({@link SearchMatchesResultView}) within {@link SearchResultView}.
|
||||
*/
|
||||
export interface SearchPathsResultView {
|
||||
card: 'search'
|
||||
kind: 'paths'
|
||||
/** Replacement title for the completed call. Omit to keep the pending-state title. */
|
||||
title?: string
|
||||
/** The discovered paths, in the tool's result order (the retained page when `truncated`). */
|
||||
paths: string[]
|
||||
/**
|
||||
* Whether the tool capped the inline result: `paths` carries only the retained
|
||||
* page, not every path the search found. A UI shows a capped indicator so it
|
||||
* never presents a partial list as complete.
|
||||
*/
|
||||
truncated: boolean
|
||||
/** Total paths the search found before capping (equals `paths.length` when not `truncated`). */
|
||||
total: number
|
||||
/**
|
||||
* UI-facing content blocks reproducing the model-facing result text, so a UI
|
||||
* without a dedicated search card renders it as text. Omit to let the UI render
|
||||
* the raw result content.
|
||||
*/
|
||||
content?: ContentBlock[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A completed search rendered as a search card, the result-time view a discovery
|
||||
* tool (`grep`, `glob`) returns from `presentResult`. One `card: 'search'` view
|
||||
* with two `kind`-discriminated shapes: grouped-by-file content matches
|
||||
* ({@link SearchMatchesResultView}) and a flat path list
|
||||
* ({@link SearchPathsResultView}). Both carry a `truncated`/`total` signal so a UI
|
||||
* never presents a capped result as complete, and an optional `content` a UI
|
||||
* without a search card renders as text. There is no call-time analogue: a search
|
||||
* call stays a {@link GenericCallView} (`kind: 'search'`) because the pending
|
||||
* state has no matches or paths to show — the structured shape exists only after
|
||||
* `execute`.
|
||||
*/
|
||||
export type SearchResultView = SearchMatchesResultView | SearchPathsResultView
|
||||
|
||||
Reference in New Issue
Block a user