Skip to content

feat: add useAI hook for Anthropic and OpenAI streaming#562

Merged
Karanjot786 merged 3 commits into
Karanjot786:mainfrom
A-adilajaleel:feature/use-ai-hook
Jun 3, 2026
Merged

feat: add useAI hook for Anthropic and OpenAI streaming#562
Karanjot786 merged 3 commits into
Karanjot786:mainfrom
A-adilajaleel:feature/use-ai-hook

Conversation

@A-adilajaleel

@A-adilajaleel A-adilajaleel commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Closes #87

Changes

  • Added useAI() hook supporting openai and anthropic providers
  • Added generate(prompt) returning Promise<string>
  • Added chat(messages) returning AsyncIterable<string> for token streaming
  • Added optional peer dependencies: openai and @anthropic-ai/sdk
  • Added helpful error messages when SDKs are not installed
  • Added mocked tests for SDK streaming responses
  • Exported useAI and related types from packages/adapters/src/index.ts

Validation

  • All tests passing (13/13)
  • Typecheck passing across the workspace

Summary by CodeRabbit

  • New Features

    • Added a public AI adapter hook (useAI) and related types, supporting OpenAI and Anthropic with generate() text responses and chat() real-time streaming.
  • Tests

    • Added tests validating generate() outputs and chat() streaming behavior for both providers.
  • Chores

    • Package manifest updated to declare the OpenAI and Anthropic SDKs as optional peer dependencies.

@github-actions github-actions Bot added type:testing +10 pts. Tests. type:feature +10 pts. New feature. labels Jun 3, 2026
@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a405cc0-1cc0-4a0b-a9d8-b7555dbe03fb

📥 Commits

Reviewing files that changed from the base of the PR and between 229b6d7 and 1ada90e.

📒 Files selected for processing (1)
  • packages/adapters/src/ai/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/adapters/src/ai/index.ts

📝 Walkthrough

Walkthrough

This PR adds a useAI adapter hook that abstracts OpenAI and Anthropic integrations. It defines a unified interface for text generation and streaming chat, dynamically loads optional SDKs at runtime, routes to the selected provider, and includes mocked tests for both providers.

Changes

AI Adapter with OpenAI and Anthropic Streaming

Layer / File(s) Summary
Type contracts and public API
packages/adapters/src/ai/index.ts
Defines AIProvider ('openai' | 'anthropic'), AIOptions with apiKey, AIMessage shape, and AIAdapter interface with generate(prompt): Promise<string> and chat(messages): AsyncIterable<string>.
Runtime SDK loading & missing-SDK errors
packages/adapters/src/ai/index.ts
Adds dynamic provider SDK loading via createRequire, detects MODULE_NOT_FOUND, and emits provider-specific install error messages.
useAI generate and streaming chat implementation
packages/adapters/src/ai/index.ts
Implements useAI factory: routes generate to OpenAI (chat.completions.create) or Anthropic (messages.create) and implements chat as an async generator streaming tokens/deltas from provider stream APIs.
Mocked SDK tests for generate and chat streaming
packages/adapters/src/ai/index.test.ts
Adds Vitest mocks for node:module to stub OpenAI and Anthropic SDKs, verifies generate returns expected string and chat yields token sequences for both providers.
Dependency configuration and module exports
packages/adapters/package.json, packages/adapters/src/index.ts
Adds optional peer dependencies openai and @anthropic-ai/sdk and re-exports useAI, AIAdapter, AIMessage, AIOptions, and AIProvider from the adapters entry point.

Sequence Diagram

sequenceDiagram
  participant Client
  participant useAI
  participant SDKLoader
  participant OpenAI
  participant Anthropic
  Client->>useAI: useAI(provider, { apiKey })
  useAI->>SDKLoader: require(provider SDK)
  SDKLoader-->>useAI: provider client
  Client->>useAI: generate(prompt)
  alt provider == openai
    useAI->>OpenAI: chat.completions.create(model:gpt-4o-mini)
    OpenAI-->>useAI: message content
  else provider == anthropic
    useAI->>Anthropic: messages.create(model:claude-haiku-4-5-20251001)
    Anthropic-->>useAI: text content
  end
  useAI-->>Client: Promise<string>
  Client->>useAI: chat(messages)
  alt provider == openai
    useAI->>OpenAI: stream chat.completions.create(stream:true)
    OpenAI-->>useAI: delta.content tokens
  else provider == anthropic
    useAI->>Anthropic: messages.stream(...)
    Anthropic-->>useAI: content_block_delta events
  end
  useAI-->>Client: AsyncIterable<string>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibble code beneath the moon,
