@thaliq/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +224 -0
- package/__test-utils__/helpers.d.ts +20 -0
- package/agent/agent-client.d.ts +45 -0
- package/agent/agent-stream.d.ts +22 -0
- package/agent/conversation-manager.d.ts +19 -0
- package/agent/index.d.ts +3 -0
- package/client.d.ts +25 -0
- package/index.d.ts +47 -0
- package/package.json +39 -0
- package/providers/thaliq-native/http-client.d.ts +26 -0
- package/providers/thaliq-native/index.d.ts +2 -0
- package/providers/thaliq-native/sse-parser.d.ts +6 -0
- package/providers/thaliq-native/thaliq-native.provider.d.ts +19 -0
- package/thaliq-sdk.cjs.js +2 -0
- package/thaliq-sdk.cjs.js.map +1 -0
- package/thaliq-sdk.es.js +2 -0
- package/thaliq-sdk.es.js.map +1 -0
- package/types/actions.d.ts +71 -0
- package/types/agent.d.ts +60 -0
- package/types/config.d.ts +101 -0
- package/types/conversations.d.ts +11 -0
- package/types/errors.d.ts +49 -0
- package/types/events.d.ts +70 -0
- package/types/identity.d.ts +21 -0
- package/types/index.d.ts +13 -0
- package/types/provider.d.ts +52 -0
- package/types/responses.d.ts +48 -0
- package/utils/action-response-builder.d.ts +29 -0
- package/utils/event-emitter.d.ts +30 -0
- package/utils/logger.d.ts +2 -0
- package/utils/retry.d.ts +32 -0
- package/utils/uuid.d.ts +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# @thaliq/sdk
|
|
2
|
+
|
|
3
|
+
Headless, type-safe, provider-agnostic SDK for building applications with [Thaliq](https://thaliq.com) AI agents.
|
|
4
|
+
|
|
5
|
+
- **Streaming-first** — `AsyncIterable<StreamEvent>` as primary primitive
|
|
6
|
+
- **Zero dependencies** — Only native runtime APIs (`fetch`, `ReadableStream`)
|
|
7
|
+
- **Provider-agnostic** — Swap backends via the `Provider` interface
|
|
8
|
+
- **Human-in-the-Loop** — Built-in HITL action support (consent, confirm, select, form)
|
|
9
|
+
- **Automatic retry** — Exponential backoff on server/network errors
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @thaliq/sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Thaliq } from '@thaliq/sdk';
|
|
21
|
+
|
|
22
|
+
const thaliq = new Thaliq({ apiKey: 'tq_live_xxx' });
|
|
23
|
+
|
|
24
|
+
// Non-streaming
|
|
25
|
+
const response = await thaliq.agent.chat('Hello!');
|
|
26
|
+
console.log(response.message);
|
|
27
|
+
|
|
28
|
+
// Streaming
|
|
29
|
+
const stream = thaliq.agent.stream('Analyze Q4 sales');
|
|
30
|
+
for await (const event of stream) {
|
|
31
|
+
if (event.type === 'content.delta') {
|
|
32
|
+
process.stdout.write(event.delta);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Streaming
|
|
38
|
+
|
|
39
|
+
Every stream event has a `type` field for type narrowing:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const stream = thaliq.agent.stream('Hello');
|
|
43
|
+
|
|
44
|
+
for await (const event of stream) {
|
|
45
|
+
switch (event.type) {
|
|
46
|
+
case 'meta':
|
|
47
|
+
console.log('Conversation:', event.conversationId);
|
|
48
|
+
break;
|
|
49
|
+
case 'status':
|
|
50
|
+
console.log('Status:', event.text);
|
|
51
|
+
break;
|
|
52
|
+
case 'content.delta':
|
|
53
|
+
process.stdout.write(event.delta);
|
|
54
|
+
break;
|
|
55
|
+
case 'tool.start':
|
|
56
|
+
console.log(`Running tool: ${event.tool}`);
|
|
57
|
+
break;
|
|
58
|
+
case 'tool.end':
|
|
59
|
+
console.log(`Tool ${event.tool}: ${event.success ? 'ok' : 'failed'}`);
|
|
60
|
+
break;
|
|
61
|
+
case 'response.completed':
|
|
62
|
+
console.log('\nDone. Tokens:', event.metadata.completionTokens);
|
|
63
|
+
break;
|
|
64
|
+
case 'error':
|
|
65
|
+
console.error('Error:', event.message);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Convenience methods
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Get just the text
|
|
75
|
+
const text = await thaliq.agent.stream('Summarize this').text();
|
|
76
|
+
|
|
77
|
+
// Get the full response (like chat, but streamed)
|
|
78
|
+
const response = await thaliq.agent.stream('Summarize this').finalResponse();
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## User Identity
|
|
82
|
+
|
|
83
|
+
For authenticated B2B scenarios, identify the user before sending messages:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
thaliq.identify({
|
|
87
|
+
userId: 'usr_123',
|
|
88
|
+
email: 'user@company.com',
|
|
89
|
+
token: 'eyJ...', // JWT passthrough for MCP servers
|
|
90
|
+
mcpTokens: { 'my-mcp-server': 'server-specific-token' },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Clear on logout
|
|
94
|
+
thaliq.reset();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Human-in-the-Loop (HITL)
|
|
98
|
+
|
|
99
|
+
When the agent requires user approval before executing a tool:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { ActionResponseBuilder } from '@thaliq/sdk';
|
|
103
|
+
|
|
104
|
+
const stream = thaliq.agent.stream('Transfer $500 to Alice');
|
|
105
|
+
for await (const event of stream) {
|
|
106
|
+
if (event.type === 'content.delta') process.stdout.write(event.delta);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (stream.pendingAction) {
|
|
110
|
+
// User approved — resume the stream
|
|
111
|
+
const resumed = thaliq.agent.respondToAction({
|
|
112
|
+
conversationId: stream.conversationId!,
|
|
113
|
+
originalMessage: 'Transfer $500 to Alice',
|
|
114
|
+
actionResponse: ActionResponseBuilder.accept(stream.pendingAction.instructionId),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
for await (const event of resumed) {
|
|
118
|
+
if (event.type === 'content.delta') process.stdout.write(event.delta);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Action response helpers:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
ActionResponseBuilder.accept(instructionId); // consent/confirm
|
|
127
|
+
ActionResponseBuilder.reject(instructionId); // reject
|
|
128
|
+
ActionResponseBuilder.select(instructionId, 'option_a'); // select
|
|
129
|
+
ActionResponseBuilder.submitForm(instructionId, { amount: '500' }); // form
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Conversations
|
|
133
|
+
|
|
134
|
+
Conversations are auto-tracked across streams:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const stream = thaliq.agent.stream('Hello');
|
|
138
|
+
for await (const event of stream) { /* ... */ }
|
|
139
|
+
|
|
140
|
+
// Continue the conversation
|
|
141
|
+
const followUp = thaliq.agent.stream('Tell me more', {
|
|
142
|
+
conversationId: thaliq.conversations.active!.id,
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Configuration
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
const thaliq = new Thaliq({
|
|
150
|
+
apiKey: 'tq_live_xxx', // Required
|
|
151
|
+
baseUrl: 'https://api.thaliq.com', // Default
|
|
152
|
+
integrationType: 'sdk', // 'sdk' | 'widget'
|
|
153
|
+
timeout: 30000, // Request timeout (ms)
|
|
154
|
+
maxRetries: 2, // Automatic retries
|
|
155
|
+
retry: {
|
|
156
|
+
baseDelay: 500, // Initial retry delay
|
|
157
|
+
maxDelay: 10000, // Max retry delay
|
|
158
|
+
},
|
|
159
|
+
logLevel: 'warn', // 'debug' | 'info' | 'warn' | 'error' | 'silent'
|
|
160
|
+
defaultHeaders: { // Custom headers on every request
|
|
161
|
+
'x-tenant-id': 'abc',
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Events
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
thaliq.on('error', (err) => console.error(err.code, err.message));
|
|
170
|
+
thaliq.on('retry', (attempt, err, delayMs) => console.warn(`Retry #${attempt}`));
|
|
171
|
+
thaliq.on('stream.start', (convId) => { /* ... */ });
|
|
172
|
+
thaliq.on('stream.end', (convId) => { /* ... */ });
|
|
173
|
+
thaliq.on('identify', (userId) => { /* ... */ });
|
|
174
|
+
thaliq.on('reset', () => { /* ... */ });
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Error Handling
|
|
178
|
+
|
|
179
|
+
All SDK errors extend `ThaliqError` with a `code` property:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { ThaliqError, AuthError, RateLimitError } from '@thaliq/sdk';
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await thaliq.agent.chat('Hello');
|
|
186
|
+
} catch (err) {
|
|
187
|
+
if (err instanceof RateLimitError) {
|
|
188
|
+
console.log(`Rate limited. Retry after ${err.retryAfter}s`);
|
|
189
|
+
} else if (err instanceof AuthError) {
|
|
190
|
+
console.log('Invalid API key');
|
|
191
|
+
} else if (err instanceof ThaliqError) {
|
|
192
|
+
console.log(err.code, err.message);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
| Error Class | Code | HTTP Status |
|
|
198
|
+
|-------------|------|-------------|
|
|
199
|
+
| `AuthError` | `AUTH_ERROR` | 401, 403 |
|
|
200
|
+
| `RateLimitError` | `RATE_LIMIT` | 429 |
|
|
201
|
+
| `ValidationError` | `VALIDATION_ERROR` | 400 |
|
|
202
|
+
| `ServiceError` | `SERVICE_ERROR` | 503 |
|
|
203
|
+
| `StreamError` | `STREAM_ERROR` | — |
|
|
204
|
+
| `TimeoutError` | `TIMEOUT_ERROR` | — |
|
|
205
|
+
| `ConnectionError` | `CONNECTION_ERROR` | — |
|
|
206
|
+
|
|
207
|
+
## Compatibility
|
|
208
|
+
|
|
209
|
+
| Runtime | Minimum |
|
|
210
|
+
|---------|---------|
|
|
211
|
+
| Node.js | 18+ (native `fetch`) |
|
|
212
|
+
| Browsers | Chrome 89+, Firefox 89+, Safari 15+ |
|
|
213
|
+
| Deno | 1.28+ |
|
|
214
|
+
| Bun | 1.0+ |
|
|
215
|
+
|
|
216
|
+
## Documentation
|
|
217
|
+
|
|
218
|
+
- [Full SDK Documentation](https://docs.thaliq.com/sdk)
|
|
219
|
+
- [SDK Protocol Specification](https://docs.thaliq.com/sdk/protocol)
|
|
220
|
+
- [Integration Guide](https://docs.thaliq.com/integration)
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Logger } from '../types/config';
|
|
2
|
+
import { Provider, ProviderRequest } from '../types/provider';
|
|
3
|
+
import { ChatResponse } from '../types/responses';
|
|
4
|
+
import { StreamEvent } from '../types/events';
|
|
5
|
+
/** Create a mock logger that captures calls */
|
|
6
|
+
export declare function createMockLogger(): Logger & {
|
|
7
|
+
calls: Record<string, unknown[][]>;
|
|
8
|
+
};
|
|
9
|
+
/** Encode SSE events into a ReadableStream<Uint8Array> */
|
|
10
|
+
export declare function createSSEStream(events: Array<{
|
|
11
|
+
event: string;
|
|
12
|
+
data: string;
|
|
13
|
+
}>): ReadableStream<Uint8Array>;
|
|
14
|
+
/** Create a mock provider for testing */
|
|
15
|
+
export declare function createMockProvider(overrides?: {
|
|
16
|
+
chat?: (request: ProviderRequest) => Promise<ChatResponse>;
|
|
17
|
+
stream?: (request: ProviderRequest) => AsyncIterable<StreamEvent>;
|
|
18
|
+
}): Provider;
|
|
19
|
+
/** Sleep for a given number of milliseconds */
|
|
20
|
+
export declare function delay(ms: number): Promise<void>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Provider } from '../types/provider';
|
|
2
|
+
import { ChatOptions, StreamOptions, AgentStream, RespondToActionOptions } from '../types/agent';
|
|
3
|
+
import { ChatResponse } from '../types/responses';
|
|
4
|
+
import { UserIdentity } from '../types/identity';
|
|
5
|
+
import { ResolvedConfig, SDKEventMap } from '../types/config';
|
|
6
|
+
import { TypedEventEmitter } from '../utils/event-emitter';
|
|
7
|
+
import { ConversationManager } from './conversation-manager';
|
|
8
|
+
export declare class AgentClient {
|
|
9
|
+
private readonly config;
|
|
10
|
+
private readonly provider;
|
|
11
|
+
private readonly getIdentity;
|
|
12
|
+
private readonly emitter;
|
|
13
|
+
private readonly conversationManager;
|
|
14
|
+
constructor(config: ResolvedConfig, provider: Provider, getIdentity: () => UserIdentity | null, emitter: TypedEventEmitter<SDKEventMap>, conversationManager: ConversationManager);
|
|
15
|
+
/** Send a message and wait for the complete response (with automatic retry) */
|
|
16
|
+
chat(message: string, options?: ChatOptions): Promise<ChatResponse>;
|
|
17
|
+
/** Send a message with streaming — returns an AgentStream */
|
|
18
|
+
stream(message: string, options?: StreamOptions): AgentStream;
|
|
19
|
+
/**
|
|
20
|
+
* Respond to a pending HITL action and resume the conversation.
|
|
21
|
+
*
|
|
22
|
+
* This is a convenience method that creates a new stream with the
|
|
23
|
+
* action response, continuing the conversation where it left off.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const stream = thaliq.agent.stream('Transfer $500');
|
|
28
|
+
* for await (const event of stream) { ... }
|
|
29
|
+
*
|
|
30
|
+
* if (stream.pendingAction) {
|
|
31
|
+
* const resumed = thaliq.agent.respondToAction({
|
|
32
|
+
* conversationId: stream.conversationId!,
|
|
33
|
+
* originalMessage: 'Transfer $500',
|
|
34
|
+
* actionResponse: ActionResponseBuilder.accept(stream.pendingAction.instructionId),
|
|
35
|
+
* });
|
|
36
|
+
* for await (const event of resumed) { ... }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
respondToAction(options: RespondToActionOptions): AgentStream;
|
|
41
|
+
/** Build the ProviderRequest from message + options + identity */
|
|
42
|
+
private buildRequest;
|
|
43
|
+
/** Resolve all headers from config + identity */
|
|
44
|
+
private resolveHeaders;
|
|
45
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AgentStream as IAgentStream, StreamStatus } from '../types/agent';
|
|
2
|
+
import { StreamEvent } from '../types/events';
|
|
3
|
+
import { ChatResponse } from '../types/responses';
|
|
4
|
+
import { PendingAction } from '../types/actions';
|
|
5
|
+
export declare class AgentStreamImpl implements IAgentStream {
|
|
6
|
+
private _conversationId;
|
|
7
|
+
private _status;
|
|
8
|
+
private _pendingAction;
|
|
9
|
+
private readonly _source;
|
|
10
|
+
private readonly _abortController;
|
|
11
|
+
private readonly _onEvent?;
|
|
12
|
+
private readonly _onComplete?;
|
|
13
|
+
private _consumed;
|
|
14
|
+
constructor(source: AsyncIterable<StreamEvent>, abortController: AbortController, onEvent?: (event: StreamEvent) => void, onComplete?: (conversationId: string | null, status: StreamStatus) => void);
|
|
15
|
+
get conversationId(): string | null;
|
|
16
|
+
get status(): StreamStatus;
|
|
17
|
+
get pendingAction(): PendingAction | null;
|
|
18
|
+
abort(): void;
|
|
19
|
+
[Symbol.asyncIterator](): AsyncIterator<StreamEvent>;
|
|
20
|
+
finalResponse(): Promise<ChatResponse>;
|
|
21
|
+
text(): Promise<string>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ConversationRef } from '../types/conversations';
|
|
2
|
+
export declare class ConversationManager {
|
|
3
|
+
private _active;
|
|
4
|
+
private readonly _history;
|
|
5
|
+
/** Create a new conversation reference */
|
|
6
|
+
create(): ConversationRef;
|
|
7
|
+
/** Get the most recently active conversation */
|
|
8
|
+
get active(): ConversationRef | null;
|
|
9
|
+
/** Get all known conversation references (in-memory only) */
|
|
10
|
+
get history(): readonly ConversationRef[];
|
|
11
|
+
/**
|
|
12
|
+
* Set the active conversation by ID (called internally after stream completes).
|
|
13
|
+
* If the ID matches an existing ref, that ref becomes active.
|
|
14
|
+
* Otherwise a new ref is created.
|
|
15
|
+
*/
|
|
16
|
+
setActive(conversationId: string): void;
|
|
17
|
+
/** Clear all state */
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
package/agent/index.d.ts
ADDED
package/client.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ThaliqConfig, SDKEventMap } from './types/config';
|
|
2
|
+
import { UserIdentity } from './types/identity';
|
|
3
|
+
import { AgentClient } from './agent/agent-client';
|
|
4
|
+
import { ConversationManager } from './agent/conversation-manager';
|
|
5
|
+
export declare class Thaliq {
|
|
6
|
+
private readonly config;
|
|
7
|
+
private readonly provider;
|
|
8
|
+
private readonly emitter;
|
|
9
|
+
private identity;
|
|
10
|
+
/** Agent client — use `thaliq.agent.chat()` and `thaliq.agent.stream()` */
|
|
11
|
+
readonly agent: AgentClient;
|
|
12
|
+
/** Conversations manager — create, track, and list conversations */
|
|
13
|
+
readonly conversations: ConversationManager;
|
|
14
|
+
constructor(userConfig: ThaliqConfig);
|
|
15
|
+
/** Register an event listener */
|
|
16
|
+
on<K extends keyof SDKEventMap>(event: K, listener: SDKEventMap[K]): this;
|
|
17
|
+
/** Register a one-time event listener */
|
|
18
|
+
once<K extends keyof SDKEventMap>(event: K, listener: SDKEventMap[K]): this;
|
|
19
|
+
/** Remove an event listener */
|
|
20
|
+
off<K extends keyof SDKEventMap>(event: K, listener: SDKEventMap[K]): this;
|
|
21
|
+
/** Set the user identity for subsequent requests */
|
|
22
|
+
identify(user: UserIdentity): void;
|
|
23
|
+
/** Clear user identity and local state */
|
|
24
|
+
reset(): void;
|
|
25
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @thaliq/sdk — Agentic SDK for Thaliq Platform
|
|
3
|
+
*
|
|
4
|
+
* Headless, type-safe, provider-agnostic SDK for building
|
|
5
|
+
* applications with Thaliq AI agents.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { Thaliq } from '@thaliq/sdk';
|
|
10
|
+
*
|
|
11
|
+
* const thaliq = new Thaliq({ apiKey: 'tq_live_xxx' });
|
|
12
|
+
*
|
|
13
|
+
* // Events
|
|
14
|
+
* thaliq.on('error', (err) => console.error(err));
|
|
15
|
+
* thaliq.on('retry', (attempt, err, delay) => console.warn(`Retry #${attempt}`));
|
|
16
|
+
*
|
|
17
|
+
* // Stream with auto-tracking
|
|
18
|
+
* const stream = thaliq.agent.stream('Hello!');
|
|
19
|
+
* for await (const event of stream) {
|
|
20
|
+
* if (event.type === 'content.delta') {
|
|
21
|
+
* process.stdout.write(event.delta);
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* console.log(thaliq.conversations.active);
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
export { Thaliq } from './client';
|
|
31
|
+
export { ThaliqNativeProvider } from './providers/thaliq-native';
|
|
32
|
+
export type { ThaliqNativeProviderConfig } from './providers/thaliq-native';
|
|
33
|
+
export { ConversationManager } from './agent/conversation-manager';
|
|
34
|
+
export type { ThaliqConfig, ResolvedConfig, RetryConfig, ResolvedRetryConfig, SDKEventMap, Logger, LogLevel, } from './types/config';
|
|
35
|
+
export type { UserIdentity } from './types/identity';
|
|
36
|
+
export type { AgentType, ChatOptions, StreamOptions, AgentStream, StreamStatus, RespondToActionOptions, } from './types/agent';
|
|
37
|
+
export type { StreamEvent, MetaEvent, StatusEvent, ContentDeltaEvent, ToolStartEvent, ToolEndEvent, ActionEvent, ResponseCompletedEvent, ErrorEvent, KeepaliveEvent, } from './types/events';
|
|
38
|
+
export type { ChatResponse, ResponseMetadata, Insight, InsightType, InsightSeverity, } from './types/responses';
|
|
39
|
+
export type { ActionType, RejectBehavior, FieldType, ActionOption, ActionField, PendingAction, ActionResponse, } from './types/actions';
|
|
40
|
+
export type { ConversationRef } from './types/conversations';
|
|
41
|
+
export type { Provider, ProviderRequest, ResolvedHeaders, } from './types/provider';
|
|
42
|
+
export { ActionResponseBuilder } from './utils/action-response-builder';
|
|
43
|
+
export { TypedEventEmitter } from './utils/event-emitter';
|
|
44
|
+
export { isRetryableError, calculateBackoff, withRetry } from './utils/retry';
|
|
45
|
+
export type { RetryOptions } from './utils/retry';
|
|
46
|
+
export { ThaliqError, AuthError, RateLimitError, ValidationError, ServiceError, StreamError, TimeoutError, ConnectionError, } from './types/errors';
|
|
47
|
+
export type { ErrorCode } from './types/errors';
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thaliq/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Headless, type-safe, provider-agnostic SDK for Thaliq AI agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./thaliq-sdk.cjs.js",
|
|
7
|
+
"module": "./thaliq-sdk.es.js",
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./index.d.ts",
|
|
12
|
+
"import": "./thaliq-sdk.es.js",
|
|
13
|
+
"require": "./thaliq-sdk.cjs.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"author": "Thaliq <hello@thaliq.com> (https://thaliq.com)",
|
|
19
|
+
"homepage": "https://docs.thaliq.com/sdk",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/thaliq/sdk"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.0.0"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"thaliq",
|
|
29
|
+
"ai",
|
|
30
|
+
"agents",
|
|
31
|
+
"sdk",
|
|
32
|
+
"streaming",
|
|
33
|
+
"sse",
|
|
34
|
+
"llm",
|
|
35
|
+
"ai-agents",
|
|
36
|
+
"hitl",
|
|
37
|
+
"human-in-the-loop"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Logger } from '../../types/config';
|
|
2
|
+
export interface HttpClientConfig {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
timeout: number;
|
|
5
|
+
logger: Logger;
|
|
6
|
+
}
|
|
7
|
+
export interface HttpRequestOptions {
|
|
8
|
+
method: 'GET' | 'POST';
|
|
9
|
+
path: string;
|
|
10
|
+
headers: Record<string, string | undefined>;
|
|
11
|
+
body?: unknown;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
timeout: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class HttpClient {
|
|
16
|
+
private readonly baseUrl;
|
|
17
|
+
private readonly defaultTimeout;
|
|
18
|
+
private readonly logger;
|
|
19
|
+
constructor(config: HttpClientConfig);
|
|
20
|
+
/** Execute a JSON request and return the parsed response */
|
|
21
|
+
json<T>(options: HttpRequestOptions): Promise<T>;
|
|
22
|
+
/** Execute a request and return the raw Response (for SSE streaming) */
|
|
23
|
+
stream(options: HttpRequestOptions): Promise<Response>;
|
|
24
|
+
/** Low-level fetch with timeout and error wrapping */
|
|
25
|
+
private rawFetch;
|
|
26
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Provider, ProviderRequest } from '../../types/provider';
|
|
2
|
+
import { ChatResponse } from '../../types/responses';
|
|
3
|
+
import { StreamEvent } from '../../types/events';
|
|
4
|
+
import { Logger } from '../../types/config';
|
|
5
|
+
export interface ThaliqNativeProviderConfig {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
timeout: number;
|
|
8
|
+
logger: Logger;
|
|
9
|
+
}
|
|
10
|
+
export declare class ThaliqNativeProvider implements Provider {
|
|
11
|
+
readonly name = "thaliq-native";
|
|
12
|
+
private readonly http;
|
|
13
|
+
private readonly logger;
|
|
14
|
+
constructor(config: ThaliqNativeProviderConfig);
|
|
15
|
+
/** POST /api/agent/chat — non-streaming */
|
|
16
|
+
chat(request: ProviderRequest): Promise<ChatResponse>;
|
|
17
|
+
/** GET /api/agent/stream — SSE streaming */
|
|
18
|
+
stream(request: ProviderRequest): AsyncIterable<StreamEvent>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class t extends Error{constructor(t,e,s){super(t),this.name="ThaliqError",this.code=e,this.status=s}}class e extends t{constructor(t,e=401){super(t,"AUTH_ERROR",e),this.name="AuthError"}}class s extends t{constructor(t,e=60){super(t,"RATE_LIMIT",429),this.name="RateLimitError",this.retryAfter=e}}class n extends t{constructor(t){super(t,"VALIDATION_ERROR",400),this.name="ValidationError"}}class r extends t{constructor(t){super(t,"SERVICE_ERROR",503),this.name="ServiceError"}}class o extends t{constructor(t){super(t,"STREAM_ERROR"),this.name="StreamError"}}class i extends t{constructor(t="Request timed out"){super(t,"TIMEOUT_ERROR"),this.name="TimeoutError"}}class a extends t{constructor(t="Connection failed"){super(t,"CONNECTION_ERROR"),this.name="ConnectionError"}}function c(o,i,a){switch(o){case 400:return new n(i);case 401:case 403:return new e(i,o);case 429:{const t=a?.get("retry-after");return new s(i,t?parseInt(t,10):60)}case 503:return new r(i);default:return new t(i,"UNKNOWN_ERROR",o)}}class l{constructor(t,e,s,n){this._conversationId=null,this._status="streaming",this._pendingAction=null,this._consumed=!1,this._source=t,this._abortController=e,this._onEvent=s,this._onComplete=n}get conversationId(){return this._conversationId}get status(){return this._status}get pendingAction(){return this._pendingAction}abort(){this._status="aborted",this._abortController.abort()}async*[Symbol.asyncIterator](){if(this._consumed)throw new o("AgentStream can only be consumed once");this._consumed=!0;try{for await(const t of this._source)"meta"===t.type&&(this._conversationId=t.conversationId),"response.completed"!==t.type||this._conversationId||(this._conversationId=t.conversationId),"action"===t.type&&(this._pendingAction=t.action,this._status="awaiting_action"),"response.completed"===t.type&&(this._status="completed"),"error"===t.type&&(this._status="error"),this._onEvent&&this._onEvent(t),yield t;"streaming"===this._status&&(this._status=this._pendingAction?"awaiting_action":"completed")}finally{this._onComplete&&this._onComplete(this._conversationId,this._status)}}async finalResponse(){let t,e,s="",n="",r=[];for await(const a of this)switch(a.type){case"meta":n=a.conversationId;break;case"content.delta":s+=a.delta;break;case"action":e=a.action;break;case"response.completed":s=a.message,n=a.conversationId,r=a.insights,t=a.metadata;break;case"error":throw new o(a.message)}const i={message:s,conversationId:n,insights:r,metadata:t??{conversationId:n,model:"unknown",promptTokens:0,completionTokens:0,processingTimeMs:0,generatedAt:(new Date).toISOString()}};return e&&(i.action=e),i}async text(){let t="";for await(const e of this){if("content.delta"===e.type&&(t+=e.delta),"response.completed"===e.type)return e.message;if("error"===e.type)throw new o(e.message)}return t}}function h(o){return!(o instanceof t)||!(o instanceof e)&&(!(o instanceof n)&&(!(o instanceof s)&&(o instanceof r||(o instanceof a||(o instanceof i||!!(o.status&&o.status>=500))))))}function d(t,e,s){const n=e*Math.pow(2,t),r=Math.min(n,s),o=r*Math.random()*.5;return Math.floor(r+o)}function u(t){return new Promise(e=>setTimeout(e,t))}async function p(e,s){const{config:n,logger:r,onRetry:o,signal:i}=s;let a;for(let l=0;l<=n.maxRetries;l++)try{return await e()}catch(c){if(a=c,l>=n.maxRetries)break;if(!h(c))break;if(i?.aborted)break;const e=d(l,n.baseDelay,n.maxDelay);r.warn(`Retry ${l+1}/${n.maxRetries} after ${e}ms`,c instanceof Error?c.message:c),o&&c instanceof t&&o(l+1,c,e),await u(e)}throw a}class g{constructor(t,e,s,n,r){this.config=t,this.provider=e,this.getIdentity=s,this.emitter=n,this.conversationManager=r}async chat(t,e){const s=this.buildRequest(t,e),n=await p(()=>this.provider.chat(s),{config:this.config.retry,logger:this.config.logger,signal:e?.signal,onRetry:(t,e,s)=>{this.emitter.emit("retry",t,e,s)}});return n.conversationId&&this.conversationManager.setActive(n.conversationId),n}stream(e,s){const n=new AbortController,r=this.buildRequest(e,s,n.signal),o=this.provider.stream(r);this.emitter.emit("stream.start",s?.conversationId??null);return new l(o,n,e=>{if("meta"===e.type&&e.conversationId&&this.conversationManager.setActive(e.conversationId),"error"===e.type){const s=new t(e.message,"STREAM_ERROR");this.emitter.emit("error",s)}s?.onEvent&&s.onEvent(e)},t=>{this.emitter.emit("stream.end",t)})}respondToAction(t){return this.stream(t.originalMessage,{conversationId:t.conversationId,agentType:t.agentType,actionResponse:t.actionResponse,onEvent:t.onEvent,signal:t.signal})}buildRequest(t,e,s){const n=this.getIdentity(),r=e?.signal??s;return{message:t,conversationId:e?.conversationId,agentType:e?.agentType,actionResponse:e?.actionResponse,headers:this.resolveHeaders(n),signal:r,timeout:this.config.timeout}}resolveHeaders(t){const e={"x-api-key":this.config.apiKey,"x-integration-type":this.config.integrationType};t&&(t.userId&&(e["x-user-id"]=t.userId),t.participantId&&(e["x-participant-id"]=t.participantId),t.token&&(e.authorization=`Bearer ${t.token}`),t.mcpTokens&&Object.keys(t.mcpTokens).length>0&&(e["x-mcp-tokens"]=JSON.stringify(t.mcpTokens)));for(const[s,n]of Object.entries(this.config.defaultHeaders))e[s]=n;return e}}class m{constructor(){this._active=null,this._history=[]}create(){const t={id:"undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,t=>{const e=16*Math.random()|0;return("x"===t?e:3&e|8).toString(16)}),createdAt:new Date};return this._history.push(t),t}get active(){return this._active}get history(){return this._history}setActive(t){const e=this._history.find(e=>e.id===t);if(e)this._active=e;else{const e={id:t,createdAt:new Date};this._history.push(e),this._active=e}}clear(){this._active=null,this._history.length=0}}class f{constructor(t){this.baseUrl=t.baseUrl.replace(/\/+$/,""),this.defaultTimeout=t.timeout,this.logger=t.logger}async json(t){const e=await this.rawFetch(t);if(!e.ok){const t=await e.text().catch(()=>"Unknown error");let s;try{const e=JSON.parse(t);s=e.message??e.error??t}catch{s=t}throw c(e.status,s,e.headers)}return e.json()}async stream(t){const e=await this.rawFetch(t);if(!e.ok){const t=await e.text().catch(()=>"Unknown error");let s;try{const e=JSON.parse(t);s=e.message??e.error??t}catch{s=t}throw c(e.status,s,e.headers)}return e}async rawFetch(t){const e=`${this.baseUrl}${t.path}`,s=t.timeout??this.defaultTimeout,n=new AbortController;let r;t.signal&&t.signal.addEventListener("abort",()=>n.abort(t.signal?.reason),{once:!0}),r=setTimeout(()=>{n.abort(new i(`Request timed out after ${s}ms`))},s);const o={"content-type":"application/json"};for(const[i,a]of Object.entries(t.headers))void 0!==a&&(o[i]=a);this.logger.debug(`${t.method} ${e}`);try{return await fetch(e,{method:t.method,headers:o,body:t.body?JSON.stringify(t.body):void 0,signal:n.signal})}catch(c){if(c instanceof i)throw c;if(c instanceof DOMException&&"AbortError"===c.name){if(n.signal.reason instanceof i)throw n.signal.reason;throw c}throw new a(c instanceof Error?c.message:"Connection failed")}finally{void 0!==r&&clearTimeout(r)}}}function y(t){let e="",s="",n="";const r=t.split("\n");for(const o of r)o.startsWith("event:")?e=o.slice(6).trim():o.startsWith("data:")?s=o.slice(5).trim():o.startsWith("id:")&&(n=o.slice(3).trim());return e||s?{event:e,data:s,id:n}:null}function v(t){const e=t.event;try{const s=t.data?JSON.parse(t.data):{};switch(e){case"meta":return{type:"meta",conversationId:s.conversationId};case"status":return{type:"status",text:s.text};case"content.delta":return{type:"content.delta",delta:s.delta};case"tool.start":return{type:"tool.start",tool:s.tool};case"tool.end":return{type:"tool.end",tool:s.tool,success:s.success};case"action":return{type:"action",action:s};case"response.completed":return{type:"response.completed",message:s.message,conversationId:s.conversationId,insights:s.insights??[],metadata:s.metadata};case"error":return{type:"error",message:s.message};case"keepalive":return{type:"keepalive",ts:s.ts};default:return null}}catch{return null}}class x{constructor(t){this.name="thaliq-native",this.logger=t.logger,this.http=new f({baseUrl:t.baseUrl,timeout:t.timeout,logger:t.logger})}async chat(t){const e={message:t.message};return t.conversationId&&(e.conversationId=t.conversationId),t.agentType&&(e.agentType=t.agentType),t.actionResponse&&(e.actionResponse=t.actionResponse),this.http.json({method:"POST",path:"/api/agent/chat",headers:t.headers,body:e,signal:t.signal,timeout:t.timeout})}async*stream(t){const e=new URLSearchParams;e.set("q",t.message),t.conversationId&&e.set("conversationId",t.conversationId),t.agentType&&e.set("agentType",t.agentType),t.actionResponse&&e.set("actionResponse",JSON.stringify(t.actionResponse));const s=await this.http.stream({method:"GET",path:`/api/agent/stream?${e.toString()}`,headers:{...t.headers,accept:"text/event-stream"},signal:t.signal,timeout:t.timeout});if(!s.body)throw new o("Response body is null — streaming not supported");this.logger.debug("SSE stream connected"),yield*async function*(t){const e=t.getReader(),s=new TextDecoder;let n="";try{for(;;){const{done:t,value:r}=await e.read();if(t)break;n+=s.decode(r,{stream:!0});const o=n.split("\n\n");n=o.pop()??"";for(const e of o){const t=y(e);if(!t)continue;const s=v(t);s&&(yield s)}}if(n.trim()){const t=y(n);if(t){const e=v(t);e&&(yield e)}}}finally{e.releaseLock()}}(s.body)}}class w{constructor(){this._listeners=new Map}on(t,e){return this._listeners.has(t)||this._listeners.set(t,new Set),this._listeners.get(t).add(e),this}once(t,e){return e._once=!0,this.on(t,e)}off(t,e){const s=this._listeners.get(t);return s&&(s.delete(e),0===s.size&&this._listeners.delete(t)),this}emit(t,...e){const s=this._listeners.get(t);if(s){for(const t of s)t(...e),t._once&&s.delete(t);0===s.size&&this._listeners.delete(t)}}removeAllListeners(t){return t?this._listeners.delete(t):this._listeners.clear(),this}listenerCount(t){return this._listeners.get(t)?.size??0}}const _={debug:0,info:1,warn:2,error:3,silent:4};const R="https://api.thaliq.com";exports.ActionResponseBuilder=class{static accept(t){return{instructionId:t,accepted:!0}}static reject(t){return{instructionId:t,accepted:!1}}static select(t,e){return{instructionId:t,accepted:!0,selectedValue:e}}static submitForm(t,e){return{instructionId:t,accepted:!0,formValues:e}}},exports.AuthError=e,exports.ConnectionError=a,exports.ConversationManager=m,exports.RateLimitError=s,exports.ServiceError=r,exports.StreamError=o,exports.Thaliq=class{constructor(t){this.emitter=new w,this.identity=null,this.config=function(t){if(!t.apiKey)throw new n("apiKey is required");const e=t.logLevel??"warn",s=t.logger??function(t){const e=_[t];return{debug(t,...s){e<=_.debug&&console.debug(`[thaliq] ${t}`,...s)},info(t,...s){e<=_.info&&console.info(`[thaliq] ${t}`,...s)},warn(t,...s){e<=_.warn&&console.warn(`[thaliq] ${t}`,...s)},error(t,...s){e<=_.error&&console.error(`[thaliq] ${t}`,...s)}}}(e),r=t.maxRetries??t.retry?.maxRetries??2;return{apiKey:t.apiKey,baseUrl:(t.baseUrl??R).replace(/\/+$/,""),integrationType:t.integrationType??"sdk",timeout:t.timeout??3e4,maxRetries:r,retry:{maxRetries:r,baseDelay:t.retry?.baseDelay??500,maxDelay:t.retry?.maxDelay??1e4},logger:s,logLevel:e,defaultHeaders:t.defaultHeaders??{}}}(t),this.provider=t.provider??new x({baseUrl:this.config.baseUrl,timeout:this.config.timeout,logger:this.config.logger}),this.conversations=new m,this.agent=new g(this.config,this.provider,()=>this.identity,this.emitter,this.conversations)}on(t,e){return this.emitter.on(t,e),this}once(t,e){return this.emitter.once(t,e),this}off(t,e){return this.emitter.off(t,e),this}identify(t){if(!t.userId)throw new n("userId is required for identify()");this.identity=t,this.config.logger.info(`User identified: ${t.userId}`),this.emitter.emit("identify",t.userId)}reset(){this.identity=null,this.conversations.clear(),this.config.logger.info("SDK state reset"),this.emitter.emit("reset")}},exports.ThaliqError=t,exports.ThaliqNativeProvider=x,exports.TimeoutError=i,exports.TypedEventEmitter=w,exports.ValidationError=n,exports.calculateBackoff=d,exports.isRetryableError=h,exports.withRetry=p;
|
|
2
|
+
//# sourceMappingURL=thaliq-sdk.cjs.js.map
|