eliciteer-chat-sdk
v0.2.0
Published
SDK for conducting AI-powered interviews with Eliciteer
Maintainers
Readme
Eliciteer Chat SDK
A lightweight TypeScript SDK for conducting AI-powered interviews with Eliciteer.
Installation
npm install eliciteer-chat-sdk
# or
yarn add eliciteer-chat-sdk
# or
pnpm add eliciteer-chat-sdkQuick Start
Vanilla JavaScript/TypeScript
import { EliciteerClient } from 'eliciteer-chat-sdk';
const client = new EliciteerClient();
// or with custom URL:
// const client = new EliciteerClient({ baseUrl: 'https://your-backend.com' });
// Load an interview by ID
const interview = await client.getInterview('interview-id');
console.log(interview.briefing);
console.log(interview.clientName);
// Send messages
let response = await client.sendMessage('interview-id', 'Hello, I am ready!');
console.log(response.response); // AI's reply
while (!response.isComplete) {
const userInput = await getUserInput(); // Your input method
response = await client.sendMessage('interview-id', userInput);
console.log(response.response);
}
console.log('Interview complete!', response.interviewSummary);React
import { EliciteerProvider, useInterview } from 'eliciteer-chat-sdk/react';
// Wrap your app with the provider
function App() {
return (
<EliciteerProvider config={{}}>
<InterviewPage interviewId="abc-123" />
</EliciteerProvider>
);
}
// Use the hook in your components
function InterviewPage({ interviewId }) {
const {
interview,
messages,
isLoading,
isInitializing,
isComplete,
error,
progress,
uiOptions,
sendMessage,
} = useInterview(interviewId);
const [input, setInput] = useState('');
if (isInitializing) {
return <div>Loading...</div>;
}
if (isComplete) {
return (
<div>
<h1>Thank you!</h1>
{interview?.interviewSummary && <p>{interview.interviewSummary}</p>}
</div>
);
}
const handleSubmit = async (e) => {
e.preventDefault();
if (!input.trim()) return;
setInput('');
await sendMessage(input);
};
return (
<div>
{/* Progress */}
<div>Topic {progress.current + 1} of {progress.total}</div>
{/* Messages */}
<div>
{messages.map((msg, i) => (
<div key={i} className={msg.role}>
{msg.content}
</div>
))}
</div>
{/* Input */}
{uiOptions?.type === 'boolean' ? (
<div>
<button onClick={() => sendMessage('Yes')} disabled={isLoading}>
Yes
</button>
<button onClick={() => sendMessage('No')} disabled={isLoading}>
No
</button>
</div>
) : (
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
disabled={isLoading}
/>
<button type="submit" disabled={isLoading}>
Send
</button>
</form>
)}
</div>
);
}API Reference
EliciteerClient
The main client for interacting with the Eliciteer API.
const client = new EliciteerClient({
baseUrl?: string; // Optional: Backend URL (default: 'https://api.eliciteer.ai')
apiPathPrefix?: string; // Optional: Path prefix after baseUrl (default: ''). Set to '/api'
// to route through a same-origin Next.js proxy (avoids CORS).
debug?: boolean; // Optional: Enable debug logging (default: false)
timeout?: number; // Optional: Request timeout in ms (default: 30000)
retries?: number; // Optional: Number of retry attempts (default: 3)
fetch?: typeof fetch; // Optional: Custom fetch implementation
});Same-origin vs cross-origin: by default the SDK calls the Eliciteer backend directly (
https://api.eliciteer.ai). If you host the SDK on the same origin as the Eliciteer web app and proxy requests under/api, setapiPathPrefix: '/api'so no cross-origin requests are made. For customer domains calling the backend directly, ensure the backend's CORS policy allows your origin (see the backendCORS_ALLOW_ORIGINS/CORS_ALLOW_ORIGIN_REGEXsettings).
Methods
startInterview(topicId: string, options?: StartInterviewOptions): Promise<StartInterviewResult>
Start a new interview session from a topic. This turns a topic (the reusable
briefing/template) into a live per-participant session. Pass context to supply
ad-hoc per-session context (e.g. a job description) that the AI uses on every
turn — keeping the topic/briefing generic.
const { interviewId } = await client.startInterview('topic-123', {
context: 'Job posting: Senior Python Engineer. Requires 5+ years Python, FastAPI.',
participantName: 'Jane',
participantEmail: '[email protected]',
});
// Then drive the conversation:
const response = await client.sendMessage(interviewId, 'I am ready!');If the backend has token enforcement enabled, pass the topic's apply_token
either by configuring it server-side or via the request; see the backend docs.
getInterview(interviewId: string): Promise<Interview>
Load an interview by ID.
const interview = await client.getInterview('abc-123');sendMessage(interviewId: string, message: string, options?: ChatOptions): Promise<ChatResponse>
Send a message in the interview.
const response = await client.sendMessage('abc-123', 'Hello!');
console.log(response.response); // AI's reply
console.log(response.isComplete); // Whether interview is done
console.log(response.uiOptions); // UI hints (text or boolean input)React Hooks
useInterview(interviewId: string, options?: UseInterviewOptions)
Main hook for conducting interviews.
const {
interview, // Interview state
messages, // Message array
isLoading, // Request in progress
isInitializing, // Initial load
isComplete, // Interview done
error, // Error if any
notFound, // 404 status
progress, // { current, total }
uiOptions, // Input type hints
debugInfo, // Debug data (if enabled)
sendMessage, // Send a message
reload, // Reload interview
} = useInterview(interviewId, {
debug: false, // Enable debug info
onComplete: (summary) => {}, // Completion callback
onError: (error) => {}, // Error callback
});useEliciteerChat(topicId: string, options?)
Auto-starting hook: creates a fresh interview session from a topic (with optional
ad-hoc context) and then drives the conversation with the same surface as
useInterview, plus interviewId, isStarting, and start().
const { messages, sendMessage, isStarting, isComplete } = useEliciteerChat('topic-123', {
context: jobDescription, // ad-hoc per-session context
participantName: 'Jane',
});Drop-in widget: <EliciteerChat>
A fully styled, self-contained chat window. One component opens the chat behind your "Apply" button — no UI to build:
import { EliciteerChat } from 'eliciteer-chat-sdk/react';
<EliciteerChat
topicId="topic-123"
context={jobDescription}
config={{ baseUrl: 'https://api.eliciteer.ai' }}
title="Apply for this role"
/>If you already wrap your tree in <EliciteerProvider>, you can omit config.
Static sites (Jekyll, plain HTML) — one <script> tag
For sites without a bundler or React, use the browser-global build from a CDN.
It bundles React + ReactDOM and exposes a global Eliciteer with mount():
<div id="eliciteer-chat"></div>
<script src="https://cdn.jsdelivr.net/npm/eliciteer-chat-sdk/dist/eliciteer.global.js"></script>
<script>
Eliciteer.mount('#eliciteer-chat', {
topicId: 'topic-123',
context: 'Job posting: Senior Python Engineer...',
config: { baseUrl: 'https://api.eliciteer.ai' },
title: 'Apply for this role'
});
</script>mount(target, options) returns a handle with unmount(). This is the
recommended integration for Jekyll and other static-site generators: drop the
script behind an "Apply" button and call mount().
Because the static page calls
api.eliciteer.aidirectly (cross-origin), the backend must allow your site's origin via its CORS configuration.
Types
Interview
interface Interview {
interviewId: string;
topicId: string;
briefing: string;
clientName?: string;
status: 'in_progress' | 'completed';
messages: Message[];
plan: TopicPlan[];
currentTopicIndex: number;
collectedInfo: string;
interviewSummary?: string;
context?: string; // Ad-hoc per-session context supplied at start
assessment?: Assessment; // Optional briefing-driven verdict (when complete)
createdAt: string;
completedAt?: string;
}ChatResponse
interface ChatResponse {
response: string;
status: 'continue' | 'completed';
isComplete: boolean;
interviewSummary?: string;
assessment?: Assessment; // Optional briefing-driven verdict (when complete)
currentTopicIndex: number;
totalTopics: number;
uiOptions?: UIOptions;
debugInfo?: DebugInfo;
}Assessment
A generic, briefing-driven verdict produced on completion — present only when the briefing/context asked for a judgment (e.g. a job screening decision):
interface Assessment {
applicable: boolean; // true only if the briefing requested a judgment
verdict?: string | null; // e.g. 'qualified' / 'not qualified'
score?: number | null; // optional 0.0–1.0
reasoning?: string | null;
}UIOptions
interface UIOptions {
type: 'text' | 'boolean';
options?: string[];
}Error Handling
The SDK provides typed errors for common scenarios:
import {
EliciteerError,
NotFoundError,
NetworkError,
TimeoutError,
} from 'eliciteer-chat-sdk';
try {
await client.sendMessage(interviewId, message);
} catch (error) {
if (error instanceof NotFoundError) {
console.log('Interview not found');
} else if (error instanceof NetworkError) {
console.log('Network issue');
} else if (error instanceof TimeoutError) {
console.log('Request timed out');
}
}Getting an Interview ID
Use startInterview (or the useEliciteerChat hook / <EliciteerChat> widget)
to create a session from a topic — no need to call the backend manually:
const client = new EliciteerClient({ baseUrl: 'https://api.eliciteer.ai' });
const { interviewId } = await client.startInterview('topic-123', {
context: 'Job posting: ...',
participantName: 'John Doe',
});
// Now use the SDK
const { messages, sendMessage } = useInterview(interviewId);License
Apache-2.0
