Skip to content

feat(AI-179): implement /fiona search — browse raw sources without synthesized answer#81

Draft
roberthunterjr with Copilot wants to merge 19 commits into
mainfrom
copilot/ai-179-clone-fiona-search
Draft

feat(AI-179): implement /fiona search — browse raw sources without synthesized answer#81
roberthunterjr with Copilot wants to merge 19 commits into
mainfrom
copilot/ai-179-clone-fiona-search

Conversation

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

/fiona search <query> was a "coming soon" stub. This implements the full skill: calls Perplexity non-streaming, returns 3–5 source snippets with hyperlinks, and deliberately omits any synthesized answer.

Core changes

  • src/agent/llm-caller.js — new searchForSources(query, options): non-streaming Perplexity call that prefers search_results (title + snippet) over citations (URL-only fallback), capped at 5 results
  • src/agent/search-caller.js (new) — abstraction layer re-exporting searchForSources; also owns formatSearchResults() (Slack mrkdwn renderer) and escapeMrkdwn() (HTML-entity-encodes &/</> in user input before embedding in message strings)
  • src/listeners/commands/fiona.jshandleSearch() replaces the stub: empty query falls back to help; rate-limit exceeded responds ephemerally; otherwise ack() immediately → search → respond() with formatted sources; records slash_search
  • src/listeners/commands/command-handler.js — adds handleSearchViaSay() for @-mention/assistant-panel entry points; routeCommandViaSay() dispatches search there; removes SEARCH_NOT_YET_TEXT; drops "(coming soon)" from HELP_TEXT search line

Example output (Slack mrkdwn)

🔍 *Search results for:* _"assessment API endpoints"_

1. *<https://docs.ed-fi.org/assessment|Assessment API — Ed-Fi ODS/API Documentation>*
_"The Assessment API provides endpoints for creating, reading, updating, and deleting assessment metadata…"_

2. *<https://www.ed-fi.org/guide|API Guidelines — Ed-Fi Alliance>*

No results → 🔍 No sources found for _"query"_. Try rephrasing your query.

Test surface

  • tests/agent/search-caller.test.js (new, 38 tests): searchForSources with search_results/citations/empty/error paths; formatSearchResults; escapeMrkdwn
  • tests/listeners/commands/fiona.test.js: search sub-command replaced with real-behavior tests (empty query → help fallback, query → ack+respond+record, missing fields, rate limiting)
  • tests/listeners/commands/command-handler.test.js: mocks search-caller, adds handleSearchViaSay tests
  • Existing app-mention, message, and escalation test files updated: added searchForSources to their llm-caller mocks and updated the "coming soon" search assertions

Copilot AI and others added 3 commits July 23, 2026 16:06
…nthesized answer

- Add searchForSources() to llm-caller.js: non-streaming Perplexity call
  that extracts search_results (with title/snippet) or falls back to
  citations (URL-only), returning up to 5 normalized NormalizedSource items.

- Create src/agent/search-caller.js: thin abstraction layer that re-exports
  searchForSources from llm-caller and provides formatSearchResults() and
  escapeMrkdwn() helpers for Slack mrkdwn formatting.

- Update src/listeners/commands/fiona.js: replace the coming-soon stub for
  /fiona search with a real handleSearch() handler — extracts the query,
  falls back to help when empty, checks rate limits, calls searchForSources,
  and responds ephemerally with formatted source snippets.

- Update src/listeners/commands/command-handler.js: remove SEARCH_NOT_YET_TEXT,
  add handleSearchViaSay() for the @-mention / assistant-panel paths, update
  routeCommandViaSay() to dispatch search to handleSearchViaSay(), and remove
  the (coming soon) annotation from HELP_TEXT.

- Update tests: fiona.test.js, command-handler.test.js, app-mention.test.js,
  message.test.js, escalation.test.js reflect the new real-search behavior.
  New test file: tests/agent/search-caller.test.js (38 unit tests).

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
…brittle test

- Rename handleComingSoonViaSay parameter from subCommand to keyword
- Simplify HELP_TEXT test to use regex instead of exact whitespace match

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
- Move SEARCH_ERROR_TEXT export inline next to its definition in search-caller.js
- Add err.message alongside err.name in handleSearchViaSay error log

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
Copilot AI changed the title [WIP] Implement /fiona search functionality for browsing raw sources feat(AI-179): implement /fiona search — browse raw sources without synthesized answer Jul 23, 2026
Copilot AI requested a review from roberthunterjr July 23, 2026 16:11

