From b4f30d773ca295e5533d58ca92db633e5257b36b Mon Sep 17 00:00:00 2001
From: "knowgrph-lifecycle[bot]"
Date: Thu, 13 Aug 2026 03:55:55 +0800
Subject: [PATCH 1/3] chore(coordination): claim
exa-coding-agent-integrations-v2 lease 484
From cafceab91a875eb5f84265f5706d1ed509c8dfab Mon Sep 17 00:00:00 2001
From: "knowgrph-lifecycle[bot]"
Date: Thu, 13 Aug 2026 04:08:27 +0800
Subject: [PATCH 2/3] feat(exa): project coding-agent search contract
---
.../helpers/mainPanelMcpExpectations.ts | 15 ++
.../mainPanelExaIntegrations.test.tsx | 122 ++++++++++++
.../mainPanelSkillsCommands.test.tsx | 8 +-
canvas/src/features/canvas/utils.ts | 1 +
.../ExaSearchSkillsCommandsProjection.tsx | 54 +++++
.../features/panels/views/exaMcpApiDocs.ts | 44 +++++
.../features/panels/views/exaSearchApiDocs.ts | 185 ++++++++++++++++++
.../panels/views/settingsView.constants.ts | 7 +
.../panels/views/useSettingsView.helpers.ts | 3 +
.../features/panels/views/useSettingsView.ts | 15 +-
.../FloatingPanelSkillsCommandsView.tsx | 8 +-
canvas/src/tests/registry/postParserCases5.ts | 3 +
.../knowgrph-mcp/knowgrph-exa-mcp-prd-tad.md | 81 +++++---
docs/runtime-readiness-contract.md | 2 +-
grph-shared/package.json | 5 +
grph-shared/src/search/exaSearchApiSsot.ts | 107 ++++++++++
16 files changed, 630 insertions(+), 30 deletions(-)
create mode 100644 canvas/src/__tests__/mainPanelExaIntegrations.test.tsx
create mode 100644 canvas/src/features/integrations/ExaSearchSkillsCommandsProjection.tsx
create mode 100644 canvas/src/features/panels/views/exaSearchApiDocs.ts
create mode 100644 grph-shared/src/search/exaSearchApiSsot.ts
diff --git a/canvas/src/__tests__/helpers/mainPanelMcpExpectations.ts b/canvas/src/__tests__/helpers/mainPanelMcpExpectations.ts
index 79ef2c6ba..004b8d597 100644
--- a/canvas/src/__tests__/helpers/mainPanelMcpExpectations.ts
+++ b/canvas/src/__tests__/helpers/mainPanelMcpExpectations.ts
@@ -40,6 +40,12 @@ import {
EXA_MCP_LOCAL_API_KEY_ENV,
EXA_MCP_REMOTE_URL,
} from 'grph-shared/search/exaMcpSsot'
+import {
+ EXA_SEARCH_API_CODING_AGENT_REQUEST_JSON,
+ EXA_SEARCH_API_DOCS_URL,
+ EXA_SEARCH_API_ENDPOINT,
+ EXA_SEARCH_API_INVOCATION_TEXT,
+} from 'grph-shared/search/exaSearchApiSsot'
import {
CLOUDFLARE_AI_GATEWAY_ACCOUNT_HEADER,
CLOUDFLARE_AI_GATEWAY_MCP_DOCS_URL,
@@ -515,6 +521,11 @@ export function assertMcpHubSurfacesExaMcpConfig(container: Element): void {
'exaMcp.max_results',
'exaMcp.fetch_content_limit',
'exaMcp.require_fetch_review',
+ 'exaMcp.search_api.endpoint',
+ 'exaMcp.search_api.default_request',
+ 'exaMcp.search_api.response_contract',
+ 'exaMcp.search_api.invocation',
+ 'exaMcp.search_api.docs_url',
'exaMcp.remote_config.codex',
'exaMcp.remote_config.generic',
'web_search_exa',
@@ -535,6 +546,10 @@ export function assertMcpHubSurfacesExaMcpConfig(container: Element): void {
EXA_MCP_DOCS_MARKDOWN_URL,
EXA_MCP_GITHUB_URL,
EXA_MCP_DASHBOARD_URL,
+ EXA_SEARCH_API_ENDPOINT,
+ EXA_SEARCH_API_CODING_AGENT_REQUEST_JSON,
+ EXA_SEARCH_API_INVOCATION_TEXT,
+ EXA_SEARCH_API_DOCS_URL,
'restart_mcp_client_after_config_change',
'Open Exa MCP Docs',
'Open FloatingPanel Chat UI',
diff --git a/canvas/src/__tests__/mainPanelExaIntegrations.test.tsx b/canvas/src/__tests__/mainPanelExaIntegrations.test.tsx
new file mode 100644
index 000000000..56f388cd4
--- /dev/null
+++ b/canvas/src/__tests__/mainPanelExaIntegrations.test.tsx
@@ -0,0 +1,122 @@
+import React from 'react'
+import { createRoot } from 'react-dom/client'
+import IntegrationsHubView from '@/features/panels/views/IntegrationsHubView'
+import { ExaSearchSkillsCommandsProjection } from '@/features/integrations/ExaSearchSkillsCommandsProjection'
+import { initJsdomHarness } from '@/tests/lib/jsdomHarness'
+import { initWindowHarness } from '@/tests/lib/windowHarness'
+import { MemoryStorage } from '@/tests/lib/memoryStorage'
+import { installDeterministicRaf, mountReactRoot, unmountReactRoot } from '@/tests/lib/reactRootHarness'
+import { useGraphStore } from '@/hooks/useGraphStore'
+import {
+ EXA_SEARCH_API_CODING_AGENT_REQUEST_JSON,
+ EXA_SEARCH_API_DEPRECATED_FIELDS,
+ EXA_SEARCH_API_DOC_AREA,
+ EXA_SEARCH_API_DOCS_URL,
+ EXA_SEARCH_API_ENDPOINT,
+ EXA_SEARCH_API_INVOCATION_TEXT,
+ buildExaCodingAgentSearchRequest,
+} from 'grph-shared/search/exaSearchApiSsot'
+
+async function withRendered(element: React.ReactElement, assertions: (container: Element) => void): Promise {
+ const storage = new MemoryStorage()
+ const { restore: restoreWindow } = initWindowHarness({ storage })
+ const { dom, restore: restoreDom } = initJsdomHarness()
+ let root: ReturnType | null = null
+ try {
+ const anyWindow = dom.window as unknown as { requestAnimationFrame?: (callback: (time: number) => void) => number }
+ anyWindow.requestAnimationFrame = installDeterministicRaf(dom.window)
+ useGraphStore.getState().resetAll()
+ const container = dom.window.document.createElement('section')
+ dom.window.document.body.appendChild(container)
+ root = createRoot(container as unknown as HTMLElement)
+ await mountReactRoot(root, element, { window: dom.window, frames: 4 })
+ assertions(container)
+ } finally {
+ if (root) await unmountReactRoot(root, { window: dom.window })
+ restoreDom()
+ restoreWindow()
+ }
+}
+
+const renderedValues = (container: Element): string => Array.from(
+ container.querySelectorAll('input, textarea, select'),
+ element => element.value,
+).join('\n')
+
+export async function testIntegrationsHubSurfacesExaCodingAgentSearchContract() {
+ await withRendered(React.createElement(IntegrationsHubView), container => {
+ const searchableText = `${container.textContent || ''}\n${renderedValues(container)}`
+ ;[
+ EXA_SEARCH_API_DOC_AREA,
+ 'exaSearchApi.endpoint',
+ 'exaSearchApi.request.query',
+ 'exaSearchApi.request.type',
+ 'exaSearchApi.request.numResults',
+ 'exaSearchApi.request.contents.highlights',
+ 'exaSearchApi.request.structured_output',
+ 'exaSearchApi.response.contract',
+ 'exaSearchApi.errors.statuses',
+ 'exaSearchApi.deprecated.fields',
+ 'exaSearchApi.invocation',
+ EXA_SEARCH_API_ENDPOINT,
+ EXA_SEARCH_API_CODING_AGENT_REQUEST_JSON,
+ EXA_SEARCH_API_INVOCATION_TEXT,
+ EXA_SEARCH_API_DOCS_URL,
+ 'Open Exa Search API Coding-Agent Guide',
+ 'Open FloatingPanel Skills & Commands',
+ ].forEach(token => {
+ if (!searchableText.includes(token)) {
+ throw new Error(`expected MainPanel Integrations to include Exa coding-agent token ${JSON.stringify(token)}`)
+ }
+ })
+ if (!container.querySelector('[data-kg-anchor^="integrations-row-exa-search-"]')) {
+ throw new Error('expected Exa Search API integration rows to use stable Exa anchors')
+ }
+ ;['YOUR_EXA_API_KEY', 'your_api_key', 'exaApiKey=', 'sk_test_', 'sk_live_'].forEach(secret => {
+ if (searchableText.includes(secret)) throw new Error(`expected Exa integration to omit secret material ${secret}`)
+ })
+ })
+}
+
+export function testExaCodingAgentSearchRequestUsesCurrentBoundedContract() {
+ const request = buildExaCodingAgentSearchRequest({
+ query: ' inspect current repository issue ',
+ type: 'unsupported',
+ numResults: 999,
+ })
+ if (
+ request.query !== 'inspect current repository issue'
+ || request.type !== 'auto'
+ || request.numResults !== 100
+ || request.contents.highlights !== true
+ ) {
+ throw new Error(`expected sanitized Exa coding-agent request, got ${JSON.stringify(request)}`)
+ }
+ ;['useAutoprompt', 'numSentences', 'tokensNum', 'livecrawl (string)'].forEach(field => {
+ if (!EXA_SEARCH_API_DEPRECATED_FIELDS.some(candidate => candidate === field)) {
+ throw new Error(`expected shared Exa contract to reject deprecated field ${field}`)
+ }
+ })
+}
+
+export async function testFloatingSkillsCommandsSurfacesCanonicalExaInvocation() {
+ await withRendered(React.createElement(ExaSearchSkillsCommandsProjection), container => {
+ const projection = container.querySelector('[data-kg-exa-search-skills-projection="configuration-only"]')
+ const invocation = projection?.querySelector('[data-kg-exa-search-invocation="canonical"]')
+ const text = projection?.textContent || ''
+ if (
+ !projection
+ || invocation?.getAttribute('data-kg-exa-search-invocation-chip-renderer') !== 'shared-markdown-sigil'
+ || !text.includes('/tool.catalog')
+ || !text.includes('#tool-routing')
+ || !text.includes('@tool-provider')
+ || !text.includes('no browser-side API key or direct search call')
+ || projection.querySelector(`a[href="${EXA_SEARCH_API_DOCS_URL}"]`) === null
+ ) {
+ throw new Error(`expected FloatingPanel Skills & Commands Exa canonical projection, got ${JSON.stringify(text)}`)
+ }
+ if (text.includes('/exa') || text.includes('#exa') || text.includes('@exa')) {
+ throw new Error(`expected Exa projection to avoid provider-specific invocation aliases, got ${JSON.stringify(text)}`)
+ }
+ })
+}
diff --git a/canvas/src/__tests__/mainPanelSkillsCommands.test.tsx b/canvas/src/__tests__/mainPanelSkillsCommands.test.tsx
index 4a846b1d7..e7dfbf145 100644
--- a/canvas/src/__tests__/mainPanelSkillsCommands.test.tsx
+++ b/canvas/src/__tests__/mainPanelSkillsCommands.test.tsx
@@ -245,6 +245,8 @@ export async function testFloatingPanelSkillsCommandsViewReusesMediaPanelLayout(
const subjectGroupToggle = container.querySelector('[data-kg-skills-commands-grammar-toggle="subject"]') as HTMLButtonElement | null
const objectGroupToggle = container.querySelector('[data-kg-skills-commands-grammar-toggle="object"]') as HTMLButtonElement | null
const motionCaptureProjection = container.querySelector('[data-kg-motion-capture-projection="skills"]')
+ const exaSearchProjection = container.querySelector('[data-kg-exa-search-skills-projection="configuration-only"]')
+ const exaSearchInvocation = exaSearchProjection?.querySelector('[data-kg-exa-search-invocation="canonical"]')
const motionCaptureInvocation = motionCaptureProjection?.querySelector('[data-kg-motion-capture-invocation="canonical"]')
const motionCaptureWebMcp = motionCaptureProjection?.querySelector('[data-kg-motion-capture-web-mcp="1"]')
if (
@@ -271,13 +273,17 @@ export async function testFloatingPanelSkillsCommandsViewReusesMediaPanelLayout(
!(sharedSearchToggle instanceof dom.window.HTMLButtonElement) ||
skillsSearchToggle !== sharedSearchToggle ||
!motionCaptureProjection ||
+ !exaSearchProjection ||
+ !exaSearchInvocation?.textContent?.includes('/tool.catalog') ||
+ !exaSearchInvocation.textContent.includes('#tool-routing') ||
+ !exaSearchInvocation.textContent.includes('@tool-provider') ||
motionCaptureProjection.getAttribute('data-kg-motion-capture-runtime-ready') === null ||
!motionCaptureInvocation?.textContent?.includes('/motion.control') ||
!motionCaptureInvocation.textContent.includes('@canvas') ||
!motionCaptureInvocation.textContent.includes('#pose') ||
!motionCaptureWebMcp?.textContent?.includes('knowgrph.control_local_motion_control')
) {
- throw new Error('Expected Skills & Commands to reuse the shared Media layout and canonical Motion Capture invocation projection')
+ throw new Error('Expected Skills & Commands to reuse the shared Media layout and canonical Exa/Motion Capture invocation projections')
}
const groupDisclosureActions = container.querySelector('[data-kg-skills-commands-disclosure-actions="header"]') as HTMLElement | null
const groupDisclosureButton = groupDisclosureActions?.querySelector('button') as HTMLButtonElement | null
diff --git a/canvas/src/features/canvas/utils.ts b/canvas/src/features/canvas/utils.ts
index 652bf0594..9c9df7f3e 100644
--- a/canvas/src/features/canvas/utils.ts
+++ b/canvas/src/features/canvas/utils.ts
@@ -23,6 +23,7 @@ export type PropsPanelOpenEventDetail = {
export type FloatingPanelOpenEventDetail = {
tab?:
| 'inspector'
+ | 'skillsCommands'
| 'node'
| 'view'
| 'camera'
diff --git a/canvas/src/features/integrations/ExaSearchSkillsCommandsProjection.tsx b/canvas/src/features/integrations/ExaSearchSkillsCommandsProjection.tsx
new file mode 100644
index 000000000..17638b730
--- /dev/null
+++ b/canvas/src/features/integrations/ExaSearchSkillsCommandsProjection.tsx
@@ -0,0 +1,54 @@
+import React from 'react'
+import { Search } from 'lucide-react'
+import { renderAgenticOsInvocationKeywordChip } from '@/features/agentic-os/agenticOsInvocationChips'
+import { renderMarkdownSigilInlineText } from '@/lib/ui/MarkdownSigilText'
+import { UI_INLINE_CHIP_GROUP_CLASSNAME } from '@/lib/ui/textLayout'
+import { UI_THEME_TOKENS } from '@/lib/ui/theme-tokens'
+import { cn } from '@/lib/utils'
+import {
+ EXA_SEARCH_API_DEFAULT_NUM_RESULTS,
+ EXA_SEARCH_API_DEFAULT_SEARCH_TYPE,
+ EXA_SEARCH_API_DOCS_URL,
+ EXA_SEARCH_API_INVOCATION_TEXT,
+} from 'grph-shared/search/exaSearchApiSsot'
+
+export function ExaSearchSkillsCommandsProjection() {
+ return (
+
+
+
+ {EXA_SEARCH_API_DEFAULT_SEARCH_TYPE} · {EXA_SEARCH_API_DEFAULT_NUM_RESULTS} results · highlights · host-owned auth
+
+
+ {renderMarkdownSigilInlineText(EXA_SEARCH_API_INVOCATION_TEXT, {
+ renderKeywordChip: ({ value, className }) => renderAgenticOsInvocationKeywordChip({
+ value,
+ className,
+ sourceLink: false,
+ }),
+ })}
+
+ Reference contract only · no browser-side API key or direct search call
+
+ )
+}
diff --git a/canvas/src/features/panels/views/exaMcpApiDocs.ts b/canvas/src/features/panels/views/exaMcpApiDocs.ts
index aee235aa1..d10b113d6 100644
--- a/canvas/src/features/panels/views/exaMcpApiDocs.ts
+++ b/canvas/src/features/panels/views/exaMcpApiDocs.ts
@@ -25,6 +25,14 @@ import {
EXA_MCP_TOOL_PROFILES,
normalizeExaMcpToolNames,
} from 'grph-shared/search/exaMcpSsot'
+import {
+ EXA_SEARCH_API_CODING_AGENT_REQUEST_JSON,
+ EXA_SEARCH_API_DEFAULT_SEARCH_TYPE,
+ EXA_SEARCH_API_DOCS_URL,
+ EXA_SEARCH_API_ENDPOINT,
+ EXA_SEARCH_API_INVOCATION_TEXT,
+ EXA_SEARCH_API_RESPONSE_FIELDS,
+} from 'grph-shared/search/exaSearchApiSsot'
export { EXA_MCP_DOC_AREA, EXA_MCP_DOCS_URL }
@@ -169,6 +177,42 @@ const EXA_MCP_DOC_ROWS: ReadonlyArray = [
tooltipDefaultValue: EXA_MCP_DEFAULT_MAX_RESULTS,
searchHints: ['numResults', 'result limit', 'evidence pack'],
},
+ {
+ key: 'search_api.endpoint',
+ typeLabel: 'endpoint',
+ value: `POST ${EXA_SEARCH_API_ENDPOINT}`,
+ responsibility: 'Source-owned Search API endpoint behind Exa MCP coding-agent search guidance.',
+ notes: 'This row is a contract projection only; the browser does not call Exa or store bearer credentials.',
+ searchHints: ['coding agent search api', EXA_SEARCH_API_ENDPOINT],
+ },
+ {
+ key: 'search_api.default_request',
+ typeLabel: 'object',
+ value: EXA_SEARCH_API_CODING_AGENT_REQUEST_JSON,
+ responsibility: 'Shared coding-agent request preview with auto search, bounded results, and highlight content.',
+ searchHints: ['contents highlights', EXA_SEARCH_API_DEFAULT_SEARCH_TYPE, 'numResults'],
+ },
+ {
+ key: 'search_api.response_contract',
+ typeLabel: 'string[]',
+ value: JSON.stringify(EXA_SEARCH_API_RESPONSE_FIELDS),
+ responsibility: 'Preserve request identity, evidence, structured output, and upstream cost reporting across MCP handoff.',
+ searchHints: ['requestId', 'searchType', 'costDollars.total', 'grounded output'],
+ },
+ {
+ key: 'search_api.invocation',
+ typeLabel: '/ # @',
+ value: EXA_SEARCH_API_INVOCATION_TEXT,
+ responsibility: 'Project the canonical Agentic Canvas OS tool-routing invocation without provider-specific aliases.',
+ searchHints: ['/tool.catalog', '#tool-routing', '@tool-provider'],
+ },
+ {
+ key: 'search_api.docs_url',
+ typeLabel: 'url',
+ value: EXA_SEARCH_API_DOCS_URL,
+ responsibility: 'Canonical Exa Search API guide for coding agents used by the shared request contract.',
+ searchHints: ['Exa Search API coding agent guide', EXA_SEARCH_API_DOCS_URL],
+ },
{
key: 'fetch_content_limit',
typeLabel: 'integer',
diff --git a/canvas/src/features/panels/views/exaSearchApiDocs.ts b/canvas/src/features/panels/views/exaSearchApiDocs.ts
new file mode 100644
index 000000000..219404b84
--- /dev/null
+++ b/canvas/src/features/panels/views/exaSearchApiDocs.ts
@@ -0,0 +1,185 @@
+import type { FlowDetails, SettingMeta } from '@/features/settings/types'
+import type { VirtualSettingsEntry } from './byteplusSharedTextApiDocs'
+import { buildSettingsRowAnchorId } from './settingsRowAnchor'
+import {
+ EXA_SEARCH_API_AUTH_HEADER,
+ EXA_SEARCH_API_AUTH_SCHEME,
+ EXA_SEARCH_API_CATEGORIES,
+ EXA_SEARCH_API_CODING_AGENT_REQUEST_JSON,
+ EXA_SEARCH_API_DEFAULT_NUM_RESULTS,
+ EXA_SEARCH_API_DEFAULT_SEARCH_TYPE,
+ EXA_SEARCH_API_DEPRECATED_FIELDS,
+ EXA_SEARCH_API_DOC_AREA,
+ EXA_SEARCH_API_DOCS_MARKDOWN_URL,
+ EXA_SEARCH_API_DOCS_URL,
+ EXA_SEARCH_API_ENDPOINT,
+ EXA_SEARCH_API_ERROR_STATUSES,
+ EXA_SEARCH_API_INVOCATION_TEXT,
+ EXA_SEARCH_API_KEY_ENV,
+ EXA_SEARCH_API_MAX_NUM_RESULTS,
+ EXA_SEARCH_API_RESPONSE_FIELDS,
+ EXA_SEARCH_API_SEARCH_TYPES,
+} from 'grph-shared/search/exaSearchApiSsot'
+
+export { EXA_SEARCH_API_DOC_AREA, EXA_SEARCH_API_DOCS_URL }
+
+type ExaSearchApiDocRow = Readonly<{
+ key: string
+ typeLabel: string
+ value: string | number | boolean
+ responsibility: string
+ notes?: string
+ searchHints?: readonly string[]
+}>
+
+const rows: ReadonlyArray = [
+ {
+ key: 'endpoint',
+ typeLabel: 'endpoint',
+ value: `POST ${EXA_SEARCH_API_ENDPOINT}`,
+ responsibility: 'Canonical Exa Search endpoint for source-grounded coding-agent research.',
+ searchHints: ['search endpoint', 'POST', EXA_SEARCH_API_ENDPOINT],
+ },
+ {
+ key: 'auth.boundary',
+ typeLabel: 'security note',
+ value: `${EXA_SEARCH_API_AUTH_HEADER}: ${EXA_SEARCH_API_AUTH_SCHEME} (${EXA_SEARCH_API_KEY_ENV} host reference)`,
+ responsibility: 'Describe server-owned bearer authentication without accepting or persisting a raw Exa API key in browser state.',
+ notes: 'The key value belongs in a trusted host or proxy. MainPanel exposes names and boundaries only.',
+ searchHints: ['authentication', EXA_SEARCH_API_KEY_ENV, EXA_SEARCH_API_AUTH_HEADER],
+ },
+ {
+ key: 'request.query',
+ typeLabel: 'string required',
+ value: 'query',
+ responsibility: 'Required natural-language search query for the coding task.',
+ },
+ {
+ key: 'request.type',
+ typeLabel: 'enum',
+ value: EXA_SEARCH_API_DEFAULT_SEARCH_TYPE,
+ responsibility: 'Search strategy selector; auto is the upstream default and chooses an appropriate strategy.',
+ searchHints: EXA_SEARCH_API_SEARCH_TYPES,
+ },
+ {
+ key: 'request.numResults',
+ typeLabel: 'integer',
+ value: EXA_SEARCH_API_DEFAULT_NUM_RESULTS,
+ responsibility: `Bound the evidence pack to 1-${EXA_SEARCH_API_MAX_NUM_RESULTS} results; default to ${EXA_SEARCH_API_DEFAULT_NUM_RESULTS}.`,
+ },
+ {
+ key: 'request.contents.highlights',
+ typeLabel: 'boolean',
+ value: true,
+ responsibility: 'Return focused passages for coding-agent context instead of transferring full page text by default.',
+ notes: 'The Exa coding-agent guide recommends highlights for concise, relevant evidence.',
+ },
+ {
+ key: 'request.filters',
+ typeLabel: 'object',
+ value: JSON.stringify({ includeDomains: [], excludeDomains: [], category: null }),
+ responsibility: 'Optional domain, category, and date filters narrow source selection without changing downstream ownership.',
+ searchHints: ['includeDomains', 'excludeDomains', 'date filters', ...EXA_SEARCH_API_CATEGORIES],
+ },
+ {
+ key: 'request.freshness',
+ typeLabel: 'integer optional',
+ value: 'contents.maxAgeHours',
+ responsibility: 'Control cache freshness explicitly; zero forces live crawling and can add latency.',
+ notes: 'Leave maxAgeHours absent unless the task requires a specific freshness boundary.',
+ searchHints: ['maxAgeHours', 'live crawl', 'cache'],
+ },
+ {
+ key: 'request.structured_output',
+ typeLabel: 'object optional',
+ value: 'outputSchema',
+ responsibility: 'Request grounded structured output with the upstream schema limits instead of parsing prose downstream.',
+ notes: 'The guide limits schemas to two nesting levels and ten properties.',
+ },
+ {
+ key: 'request.stream',
+ typeLabel: 'boolean',
+ value: false,
+ responsibility: 'Opt into SSE only when the caller owns OpenAI-compatible chat-chunk consumption.',
+ },
+ {
+ key: 'request.coding_agent_default',
+ typeLabel: 'json',
+ value: EXA_SEARCH_API_CODING_AGENT_REQUEST_JSON,
+ responsibility: 'Non-secret request preview shared by MainPanel Integrations, MCP guidance, and Skills & Commands.',
+ },
+ {
+ key: 'response.contract',
+ typeLabel: 'string[]',
+ value: JSON.stringify(EXA_SEARCH_API_RESPONSE_FIELDS),
+ responsibility: 'Track request identity, selected search type, evidence, structured output, and reported total cost.',
+ },
+ {
+ key: 'errors.statuses',
+ typeLabel: 'integer[]',
+ value: JSON.stringify(EXA_SEARCH_API_ERROR_STATUSES),
+ responsibility: 'Handle validation, authentication, semantic validation, rate limiting, and service failures explicitly.',
+ },
+ {
+ key: 'deprecated.fields',
+ typeLabel: 'string[]',
+ value: JSON.stringify(EXA_SEARCH_API_DEPRECATED_FIELDS),
+ responsibility: 'Reject legacy top-level content and retired tuning fields at the shared request boundary.',
+ },
+ {
+ key: 'invocation',
+ typeLabel: '/ # @',
+ value: EXA_SEARCH_API_INVOCATION_TEXT,
+ responsibility: 'Route Exa through canonical Agentic Canvas OS tool catalog semantics without inventing provider-specific grammar.',
+ },
+ {
+ key: 'docs.url',
+ typeLabel: 'url',
+ value: EXA_SEARCH_API_DOCS_URL,
+ responsibility: 'Canonical Exa Search API guide for coding agents.',
+ },
+ {
+ key: 'docs.markdown_url',
+ typeLabel: 'url',
+ value: EXA_SEARCH_API_DOCS_MARKDOWN_URL,
+ responsibility: 'Markdown form of the canonical guide for source verification.',
+ },
+]
+
+const toBaseType = (typeLabel: string): SettingMeta['type'] => {
+ const normalized = typeLabel.toLowerCase()
+ if (normalized.includes('boolean')) return 'boolean'
+ if (normalized.includes('object') || normalized.includes('[]') || normalized.includes('json')) return 'json'
+ if (normalized.includes('integer') || normalized.includes('number')) return 'number'
+ return 'string'
+}
+
+export function getExaSearchApiRowAnchorId(rowKey: string): string {
+ return buildSettingsRowAnchorId('integrations-row-exa-search', rowKey)
+}
+
+export const EXA_SEARCH_API_DOC_ENTRIES: ReadonlyArray = rows.map(row => {
+ const details: FlowDetails = {
+ area: EXA_SEARCH_API_DOC_AREA,
+ responsibility: row.responsibility,
+ notes: row.notes || '',
+ modules: ['Exa Search API'],
+ classes: ['CodingAgentSearchContract'],
+ functions: ['MainPanel Integrations', 'MainPanel MCP', 'FloatingPanel Skills & Commands'],
+ imports: [],
+ }
+ return {
+ meta: {
+ key: `exaSearchApi.${row.key}`,
+ type: toBaseType(row.typeLabel),
+ source: 'backendEnv',
+ read: () => row.value,
+ },
+ value: row.value,
+ typeLabel: row.typeLabel,
+ tooltipRole: 'Exa Search API',
+ tooltipDefaultValue: row.value,
+ searchHints: ['exa search api', 'coding agent search', row.key, ...(row.searchHints || [])],
+ details,
+ }
+})
diff --git a/canvas/src/features/panels/views/settingsView.constants.ts b/canvas/src/features/panels/views/settingsView.constants.ts
index 05ac23518..bf37c2b7f 100644
--- a/canvas/src/features/panels/views/settingsView.constants.ts
+++ b/canvas/src/features/panels/views/settingsView.constants.ts
@@ -28,6 +28,7 @@ import { OPENAI_MCP_DOC_AREA, OPENAI_MCP_DOCS_URL } from './openaiMcpApiDocs'
import { OPERATOR_DEPLOY_MCP_DOC_AREA, OPERATOR_DEPLOY_MCP_DOCS_URL } from './operatorDeployMcpApiDocs'
import { OPERATOR_DEPLOY_SETTING_KEYS } from '@/features/settings/operatorDeploySsot'
import { EXA_MCP_DOC_AREA, EXA_MCP_DOCS_URL } from './exaMcpApiDocs'
+import { EXA_SEARCH_API_DOC_AREA, EXA_SEARCH_API_DOCS_URL } from './exaSearchApiDocs'
import { FEISHU_BASE_MCP_DOC_AREA, FEISHU_BASE_MCP_DOCS_URL } from './feishuBaseMcpApiDocs'
import { LARK_APP_MCP_DOC_AREA, LARK_APP_MCP_DOCS_URL } from './larkAppMcpApiDocs'
import { STRIPE_MCP_DOC_AREA } from './stripeMcpApiDocs'
@@ -97,6 +98,12 @@ export const INTEGRATIONS_SECTION_META: Readonly> =
panelLabel: 'Open FloatingPanel Props Panel Widget Card',
openPanel: () => emitPropsPanelOpen(),
},
+ [EXA_SEARCH_API_DOC_AREA]: {
+ docsUrl: EXA_SEARCH_API_DOCS_URL,
+ docsLabel: 'Open Exa Search API Coding-Agent Guide',
+ panelLabel: 'Open FloatingPanel Skills & Commands',
+ openPanel: () => emitFloatingPanelOpen({ tab: 'skillsCommands', open: true }),
+ },
[OPENAI_CHAT_API_DOC_AREA]: {
docsUrl: 'https://developers.openai.com/api/reference/resources/responses',
docsLabel: 'Open OpenAI Chat API Docs',
diff --git a/canvas/src/features/panels/views/useSettingsView.helpers.ts b/canvas/src/features/panels/views/useSettingsView.helpers.ts
index e297050ce..f09d3c729 100644
--- a/canvas/src/features/panels/views/useSettingsView.helpers.ts
+++ b/canvas/src/features/panels/views/useSettingsView.helpers.ts
@@ -37,6 +37,7 @@ import { CLOUDFLARE_AI_GATEWAY_MCP_DOC_AREA } from './cloudflareAiGatewayMcpApiD
import { BYTEPLUS_MODELARK_MCP_DOC_AREA } from './byteplusModelArkMcpApiDocs'
import { OPENAI_MCP_DOC_AREA } from './openaiMcpApiDocs'
import { EXA_MCP_DOC_AREA } from './exaMcpApiDocs'
+import { EXA_SEARCH_API_DOC_AREA } from './exaSearchApiDocs'
import { FEISHU_BASE_MCP_DOC_AREA } from './feishuBaseMcpApiDocs'
import { LARK_APP_MCP_DOC_AREA } from './larkAppMcpApiDocs'
import { STRIPE_MCP_DOC_AREA } from './stripeMcpApiDocs'
@@ -117,6 +118,7 @@ const SETTINGS_AREA_ORDER: readonly string[] = [
MARKDOWN_DATA_VIEW_COPY.titleDefault,
'Import / Export',
'Integrations',
+ EXA_SEARCH_API_DOC_AREA,
BYTEPLUS_SHARED_TEXT_API_DOC_AREA,
BYTEPLUS_IMAGE_GENERATION_API_DOC_AREA,
BYTEPLUS_VIDEO_GENERATION_API_DOC_AREA,
@@ -163,6 +165,7 @@ export function isIntegrationsOwnedSetting(key: string, areaRaw: string): boolea
if (
area === 'Chat'
|| area === 'Integrations'
+ || area === EXA_SEARCH_API_DOC_AREA
|| area === BYTEPLUS_SHARED_TEXT_API_DOC_AREA
|| area === BYTEPLUS_IMAGE_GENERATION_API_DOC_AREA
|| area === BYTEPLUS_VIDEO_GENERATION_API_DOC_AREA
diff --git a/canvas/src/features/panels/views/useSettingsView.ts b/canvas/src/features/panels/views/useSettingsView.ts
index 0f2a6852d..30b1864f7 100644
--- a/canvas/src/features/panels/views/useSettingsView.ts
+++ b/canvas/src/features/panels/views/useSettingsView.ts
@@ -131,6 +131,11 @@ import {
SENSENOVA_API_DOC_ENTRIES,
getSensenovaApiRowAnchorId,
} from './sensenovaApiDocs'
+import {
+ EXA_SEARCH_API_DOC_AREA,
+ EXA_SEARCH_API_DOC_ENTRIES,
+ getExaSearchApiRowAnchorId,
+} from './exaSearchApiDocs'
import {
MAPS_API_DOC_ENTRIES,
getMapsApiRowAnchorId,
@@ -189,6 +194,7 @@ const getSettingsSearchHints = (key: string): string[] => {
}
const INTEGRATION_API_DOC_ENTRIES = [
+ ...EXA_SEARCH_API_DOC_ENTRIES,
...BYTEPLUS_SHARED_TEXT_API_REQUEST_DOC_ENTRIES,
...BYTEPLUS_IMAGE_GENERATION_API_REQUEST_DOC_ENTRIES,
...BYTEPLUS_VIDEO_GENERATION_API_REQUEST_DOC_ENTRIES,
@@ -1031,7 +1037,9 @@ export function useSettingsView({
const area = normalizeSettingsAreaLabel(entry.details.area)
const normalizedDisplayValues = normalizedProviderValuesByArea.get(area) || values
const anchorId =
- area === BYTEPLUS_SHARED_TEXT_API_DOC_AREA
+ area === EXA_SEARCH_API_DOC_AREA
+ ? getExaSearchApiRowAnchorId(entry.meta.key)
+ : area === BYTEPLUS_SHARED_TEXT_API_DOC_AREA
? getBytePlusSharedTextApiRowAnchorId(entry.meta.key)
: area === BYTEPLUS_IMAGE_GENERATION_API_DOC_AREA
? getBytePlusImageGenerationApiRowAnchorId(entry.meta.key)
@@ -1199,6 +1207,11 @@ export function useSettingsView({
return area === 'Chat' || area === 'Integrations'
},
},
+ {
+ title: EXA_SEARCH_API_DOC_AREA,
+ searchIndex: normalizeText('Exa Search API coding agents web research highlights structured output cost tool routing FloatingPanel Skills Commands'),
+ match: entry => normalizeSettingsAreaLabel(entry.details.area) === EXA_SEARCH_API_DOC_AREA,
+ },
{
title: BYTEPLUS_SHARED_TEXT_API_DOC_AREA,
searchIndex: normalizeText('BytePlus Shared + Text API BytePlus Chat API ModelArk FloatingPanel Props Panel Widget Card text generation shared auth api key endpoint'),
diff --git a/canvas/src/features/toolbar/FloatingPanelSkillsCommandsView.tsx b/canvas/src/features/toolbar/FloatingPanelSkillsCommandsView.tsx
index f037f8005..8682c43b2 100644
--- a/canvas/src/features/toolbar/FloatingPanelSkillsCommandsView.tsx
+++ b/canvas/src/features/toolbar/FloatingPanelSkillsCommandsView.tsx
@@ -19,6 +19,7 @@ import {
import { UI_THEME_TOKENS } from '@/lib/ui/theme-tokens'
import { cn } from '@/lib/utils'
import { MotionCapturePlatformProjection } from '@/features/three/MotionCapturePlatformProjection'
+import { ExaSearchSkillsCommandsProjection } from '@/features/integrations/ExaSearchSkillsCommandsProjection'
import { useSkillsCommandsMcpTarget } from '@/features/agentic-os/skillsCommandsMcpTarget'
const SKILLS_COMMANDS_PREFIX_FILTERS: Array<{ filter: SkillsCommandsPrefixFilter; label: string; Icon: typeof Slash }> = [
@@ -200,7 +201,12 @@ export function FloatingPanelSkillsCommandsView() {
Source-backed invocation selected: {targetTokens.join(' ')}
) : null}
- {targetingMcpInvocation ? null : }
+ {targetingMcpInvocation ? null : (
+ <>
+
+
+ >
+ )}
B["Dispatcher: virtual MCP rows"]
- B --> C["SSOT normalizer"]
- C --> D["Executor: deterministic config builder"]
- D --> E["Observer: focused source test"]
+ A["Integrations / MCP / Skills request"] --> B["Dispatcher: surface projection"]
+ B --> C["Search API + MCP SSOT"]
+ C --> D["Executor: deterministic reference/config builder"]
+ D --> E["Observer: focused contract tests"]
E --> F["Operator-owned MCP host"]
F -. "provider result; no repo harness evidenced" .-> G["Future evidence validation gate"]
```
@@ -189,7 +203,10 @@ may infer remote readiness from the source test.
flowchart TB
subgraph Authoring["Authoring lane"]
S["exaMcpSsot.ts"]
+ A["exaSearchApiSsot.ts"]
U["exaMcpApiDocs.ts"]
+ I["exaSearchApiDocs.ts"]
+ K["ExaSearchSkillsCommandsProjection.tsx"]
T["mainPanelMcpExa.test.tsx"]
end
subgraph Host["Operator host boundary"]
@@ -198,6 +215,9 @@ flowchart TB
subgraph Upstream["External provider boundary"]
P["Hosted Exa service"]
end
+ A --> I
+ A --> U
+ A --> K
S --> U --> T
U -. "copy only" .-> H
H -. "host-owned request" .-> P
@@ -220,6 +240,8 @@ flowchart TB
| `TAD-EXA-SSOT` | Shared SSOT | `TAD-EXA-SSOT-NORMALIZE`; `TAD-EXA-SSOT-URL` (`normalizeExaMcpToolNames`; `buildExaMcpRemoteUrl`) | `VCC-EXA-01`, `VCC-EXA-03` | Three allowed names; default has two. |
| `TAD-EXA-CONFIG` | Config builders | `TAD-EXA-CONFIG-BUILD` (`resolveExaMcpEnabledTools`; config builders) | `VCC-EXA-01`, `VCC-EXA-02` | No credential value or header material. |
| `TAD-EXA-MAINPANEL` | MainPanel aggregation | `TAD-EXA-MAINPANEL-ROWS` (`EXA_MCP_DOC_ENTRIES`) | `VCC-EXA-04` | No parallel tab or browser MCP client. |
+| `TAD-EXA-SEARCH-SSOT` | Coding-agent Search API SSOT | `TAD-EXA-SEARCH-BUILD` (`buildExaCodingAgentSearchRequest`) | `VCC-EXA-07`, `VCC-EXA-08` | Required query; allowed mode; 1-100 results; highlights by default. |
+| `TAD-EXA-SKILLS` | Skills & Commands projection | `TAD-EXA-SKILLS-INVOKE` (`EXA_SEARCH_API_INVOCATION_TEXT`) | `VCC-EXA-09` | Canonical dictionary tokens only; no private aliases. |
| `TAD-EXA-EVIDENCE` | Evidence boundary (not implemented) | `TAD-EXA-EVIDENCE-VALIDATE` | `VCC-EXA-05` | No direct canvas mutation. |
| `TAD-EXA-HARNESS` | Execution harness (not implemented) | `TAD-EXA-HARNESS-EXECUTE` | `VCC-EXA-06` | Activation requires token, quota, and circuit-breaker bounds. |
@@ -237,6 +259,9 @@ here.
| `PRD-EXA-04` | `TAD-EXA-MAINPANEL` | `TAD-EXA-MAINPANEL-ROWS` | `VCC-EXA-04` |
| `PRD-EXA-05` | `TAD-EXA-EVIDENCE` | `TAD-EXA-EVIDENCE-VALIDATE` | `VCC-EXA-05` |
| `PRD-EXA-06` | `TAD-EXA-HARNESS` | `TAD-EXA-HARNESS-EXECUTE` | `VCC-EXA-06` |
+| `PRD-EXA-07` | `TAD-EXA-SEARCH-SSOT` | `TAD-EXA-SEARCH-BUILD` | `VCC-EXA-07` |
+| `PRD-EXA-08` | `TAD-EXA-SEARCH-SSOT` + `TAD-EXA-MAINPANEL` | `TAD-EXA-SEARCH-BUILD` + `TAD-EXA-MAINPANEL-ROWS` | `VCC-EXA-08` |
+| `PRD-EXA-09` | `TAD-EXA-SKILLS` | `TAD-EXA-SKILLS-INVOKE` | `VCC-EXA-09` |
### Security and error contract
@@ -278,12 +303,16 @@ publication.
| VCC | Exact check | Expected end state | Constraint | Evidence Reference |
|---|---|---|---|---|
-| `VCC-EXA-01` | From `canvas/`: `npm run test:ci:unit -- ui.mainPanel.mcpHub.exaDefaultGeneratedConfigNonSecret` | One registered case runs and the default config uses the source-owned profile. | Require `SUMMARY total=1 ... failed=0`; no network. | None recorded |
-| `VCC-EXA-02` | Same exact registered case as `VCC-EXA-01` | Generated text omits secret material. | No real credential fixture. | None recorded |
-| `VCC-EXA-03` | From `canvas/`: `npm run test:ci:unit -- ui.mainPanel.mcpHub.exaFiltersUnsupportedTools` | One registered case runs; unknown/duplicate tools are filtered. | Require `SUMMARY total=1 ... failed=0`. | None recorded |
+| `VCC-EXA-01` | From `canvas/`: `npm run test:ci:unit -- ui.mainPanel.mcpHub.exa` | Three registered config cases run and the default config uses the source-owned profile. | Require `SUMMARY total=3 ... failed=0`; no network. | Local authoring run, 2026-08-13: `total=3 ok=3 failed=0` |
+| `VCC-EXA-02` | Same exact registered filter as `VCC-EXA-01` | Generated text omits secret material. | No real credential fixture. | Local authoring run, 2026-08-13: `total=3 ok=3 failed=0` |
+| `VCC-EXA-03` | Same exact registered filter as `VCC-EXA-01` | Unknown/duplicate tools are filtered. | Require `SUMMARY total=3 ... failed=0`. | Local authoring run, 2026-08-13: `total=3 ok=3 failed=0` |
| `VCC-EXA-04` | Source-owner review of `exaMcpApiDocs.ts` and its call sites | UI remains configuration/documentation only. | A source review is not delivery evidence. | None recorded |
| `VCC-EXA-05` | No invocable Exa evidence-harness case exists. | Provider content cannot mutate app state without validation. | Unsatisfied; no readiness credit. | None recorded |
| `VCC-EXA-06` | No invocable Exa execution-harness case exists. | Execution remains disabled until token, quota, and circuit-breaker bounds are specified and checked. | Unsatisfied; no readiness credit. | None recorded |
+| `VCC-EXA-07` | From `canvas/`: run `npm run test:ci:unit -- ui.mainPanel.integrationsHub.exaCodingAgentSearchContract` and `npm run test:ci:unit -- integrations.exa.codingAgentSearchRequestBounded` | Both registered cases pass; rows and bounded request contract match the shared SSOT. | No network and no real credential fixture. | Local authoring runs, 2026-08-13: each `total=1 ok=1 failed=0` |
+| `VCC-EXA-08` | From `canvas/`: `npm run test:ci:unit -- ui.mainPanel.mcpHub.surfacesExaMcpConfig` | Existing MCP rendering case also checks the coding-agent endpoint, request, response/cost fields, invocation, and guide URL. | Source projection only. | Local authoring run, 2026-08-13: `total=1 ok=1 failed=0` |
+| `VCC-EXA-09` | From `canvas/`: separately run `npm run test:ci:unit -- ui.floatingPanel.skillsCommands.exaCanonicalInvocation` and `npm run test:ci:unit -- ui.floatingPanel.skillsCommands.reusesMediaLayout` | Direct and composed FloatingPanel checks pass with canonical `/ # @` tokens and no Exa alias. | Remote catalog remains the dictionary source of truth. | Local authoring runs, 2026-08-13: each `total=1 ok=1 failed=0` |
-With no recorded result, lane, commit, and evaluator for these VCCs, the local
-rung remains `spec-complete` and the delivered rung remains `undocumented`.
+Focused source and TypeScript results raise this authoring lane to
+`dev-proven`. No public delivery, live provider call, account quota, or
+production evidence is recorded, so the delivered rung remains `undocumented`.
diff --git a/docs/runtime-readiness-contract.md b/docs/runtime-readiness-contract.md
index a6c6b6a1e..4161fcc9d 100644
--- a/docs/runtime-readiness-contract.md
+++ b/docs/runtime-readiness-contract.md
@@ -13,7 +13,7 @@ stage_contract:
order: ["research", "storyboard", "render", "edit", "publish", "checkout"]
docs_dependency:
repository: "https://github.com/huijoohwee/agentic-canvas-os.git"
- ref: "270dfa50cc6825115d0856ae931047cd5e531b18"
+ ref: "af931c872c03353ad500ec87a54501cad887e5cd"
root_env: "KNOWGRPH_AGENTIC_CANVAS_OS_DOCS_ROOT"
default_relative_root: "../agentic-canvas-os/docs"
required_files: ["FACTS.md", "DICTIONARY-COMMAND.md", "DICTIONARY-SEMANTIC.md", "DICTIONARY-BINDING.md", "START-WORKFLOW.md", "RELEASE-WORKFLOW.md", "CANONICAL-LIFECYCLE.md", "RUNTIME-PROOF.md", "REPOSITORY-PACKING.md", "LIVE-AGENT-PROVIDER-PROOF.md", "PROGRESSIVE-AGENTS.md", "PROMPT-PRESETS.md", "AGENT-TOOLKIT.md", "APPLICATION-COMPOSITION.md", "SKILL-EVOLUTION.md", "AGENT-TEAM.md", "VOICE-STUDIO.md", "SKILLS.md", "schemas/production-runtime-readiness.v2.schema.json"]
diff --git a/grph-shared/package.json b/grph-shared/package.json
index e220fcf65..94f2d234f 100644
--- a/grph-shared/package.json
+++ b/grph-shared/package.json
@@ -348,6 +348,11 @@
"import": "./dist/search/exaMcpSsot.js",
"default": "./dist/search/exaMcpSsot.js"
},
+ "./search/exaSearchApiSsot": {
+ "types": "./dist/search/exaSearchApiSsot.d.ts",
+ "import": "./dist/search/exaSearchApiSsot.js",
+ "default": "./dist/search/exaSearchApiSsot.js"
+ },
"./search/feishuBaseMcpSsot": {
"types": "./dist/search/feishuBaseMcpSsot.d.ts",
"import": "./dist/search/feishuBaseMcpSsot.js",
diff --git a/grph-shared/src/search/exaSearchApiSsot.ts b/grph-shared/src/search/exaSearchApiSsot.ts
new file mode 100644
index 000000000..a45b2b5a0
--- /dev/null
+++ b/grph-shared/src/search/exaSearchApiSsot.ts
@@ -0,0 +1,107 @@
+export const EXA_SEARCH_API_DOC_AREA = 'Exa Search API for Coding Agents'
+
+export const EXA_SEARCH_API_DOCS_URL = 'https://exa.ai/docs/reference/search-api-guide-for-coding-agents'
+
+export const EXA_SEARCH_API_DOCS_MARKDOWN_URL = `${EXA_SEARCH_API_DOCS_URL}.md`
+
+export const EXA_SEARCH_API_ENDPOINT = 'https://api.exa.ai/search'
+
+export const EXA_SEARCH_API_KEY_ENV = 'EXA_API_KEY'
+
+export const EXA_SEARCH_API_AUTH_HEADER = 'Authorization'
+
+export const EXA_SEARCH_API_AUTH_SCHEME = 'Bearer'
+
+export const EXA_SEARCH_API_SEARCH_TYPES = [
+ 'auto',
+ 'fast',
+ 'instant',
+ 'deep-lite',
+ 'deep',
+ 'deep-reasoning',
+] as const
+
+export const EXA_SEARCH_API_DEFAULT_SEARCH_TYPE = 'auto'
+
+export const EXA_SEARCH_API_DEFAULT_NUM_RESULTS = 10
+
+export const EXA_SEARCH_API_MAX_NUM_RESULTS = 100
+
+export const EXA_SEARCH_API_CATEGORIES = [
+ 'company',
+ 'people',
+ 'publication',
+ 'news',
+ 'personal site',
+ 'financial report',
+] as const
+
+export const EXA_SEARCH_API_CODING_AGENT_CONTENTS = Object.freeze({ highlights: true })
+
+export const EXA_SEARCH_API_INVOCATION = Object.freeze({
+ action: '/tool.catalog',
+ semantic: '#tool-routing',
+ binding: '@tool-provider',
+})
+
+export const EXA_SEARCH_API_RESPONSE_FIELDS = [
+ 'requestId',
+ 'searchType',
+ 'results',
+ 'output',
+ 'costDollars.total',
+] as const
+
+export const EXA_SEARCH_API_ERROR_STATUSES = [400, 401, 422, 429, 500] as const
+
+export const EXA_SEARCH_API_DEPRECATED_FIELDS = [
+ 'useAutoprompt',
+ 'text (top-level)',
+ 'highlights (top-level)',
+ 'summary (top-level)',
+ 'numSentences',
+ 'highlightsPerUrl',
+ 'tokensNum',
+ 'livecrawl (string)',
+] as const
+
+export type ExaSearchApiSearchType = typeof EXA_SEARCH_API_SEARCH_TYPES[number]
+
+export type ExaCodingAgentSearchRequest = Readonly<{
+ query: string
+ type: ExaSearchApiSearchType
+ numResults: number
+ contents: Readonly<{ highlights: true }>
+}>
+
+const ALLOWED_SEARCH_TYPES = new Set(EXA_SEARCH_API_SEARCH_TYPES)
+
+export function buildExaCodingAgentSearchRequest(input: {
+ query: string
+ type?: string
+ numResults?: number
+}): ExaCodingAgentSearchRequest {
+ const query = String(input.query || '').trim()
+ if (!query) throw new Error('Exa search query is required.')
+ const type = ALLOWED_SEARCH_TYPES.has(String(input.type || ''))
+ ? input.type as ExaSearchApiSearchType
+ : EXA_SEARCH_API_DEFAULT_SEARCH_TYPE
+ const requestedCount = Number.isFinite(input.numResults)
+ ? Math.trunc(input.numResults as number)
+ : EXA_SEARCH_API_DEFAULT_NUM_RESULTS
+ const numResults = Math.min(EXA_SEARCH_API_MAX_NUM_RESULTS, Math.max(1, requestedCount))
+ return {
+ query,
+ type,
+ numResults,
+ contents: EXA_SEARCH_API_CODING_AGENT_CONTENTS,
+ }
+}
+
+export const EXA_SEARCH_API_CODING_AGENT_REQUEST_JSON = JSON.stringify(
+ buildExaCodingAgentSearchRequest({ query: 'repository issue and implementation context' }),
+ null,
+ 2,
+)
+
+export const EXA_SEARCH_API_INVOCATION_TEXT = Object.values(EXA_SEARCH_API_INVOCATION).join(' ')
From c7ba1e8fd08f883637a0babb80d888a96dfee392 Mon Sep 17 00:00:00 2001
From: "knowgrph-lifecycle[bot]"
Date: Thu, 13 Aug 2026 07:05:31 +0800
Subject: [PATCH 3/3] chore(coordination): claim
exa-coding-agent-integrations-v2 lease 485