Repository: TabbyML/tabby
File: clients/tabby-chat-panel/src/index.ts (TypeScript)

Code before:
   * Open the target URL in the external browser.
   * @param url The target URL to open.
   */
  openExternal: (url: string) => Promise<void>

  // Provide all repos found in workspace folders.
  readWorkspaceGitRepositories?: () => Promise<GitRepository[]>

  /**
   * @returns The active selection of active editor.
   */
  getActiveEditorSelection: () => Promise<EditorFileContext | null>

  /**
   * Return a FileAtInfo List with kind of file
   * @param kind passing what kind of At info client want to get
   * @returns FileAtInfo array
   */
  provideFileAtInfo?: (opts?: AtInputOpts) => Promise<FileInfo[] | null>

  /**
   * Return the range of content in the file
   * @param info context to determine the content range
   * @returns the content of the range
   */
  provideRangeContent?: (info: FileInfo) => Promise<string | null>
}

export interface ClientApi extends ClientApiMethods {
  /**
   * Checks if the client supports this capability.
   * This method is designed to check capability across different clients (IDEs).
   * Note: This method should not be used to ensure compatibility across different chat panel SDK versions.
   */
  hasCapability: (method: keyof ClientApiMethods) => Promise<boolean>
}

export function createClient(target: HTMLIFrameElement, api: ClientApiMethods): ServerApi {
  return createThreadFromIframe(target, {
    expose: {
      refresh: api.refresh,
      onApplyInEditor: api.onApplyInEditor,
      onApplyInEditorV2: api.onApplyInEditorV2,
      onLoaded: api.onLoaded,
      onCopy: api.onCopy,
      onKeyboardEvent: api.onKeyboardEvent,
      lookupSymbol: api.lookupSymbol,
      openInEditor: api.openInEditor,
      openExternal: api.openExternal,
      readWorkspaceGitRepositories: api.readWorkspaceGitRepositories,
      getActiveEditorSelection: api.getActiveEditorSelection,

Review comment (suggestion):
```suggestion
  readFileContent?: (range: FileRange) => Promise<string | null>
```

Code after:
  onKeyboardEvent: (type: 'keydown' | 'keyup' | 'keypress', event: KeyboardEventInit) => void

  /**
   * Find the target symbol and return the symbol information.
   * @param symbol The symbol to find.
   * @param hints The optional {@link LookupSymbolHint} list to help find the symbol. The hints should be sorted by priority.
   * @returns The symbol information if found, otherwise undefined.
   */
  lookupSymbol?: (symbol: string, hints?: LookupSymbolHint[] | undefined) => Promise<SymbolInfo | undefined>

  /**
   * Open the target file location in the editor.
   * @param target The target file location to open.
   * @returns Whether the file location is opened successfully.
   */
  openInEditor: (target: FileLocation) => Promise<boolean>

  /**
   * Open the target URL in the external browser.
   * @param url The target URL to open.
   */
  openExternal: (url: string) => Promise<void>

  // Provide all repos found in workspace folders.
  readWorkspaceGitRepositories?: () => Promise<GitRepository[]>

  /**
   * @returns The active selection of active editor.
   */
  getActiveEditorSelection: () => Promise<EditorFileContext | null>

  /**
   * Fetch the saved session state from the client.
   * When initialized, the chat panel attempts to fetch the saved session state to restore the session.
   * @param keys The keys to be fetched. If not provided, all keys will be returned.
   * @return The saved persisted state, or null if no state is found.
   */
  fetchSessionState?: (keys?: string[] | undefined) => Promise<Record<string, unknown> | null>

  /**
   * Save the session state of the chat panel.
   * The client is responsible for maintaining the state in case of a webview reload.
   * The saved state should be merged and updated by the record key.
   * @param state The state to save.
   */
  storeSessionState?: (state: Record<string, unknown>) => Promise<void>

  /**
   * Returns a list of file information matching the specified query.
   * @param params An {@link ListFilesInWorkspaceParams} object that includes a search query and a limit for the results.
   * @returns An array of {@link ListFileItem} objects that could be empty.