Two SDKs hum a streaming tune,
Tokens hop, then softly part,
Anthropic, OpenAI, both do their part,
Tests applaud — a rabbit's boon!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description covers the main changes and validation results but does not follow the repository's required template with sections like 'Which package(s)?', 'Type of Change', and 'Checklist'. Fill out the complete description template including 'Which package(s)?', 'Type of Change' checkboxes, and 'Checklist' items to ensure proper categorization and compliance.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add useAI hook for Anthropic and OpenAI streaming' accurately summarizes the main change and follows the required 'type: short description' format.
Linked Issues check ✅ Passed All coding requirements from issue #87 are met: useAI hook implemented with both providers, generate/chat methods return correct types, optional peer dependencies declared, error messages added, tests mock SDK responses, and exports are properly placed.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #87 objectives: useAI implementation, type exports, package.json updates, and test coverage with no extraneous modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎉 Thanks for your first PR to TermUI, @A-adilajaleel.

Before your PR merges:

  1. Star the repo. Required. The star-check job blocks your merge otherwise.
  2. ✅ All checks green: build, test, typecheck.
  3. 🏷 PR title follows type: short description. Example: fix: handle empty list.
  4. 🔗 Link your closing issue in the description.

GSSoC 2026 points come from labels after merge:

  • gssoc:approved. +50 base points.
  • level:beginner / intermediate / advanced / critical. +20 / +35 / +55 / +80.
  • quality:clean / exceptional. x 1.2 / x 1.5.
  • type:*. Stackable bonus.

Your reviewer responds within 48 hours. Ping @Karanjot786 on Discord for urgent help.

🚀 Welcome to the cohort.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/adapters/src/ai/index.test.ts (1)

59-95: ⚡ Quick win

Add a test for the missing optional SDK path.

The PR objective explicitly calls out helpful install errors, but the suite only exercises the happy path. A regression in the missing-peer-dependency branch would currently ship unnoticed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/ai/index.test.ts` around lines 59 - 95, Add a test that
simulates the optional SDK being absent and asserts the helpful install error
path in useAI: call useAI('openai' or 'anthropic', { apiKey: 'test-key',
sdkPath: '<nonexistent>' }) or otherwise mock the optional SDK loader to
throw/not-found, then verify that invoking generate and chat surfaces the
expected install/helpful error (or throws the specific error message) instead of
a generic failure; target the useAI factory and its generate/chat behaviors so
the test covers both providers and streaming chat code paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/adapters/src/ai/index.ts`:
- Around line 19-25: Change isModuleNotFound so it accepts the expected module
name (e.g., 'openai' or '`@anthropic-ai/sdk`') and only returns true when the
error is an Error with code === 'MODULE_NOT_FOUND' and its message indicates
that the missing module matches the provided moduleName (e.g., message contains
"Cannot find module 'openai'"). Update the call sites that currently call
isModuleNotFound(error) to pass the requested module string (the same literal
used in req(...)). If the check fails, ensure the original error is rethrown so
transitive-dependency failures are not rewritten as "install optional peer
dependency."

---

Nitpick comments:
In `@packages/adapters/src/ai/index.test.ts`:
- Around line 59-95: Add a test that simulates the optional SDK being absent and
asserts the helpful install error path in useAI: call useAI('openai' or
'anthropic', { apiKey: 'test-key', sdkPath: '<nonexistent>' }) or otherwise mock
the optional SDK loader to throw/not-found, then verify that invoking generate
and chat surfaces the expected install/helpful error (or throws the specific
error message) instead of a generic failure; target the useAI factory and its
generate/chat behaviors so the test covers both providers and streaming chat
code paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e9253fa4-2aa0-47d9-b52a-b4ab44bf0c17

📥 Commits

Reviewing files that changed from the base of the PR and between 9f85fe4 and 8346c5e.

📒 Files selected for processing (4)
  • packages/adapters/package.json
  • packages/adapters/src/ai/index.test.ts
  • packages/adapters/src/ai/index.ts
  • packages/adapters/src/index.ts

Comment thread packages/adapters/src/ai/index.ts Outdated
Comment on lines +19 to +25
function isModuleNotFound(error: unknown): boolean {
return (
error instanceof Error &&
'code' in error &&
(error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND'
)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/adapters/src/ai/index.ts"

echo "== File exists and line count =="
ls -la "$FILE"
wc -l "$FILE"

echo
echo "== Relevant sections (around lines 1-120) =="
sed -n '1,120p' "$FILE" | nl -ba

echo
echo "== Search for helper and loader functions =="
rg -n "function isModuleNotFound|loadOpenAI|loadAnthropic|useAI" "$FILE" || true

Repository: Karanjot786/TermUI

Length of output: 324


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/adapters/src/ai/index.ts"

echo "== File head with line numbers (1-140) =="
sed -n '1,140p' "$FILE" | cat -n

echo
echo "== Exact implementations (helpers + loaders) =="
rg -n "function isModuleNotFound|function loadOpenAI|function loadAnthropic|export function useAI|useAI\\(" "$FILE" || true

# Print a bit more around any matched lines
for pat in "function isModuleNotFound" "function loadOpenAI" "function loadAnthropic"; do
  LINE=$(rg -n "$pat" "$FILE" | head -n 1 | cut -d: -f1 || true)
  if [ -n "${LINE:-}" ]; then
    START=$((LINE-15))
    END=$((LINE+60))
    if [ "$START" -lt 1 ]; then START=1; fi
    echo
    echo "---- $pat (context $START-$END) ----"
    sed -n "${START},${END}p" "$FILE" | cat -n
  fi
done

Repository: Karanjot786/TermUI

Length of output: 13631


🌐 Web query:

Node.js MODULE_NOT_FOUND error message contains missing module name error fields requireStack code path

💡 Result:

The MODULE_NOT_FOUND error in Node.js occurs when the module resolution system is unable to locate a requested file or package [1][2][3]. When this error is thrown, the resulting Error object typically includes specific properties to aid in debugging [2][4][5]. Core Error Fields: - code: A string set to 'MODULE_NOT_FOUND' [2][3]. - requireStack: An array of strings representing the stack of files that led to the module resolution attempt [2][4]. This property provides a breadcrumb trail of imports or requires, making it easier to identify where the broken dependency originated [5]. Recent Improvements: Historically, the requireStack property was not consistently populated across all resolution paths (such as when failing to resolve via package.json 'main' fields or self-referential imports) [4]. However, as of March 2026, Node.js core includes updates to ensure that requireStack is constructed and attached to the error regardless of which specific resolution sub-path triggered the failure [4]. Debugging Tips: - Inspect requireStack: Since this property is an array, you can programmatically log it to see the chain of files that caused the error [5]. - Use NODE_DEBUG: You can set the environment variable NODE_DEBUG=loader,module to see verbose output of the module system's resolution process, which often reveals the exact location where the lookup failed [6]. - Diagnostic Tools: Functions like require.resolve.paths(moduleName) allow you to view the list of directories Node.js searches for a given module, helping to verify if your directory structure or configuration matches expected behavior [7]. Note that while MODULE_NOT_FOUND is the standard code for resolution failures, the ECMAScript module loader may specifically throw ERR_MODULE_NOT_FOUND [1][8]. Both error types are intended to signal a failure to resolve a module specifier [1][9].

Citations:


Gate the “install SDK” error on missing the requested provider module

packages/adapters/src/ai/index.ts currently treats any MODULE_NOT_FOUND thrown by req('openai') / req('@anthropic-ai/sdk') as “install the optional peer dependency”, which can mask failures where those packages are present but a transitive dependency is missing. Make isModuleNotFound accept the expected module name and only rewrite when the error indicates the missing module is openai or @anthropic-ai/sdk; otherwise rethrow the original error.

Suggested direction
-function isModuleNotFound(error: unknown): boolean {
+function isModuleNotFound(error: unknown, moduleName: string): boolean {
   return (
     error instanceof Error &&
-    'code' in error &&
-    (error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND'
+    (error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND' &&
+    error.message.includes(`'${moduleName}'`)
   )
 }
 
 function loadOpenAI() {
+  const req = createRequire(import.meta.url)
   try {
-    const req = createRequire(import.meta.url)
     // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
     const mod = req('openai') as any
     return 'default' in mod ? mod.default : mod
   } catch (error) {
-    if (isModuleNotFound(error)) {
+    if (isModuleNotFound(error, 'openai')) {
       throw new Error(
         'useAI() requires the optional peer dependency `openai`. Install it with: bun add openai'
       )
     }
     throw error
   }
 }
 
 function loadAnthropic() {
+  const req = createRequire(import.meta.url)
   try {
-    const req = createRequire(import.meta.url)
     // eslint-disable-next-line `@typescript-eslint/no-explicit-any`
     const mod = req('`@anthropic-ai/sdk`') as any
     return 'default' in mod ? mod.default : mod
   } catch (error) {
-    if (isModuleNotFound(error)) {
+    if (isModuleNotFound(error, '`@anthropic-ai/sdk`')) {
       throw new Error(
         'useAI() requires the optional peer dependency `@anthropic-ai/sdk`. Install it with: bun add `@anthropic-ai/sdk`'
       )
     }
     throw error
   }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/ai/index.ts` around lines 19 - 25, Change
isModuleNotFound so it accepts the expected module name (e.g., 'openai' or
'`@anthropic-ai/sdk`') and only returns true when the error is an Error with code
=== 'MODULE_NOT_FOUND' and its message indicates that the missing module matches
the provided moduleName (e.g., message contains "Cannot find module 'openai'").
Update the call sites that currently call isModuleNotFound(error) to pass the
requested module string (the same literal used in req(...)). If the check fails,
ensure the original error is rethrown so transitive-dependency failures are not
rewritten as "install optional peer dependency."

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/adapters/src/ai/index.ts (2)

35-35: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing required moduleName argument in isModuleNotFound call.

The isModuleNotFound function signature was updated to require a moduleName parameter (line 19), but this call site was not updated. This is either a TypeScript compile error or will cause runtime failure where moduleName is undefined, breaking the detection logic.

🐛 Proposed fix
-    if (isModuleNotFound(error)) {
+    if (isModuleNotFound(error, 'openai')) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/ai/index.ts` at line 35, The call to isModuleNotFound
in the AI adapter index is missing the required moduleName argument after the
signature change, so update this call site to pass the specific module name
being checked. Use the surrounding error-handling logic in index.ts to identify
the relevant module identifier and thread it into isModuleNotFound(error,
moduleName) so the detection path works correctly again.

51-51: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Missing required moduleName argument in isModuleNotFound call.

Same issue as loadOpenAI—the call site needs updating to pass the expected module name.

🐛 Proposed fix
-    if (isModuleNotFound(error)) {
+    if (isModuleNotFound(error, '`@anthropic-ai/sdk`')) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/adapters/src/ai/index.ts` at line 51, The call to isModuleNotFound
inside the function handling errors is missing the required moduleName argument.
To fix this, update the call to isModuleNotFound by adding the specific module
name string being checked for, similar to the fix applied in the loadOpenAI
function. This will ensure isModuleNotFound receives the correct argument to
perform its check properly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/adapters/src/ai/index.ts`:
- Line 35: The call to isModuleNotFound in the AI adapter index is missing the
required moduleName argument after the signature change, so update this call
site to pass the specific module name being checked. Use the surrounding
error-handling logic in index.ts to identify the relevant module identifier and
thread it into isModuleNotFound(error, moduleName) so the detection path works
correctly again.
- Line 51: The call to isModuleNotFound inside the function handling errors is
missing the required moduleName argument. To fix this, update the call to
isModuleNotFound by adding the specific module name string being checked for,
similar to the fix applied in the loadOpenAI function. This will ensure
isModuleNotFound receives the correct argument to perform its check properly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 88c5b0df-4338-4354-944b-67e119e6b656

📥 Commits

Reviewing files that changed from the base of the PR and between 8346c5e and 229b6d7.

📒 Files selected for processing (1)
  • packages/adapters/src/ai/index.ts

@Karanjot786 Karanjot786 added gssoc:approved Approved PR. Earns +50 base points. quality:clean x 1.2 multiplier. Clean implementation. level:advanced +55 pts. Complex task. quality:exceptional x 1.5 multiplier. Exceptional work. and removed quality:clean x 1.2 multiplier. Clean implementation. labels Jun 3, 2026
@Karanjot786 Karanjot786 merged commit d70574d into Karanjot786:main Jun 3, 2026
8 checks passed
Debangana-Dutta pushed a commit to Debangana-Dutta/TermUI that referenced this pull request Jun 6, 2026
)

* feat: add useAI hook for Anthropic and OpenAI streaming

* fix: gate MODULE_NOT_FOUND on requested module name

* fix: gate MODULE_NOT_FOUND on requested module name

Co-Authored-By: karanjots801@gmail.com

---------

Co-authored-by: Karanjot786 <karanjots801@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved Approved PR. Earns +50 base points. level:advanced +55 pts. Complex task. quality:exceptional x 1.5 multiplier. Exceptional work. type:feature +10 pts. New feature. type:testing +10 pts. Tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feature] Add AI adapter (useAI hook) for Anthropic and OpenAI streaming

2 participants