try {
const response = await perplexityClient.chat.completions.create({
model: PERPLEXITY_API_MODEL,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is using whatever the default model for pplx is. Currently this is set to sonar, which is the synthesis model. We should update this to use the search api to reduce the adjustments to extracting sources and limiting count, this API will handle most of that. Please look up the guidance on implementing the search API and apply here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched searchForSources to the Perplexity Search API (POST https://api.perplexity.ai/search) via native fetch. The Search API directly returns a structured results array (title, url, snippet) so the search_results/citations extraction and fallback logic has been removed. Count limiting is now delegated to the API via the max_results parameter, with normalizeSources as a secondary cap.

Updated search-caller.test.js to mock globalThis.fetch instead of openai.chat.completions.create, and added a test for HTTP error responses. All 570 tests pass. Commit: feat(AI-179): switch searchForSources to Perplexity Search API (POST /search)

Copilot AI and others added 2 commits July 23, 2026 16:26
…/search)

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>

@roberthunterjr roberthunterjr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot. Looking good overall. A couple of comments from before to address. Also, here is an image of the reponse seen.

Image

}

// ─── Source Search ─────────────────────────────────────────────────────────
const SEARCH_MAX_SOURCES = 5;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was specified in ticket to be 5. Lets make this an env value and set a max gate here to 10, that sends the minimum of the SEARCH_MAX_SOURCES value and 10 when performing the actual API call

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SEARCH_MAX_SOURCES now reads from process.env.SEARCH_MAX_SOURCES (default 5). A hard cap of SEARCH_ABSOLUTE_MAX = 10 is enforced via Math.min(maxSources, 10) before the Perplexity API call — so max_results never exceeds 10 regardless of the env value. Commit: feat(AI-179): env-backed SEARCH_MAX_SOURCES with hard cap

* @param {Array<import('./llm-caller.js').NormalizedSource>} sources - Normalized source list
* @returns {string} Slack mrkdwn-formatted search results string
*/
export function formatSearchResults(query, sources) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking pretty good. One issue I see is the response items appear as a long, unformatted list, making it difficult to read. Lets improve the readabilitiy, perhaps formatting the snippet and truncating the content more so the total number of sources is easier to see.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a truncateSnippet helper in search-caller.js that:

  • Collapses newlines and extra whitespace to a single space
  • Strips **bold** markers (using [\s\S]*? to handle asterisks inside bold text) and ### heading markers
  • Truncates to 150 chars at a word boundary and appends

The SNIPPET_MAX_CHARS limit is also env-backed via SEARCH_SNIPPET_MAX_CHARS (default 150) so operators can tune it. Single-asterisk italic is intentionally not stripped to avoid false positives on math/code notation like a * b. Commit: feat(AI-179): env-backed SEARCH_MAX_SOURCES with hard cap; strip markdown and truncate snippets

Copilot AI and others added 4 commits July 23, 2026 18:37
…down and truncate snippets

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
…ateSnippet

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
…ted to avoid mock breakage

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>

@roberthunterjr roberthunterjr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot A few tweaks here:

Lets use the perplexity client for the search api as seen here, instead of direct API call:
https://docs.perplexity.ai/docs/search/best-practices

Lets ensure the client for Search API also respects the domain filtering defaults or configuration from the .env for filtering.

The snippets revealed as search results are still too long, lets make the snippet truncate after 160 words.

Lastly, lets clear the deliniation between search results. Either lets improve markdown or try something like block kit cards to make the seperation of results and their source and snippets clearer.

Copilot AI and others added 2 commits July 23, 2026 21:58
…er, word truncation, block kit

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
…lock Kit cards

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>

Copilot AI commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

@copilot A few tweaks here:

Lets use the perplexity client for the search api as seen here, instead of direct API call:...

Done in commit feat(AI-179): use Perplexity SDK, domain filter, 160-word snippets, Block Kit cards.

  • Perplexity SDKsearchForSources now uses @perplexity-ai/perplexity_ai (client.search.create(...)) instead of raw fetch; the package is added as a dependency.
  • Domain filteringsearch_domain_filter: PERPLEXITY_DOMAIN_FILTER is passed to every search.create call, so the PERPLEXITY_DOMAIN_FILTER env var (defaulting to www.ed-fi.org,docs.ed-fi.org) applies to search the same way it does to chat completions.
  • 160-word truncationtruncateSnippet now counts words instead of characters, capped at 160 (configurable via SEARCH_SNIPPET_MAX_WORDS). Both env vars are documented in .env.sample.
  • Block Kit cardsformatSearchResults now returns { text, blocks }. Each result is a section block (numbered title link) + context block (snippet), separated by divider blocks for clear visual separation. The plain-text text field serves as a notification/accessibility fallback. All callers updated to spread { text, blocks } into respond()/say().

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
Copilot AI and others added 5 commits July 23, 2026 23:02
Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>
@roberthunterjr

Copy link
Copy Markdown
Contributor

@copilot

It looks like the build tests failed due to linting errors. Please correct and provide update

Co-authored-by: roberthunterjr <8196647+roberthunterjr@users.noreply.github.com>

Copilot AI commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@copilot

It looks like the build tests failed due to linting errors. Please correct and provide update

Fixed — ran npm run lint:fix (Biome) on feedback.js, feedback_block.js, and feedback_reason.js to correct formatting. Lint and full test suite (588 tests) now pass. Commit: fix: resolve biome lint/format errors in feedback files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants