feat: add useAI hook for Anthropic and OpenAI streaming#562
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR adds a ChangesAI Adapter with OpenAI and Anthropic Streaming
Sequence DiagramsequenceDiagram
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>
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🎉 Thanks for your first PR to TermUI, @A-adilajaleel.
Before your PR merges:
- ⭐ Star the repo. Required. The
star-checkjob blocks your merge otherwise. - ✅ All checks green:
build,test,typecheck. - 🏷 PR title follows
type: short description. Example:fix: handle empty list. - 🔗 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.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/adapters/src/ai/index.test.ts (1)
59-95: ⚡ Quick winAdd 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
📒 Files selected for processing (4)
packages/adapters/package.jsonpackages/adapters/src/ai/index.test.tspackages/adapters/src/ai/index.tspackages/adapters/src/index.ts
| function isModuleNotFound(error: unknown): boolean { | ||
| return ( | ||
| error instanceof Error && | ||
| 'code' in error && | ||
| (error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND' | ||
| ) | ||
| } |
There was a problem hiding this comment.
🧩 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" || trueRepository: 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
doneRepository: 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:
- 1: https://nodejs.org/docs/latest/api/errors.html
- 2: nodejs/node@05cd1a0
- 3: https://nodejs.org/docs/v16.14.0/api/modules.html
- 4: module: add requireStack to all error paths nodejs/node#62059
- 5: lib: improve error message on missing module nodejs/node#25690
- 6: https://mrmurphy.dev/debugging-a-module-resolution-problem-in-node/
- 7: https://codeforgeek.com/nodejs-cannot-find-module/
- 8: doc: fix documentation for
MODULE_NOT_FOUNDandERR_MODULE_NOT_FOUNDnodejs/node#41645 - 9: https://nodejs.org/api/errors.html
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."
There was a problem hiding this comment.
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 winMissing required
moduleNameargument inisModuleNotFoundcall.The
isModuleNotFoundfunction signature was updated to require amoduleNameparameter (line 19), but this call site was not updated. This is either a TypeScript compile error or will cause runtime failure wheremoduleNameisundefined, 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 winMissing required
moduleNameargument inisModuleNotFoundcall.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
📒 Files selected for processing (1)
packages/adapters/src/ai/index.ts
Closes #87
Changes
useAI()hook supportingopenaiandanthropicprovidersgenerate(prompt)returningPromise<string>chat(messages)returningAsyncIterable<string>for token streamingopenaiand@anthropic-ai/sdkuseAIand related types frompackages/adapters/src/index.tsValidation
Summary by CodeRabbit
New Features
Tests
Chores