@chaoslabs/ai-sdk 0.0.2 → 0.0.4

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/dist/blocks.js ADDED
@@ -0,0 +1,246 @@
1
+ // Chaos AI SDK - Block Utilities
2
+ //
3
+ // Helper functions for working with blocks extracted from responses.
4
+ import { extractBlocks, isTableBlock, isChartBlock, isTransactionActionBlock, isMarkdownBlock, isInteractiveBlock, } from './types.js';
5
+ // ============================================================================
6
+ // Block Extraction Utilities
7
+ // ============================================================================
8
+ /**
9
+ * Extract all table blocks from a response.
10
+ */
11
+ export function extractTableBlocks(response) {
12
+ return extractBlocks(response).filter(isTableBlock);
13
+ }
14
+ /**
15
+ * Extract all chart blocks from a response.
16
+ */
17
+ export function extractChartBlocks(response) {
18
+ return extractBlocks(response).filter(isChartBlock);
19
+ }
20
+ /**
21
+ * Extract all transaction action blocks from a response.
22
+ */
23
+ export function extractTransactionBlocks(response) {
24
+ return extractBlocks(response).filter(isTransactionActionBlock);
25
+ }
26
+ /**
27
+ * Extract all markdown blocks from a response.
28
+ */
29
+ export function extractMarkdownBlocks(response) {
30
+ return extractBlocks(response).filter(isMarkdownBlock);
31
+ }
32
+ /**
33
+ * Extract all interactive blocks from a response.
34
+ */
35
+ export function extractInteractiveBlocks(response) {
36
+ return extractBlocks(response).filter(isInteractiveBlock);
37
+ }
38
+ // ============================================================================
39
+ // Block Search Utilities
40
+ // ============================================================================
41
+ /**
42
+ * Find the first table block with a matching title.
43
+ */
44
+ export function findTableByTitle(response, title) {
45
+ return extractTableBlocks(response).find((block) => block.title.toLowerCase().includes(title.toLowerCase()));
46
+ }
47
+ /**
48
+ * Find the first chart block with a matching title.
49
+ */
50
+ export function findChartByTitle(response, title) {
51
+ return extractChartBlocks(response).find((block) => block.title.toLowerCase().includes(title.toLowerCase()));
52
+ }
53
+ /**
54
+ * Find transaction blocks containing a specific primitive type.
55
+ */
56
+ export function findTransactionsByPrimitive(response, primitiveType) {
57
+ return extractTransactionBlocks(response).filter((block) => block.primitives?.some((p) => p.primitive === primitiveType));
58
+ }
59
+ // ============================================================================
60
+ // Primitive Extraction
61
+ // ============================================================================
62
+ /**
63
+ * Extract all primitives from all transaction blocks.
64
+ */
65
+ export function extractPrimitives(response) {
66
+ const primitives = [];
67
+ for (const block of extractTransactionBlocks(response)) {
68
+ if (block.primitives) {
69
+ primitives.push(...block.primitives);
70
+ }
71
+ }
72
+ return primitives;
73
+ }
74
+ /**
75
+ * Extract primitives of a specific type.
76
+ */
77
+ export function extractPrimitivesByType(response, primitiveType) {
78
+ return extractPrimitives(response).filter((p) => p.primitive === primitiveType);
79
+ }
80
+ /**
81
+ * Get unique primitive types from a response.
82
+ */
83
+ export function getPrimitiveTypes(response) {
84
+ const types = new Set();
85
+ for (const primitive of extractPrimitives(response)) {
86
+ types.add(primitive.primitive);
87
+ }
88
+ return Array.from(types);
89
+ }
90
+ // ============================================================================
91
+ // Table Utilities
92
+ // ============================================================================
93
+ /**
94
+ * Convert a table block to an array of objects.
95
+ * Each object has keys from the table headers.
96
+ */
97
+ export function tableToObjects(table) {
98
+ const { tableHeaders, tableRows } = table;
99
+ return tableRows.map((row) => {
100
+ const obj = {};
101
+ for (let i = 0; i < tableHeaders.length; i++) {
102
+ obj[tableHeaders[i]] = row[i];
103
+ }
104
+ return obj;
105
+ });
106
+ }
107
+ /**
108
+ * Get a specific column from a table block.
109
+ */
110
+ export function getTableColumn(table, columnName) {
111
+ const columnIndex = table.tableHeaders.indexOf(columnName);
112
+ if (columnIndex === -1)
113
+ return [];
114
+ return table.tableRows.map((row) => row[columnIndex]);
115
+ }
116
+ /**
117
+ * Find a row in a table by matching a column value.
118
+ */
119
+ export function findTableRow(table, columnName, value) {
120
+ const columnIndex = table.tableHeaders.indexOf(columnName);
121
+ if (columnIndex === -1)
122
+ return undefined;
123
+ return table.tableRows.find((row) => row[columnIndex] === value);
124
+ }
125
+ /**
126
+ * Get table dimensions.
127
+ */
128
+ export function getTableDimensions(table) {
129
+ return {
130
+ columns: table.tableHeaders.length,
131
+ rows: table.tableRows.length,
132
+ };
133
+ }
134
+ // ============================================================================
135
+ // Chart Utilities
136
+ // ============================================================================
137
+ /**
138
+ * Get chart data as label-value pairs.
139
+ * Works with both segments and legacy data formats.
140
+ */
141
+ export function getChartData(chart) {
142
+ if (chart.segments) {
143
+ return chart.segments.map((s) => ({ label: s.label, value: s.value }));
144
+ }
145
+ if (chart.data) {
146
+ return chart.data.map(([label, value]) => ({ label, value }));
147
+ }
148
+ return [];
149
+ }
150
+ /**
151
+ * Get the total value of all chart segments.
152
+ */
153
+ export function getChartTotal(chart) {
154
+ return getChartData(chart).reduce((sum, item) => sum + item.value, 0);
155
+ }
156
+ /**
157
+ * Get chart segment percentages.
158
+ */
159
+ export function getChartPercentages(chart) {
160
+ const data = getChartData(chart);
161
+ const total = data.reduce((sum, item) => sum + item.value, 0);
162
+ if (total === 0)
163
+ return data.map((d) => ({ label: d.label, percentage: 0 }));
164
+ return data.map((d) => ({
165
+ label: d.label,
166
+ percentage: (d.value / total) * 100,
167
+ }));
168
+ }
169
+ // ============================================================================
170
+ // Risk Utilities
171
+ // ============================================================================
172
+ /**
173
+ * Get all warnings from transaction blocks.
174
+ */
175
+ export function getAllWarnings(response) {
176
+ const warnings = [];
177
+ for (const block of extractTransactionBlocks(response)) {
178
+ if (block.risks?.warnings) {
179
+ warnings.push(...block.risks.warnings);
180
+ }
181
+ }
182
+ return warnings;
183
+ }
184
+ /**
185
+ * Get all blockers from transaction blocks.
186
+ */
187
+ export function getAllBlockers(response) {
188
+ const blockers = [];
189
+ for (const block of extractTransactionBlocks(response)) {
190
+ if (block.risks?.blockers) {
191
+ blockers.push(...block.risks.blockers);
192
+ }
193
+ }
194
+ return blockers;
195
+ }
196
+ /**
197
+ * Get the highest risk level from all transaction blocks.
198
+ */
199
+ export function getHighestRiskLevel(response) {
200
+ const riskLevels = ['low', 'medium', 'high', 'critical'];
201
+ let highestIndex = -1;
202
+ let highestLevel;
203
+ for (const block of extractTransactionBlocks(response)) {
204
+ if (block.risks?.level) {
205
+ const index = riskLevels.indexOf(block.risks.level.toLowerCase());
206
+ if (index > highestIndex) {
207
+ highestIndex = index;
208
+ highestLevel = block.risks.level;
209
+ }
210
+ }
211
+ }
212
+ return highestLevel;
213
+ }
214
+ // ============================================================================
215
+ // Block Statistics
216
+ // ============================================================================
217
+ /**
218
+ * Count blocks by type in a response.
219
+ */
220
+ export function countBlocksByType(response) {
221
+ const counts = {
222
+ table: 0,
223
+ chart: 0,
224
+ transaction_action: 0,
225
+ markdown: 0,
226
+ interactive: 0,
227
+ };
228
+ for (const block of extractBlocks(response)) {
229
+ if (block.type in counts) {
230
+ counts[block.type]++;
231
+ }
232
+ }
233
+ return counts;
234
+ }
235
+ /**
236
+ * Check if a response contains any blocks.
237
+ */
238
+ export function hasBlocks(response) {
239
+ return extractBlocks(response).length > 0;
240
+ }
241
+ /**
242
+ * Check if a response contains a specific block type.
243
+ */
244
+ export function hasBlockType(response, type) {
245
+ return extractBlocks(response).some((block) => block.type === type);
246
+ }
package/dist/client.d.ts CHANGED
@@ -1,23 +1,33 @@
1
1
  import type { ChaosConfig, CreateResponseParams, Response } from './types.js';
2
2
  import { WALLET_MODEL, ASK_MODEL } from './request.js';
3
+ import { StreamingHttpClient } from './http-streaming.js';
3
4
  export { WALLET_MODEL, ASK_MODEL };
5
+ /**
6
+ * Extended config with useNativeHttp option
7
+ */
8
+ interface ExtendedChaosConfig extends ChaosConfig {
9
+ apiKey: string;
10
+ baseUrl: string;
11
+ timeout: number;
12
+ useNativeHttp: boolean;
13
+ }
4
14
  /**
5
15
  * The v1 responses API namespace.
6
16
  */
7
17
  declare class V1Responses {
8
18
  private config;
9
19
  private sessionId;
10
- private abortController;
11
- constructor(config: Required<ChaosConfig>);
20
+ private streamingClient;
21
+ constructor(config: ExtendedChaosConfig);
12
22
  private generateId;
13
23
  /**
14
24
  * Create a response using v1 API.
15
25
  */
16
26
  create(params: CreateResponseParams): Promise<Response>;
17
27
  /**
18
- * Create a non-streaming response.
28
+ * Create a response using native HTTP streaming.
19
29
  */
20
- private createNonStreaming;
30
+ private createWithNativeHttp;
21
31
  /**
22
32
  * Cancel the current request.
23
33
  */
@@ -32,7 +42,7 @@ declare class V1Responses {
32
42
  */
33
43
  declare class V1Chat {
34
44
  readonly responses: V1Responses;
35
- constructor(config: Required<ChaosConfig>);
45
+ constructor(config: ExtendedChaosConfig);
36
46
  }
37
47
  /**
38
48
  * Chaos AI SDK Client - V1 API.
@@ -64,6 +74,8 @@ declare class V1Chat {
64
74
  export declare class Chaos {
65
75
  readonly chat: V1Chat;
66
76
  private config;
77
+ /** Whether to use native http module for streaming */
78
+ readonly useNativeHttp: boolean;
67
79
  constructor(config: ChaosConfig);
68
80
  /**
69
81
  * Get the API key.
@@ -73,5 +85,9 @@ export declare class Chaos {
73
85
  * Get the base URL.
74
86
  */
75
87
  get baseUrl(): string;
88
+ /**
89
+ * Get a StreamingHttpClient instance for direct streaming access.
90
+ */
91
+ getStreamingClient(): StreamingHttpClient;
76
92
  }
77
93
  export default Chaos;
package/dist/client.js CHANGED
@@ -3,23 +3,37 @@
3
3
  // This client talks to the new v1 API endpoint with Bearer auth.
4
4
  import { ChaosError, ChaosTimeoutError } from './types.js';
5
5
  import { toV1WalletRequest, toV1AskRequest, buildV1Headers, buildV1Endpoint, WALLET_MODEL, ASK_MODEL, } from './request.js';
6
- import { parseV1Stream, toV1Response } from './response.js';
6
+ import { toV1Response } from './response.js';
7
+ import { parseStreamLine } from './stream.js';
8
+ import { StreamingHttpClient } from './http-streaming.js';
9
+ /**
10
+ * Build final state from a list of stream events.
11
+ */
12
+ function buildFinalStateFromEvents(events) {
13
+ const blocks = [];
14
+ for (const event of events) {
15
+ if (event.type === 'report' && event.content) {
16
+ const content = event.content;
17
+ if (content.data) {
18
+ blocks.push(content.data);
19
+ }
20
+ }
21
+ }
22
+ return { blocks };
23
+ }
7
24
  // Export model constants
8
25
  export { WALLET_MODEL, ASK_MODEL };
9
26
  // ============================================================================
10
27
  // Constants
11
28
  // ============================================================================
12
29
  const DEFAULT_TIMEOUT = 120000; // 2 minutes
13
- // ============================================================================
14
- // V1 Responses Class
15
- // ============================================================================
16
30
  /**
17
31
  * The v1 responses API namespace.
18
32
  */
19
33
  class V1Responses {
20
34
  config;
21
35
  sessionId;
22
- abortController = null;
36
+ streamingClient = null;
23
37
  constructor(config) {
24
38
  this.config = config;
25
39
  this.sessionId = this.generateId();
@@ -31,48 +45,63 @@ class V1Responses {
31
45
  * Create a response using v1 API.
32
46
  */
33
47
  async create(params) {
34
- return this.createNonStreaming(params);
48
+ // Always use native HTTP for true streaming behavior
49
+ return this.createWithNativeHttp(params);
35
50
  }
36
51
  /**
37
- * Create a non-streaming response.
52
+ * Create a response using native HTTP streaming.
38
53
  */
39
- async createNonStreaming(params) {
54
+ async createWithNativeHttp(params) {
40
55
  const responseId = this.generateId();
41
56
  const sessionId = params.metadata.session_id || this.sessionId;
42
- // Create abort controller for timeout
43
- this.abortController = new AbortController();
44
- const timeoutSignal = AbortSignal.timeout(this.config.timeout);
45
- const signal = AbortSignal.any([this.abortController.signal, timeoutSignal]);
57
+ // Create streaming client
58
+ this.streamingClient = new StreamingHttpClient({
59
+ baseUrl: this.config.baseUrl,
60
+ timeout: this.config.timeout,
61
+ });
46
62
  // Build request
47
- const endpoint = buildV1Endpoint(this.config.baseUrl);
48
63
  const headers = buildV1Headers(this.config.apiKey);
49
64
  // Check if wallet or ask mode
50
65
  const isWallet = params.model === WALLET_MODEL || params.model.includes('WALLET');
51
66
  const requestBody = isWallet
52
67
  ? toV1WalletRequest(params, sessionId)
53
68
  : toV1AskRequest(params, sessionId);
69
+ // Build endpoint path
70
+ const endpoint = buildV1Endpoint(this.config.baseUrl);
71
+ const url = new URL(endpoint);
72
+ const path = url.pathname + url.search;
54
73
  try {
55
- // Make request
56
- const response = await fetch(endpoint, {
74
+ // Collect events for final response
75
+ const events = [];
76
+ // Stream lines using native http
77
+ for await (const line of this.streamingClient.streamLines(path, {
57
78
  method: 'POST',
58
79
  headers,
59
80
  body: JSON.stringify(requestBody),
60
- signal,
61
- });
62
- if (!response.ok) {
63
- throw new ChaosError(`HTTP error: ${response.status} ${response.statusText}`, response.status);
81
+ })) {
82
+ try {
83
+ const event = JSON.parse(line);
84
+ events.push(event);
85
+ // Call stream event callback if provided
86
+ if (params.onStreamEvent) {
87
+ const msg = parseStreamLine(line);
88
+ if (msg) {
89
+ params.onStreamEvent(msg);
90
+ }
91
+ }
92
+ }
93
+ catch {
94
+ // Skip invalid JSON lines
95
+ }
64
96
  }
65
- if (!response.body) {
66
- throw new ChaosError('Response body is null');
67
- }
68
- // Parse NDJSON stream
69
- const finalState = await parseV1Stream(response.body);
97
+ // Build final state from events
98
+ const finalState = buildFinalStateFromEvents(events);
70
99
  // Convert to Response
71
100
  return toV1Response(responseId, params.model, finalState);
72
101
  }
73
102
  catch (error) {
74
- if (error instanceof Error && error.name === 'TimeoutError') {
75
- throw new ChaosTimeoutError(this.config.timeout);
103
+ if (error instanceof ChaosTimeoutError) {
104
+ throw error;
76
105
  }
77
106
  if (error instanceof ChaosError) {
78
107
  throw error;
@@ -80,15 +109,17 @@ class V1Responses {
80
109
  throw new ChaosError(error instanceof Error ? error.message : String(error));
81
110
  }
82
111
  finally {
83
- this.abortController = null;
112
+ this.streamingClient = null;
84
113
  }
85
114
  }
86
115
  /**
87
116
  * Cancel the current request.
88
117
  */
89
118
  cancel() {
90
- this.abortController?.abort();
91
- this.abortController = null;
119
+ // Cancel native HTTP streaming client
120
+ if (this.streamingClient) {
121
+ this.streamingClient.abort();
122
+ }
92
123
  }
93
124
  /**
94
125
  * Reset the session for a new conversation.
@@ -143,12 +174,18 @@ class V1Chat {
143
174
  export class Chaos {
144
175
  chat;
145
176
  config;
177
+ /** Whether to use native http module for streaming */
178
+ useNativeHttp;
146
179
  constructor(config) {
180
+ // Default useNativeHttp to true for proper streaming
181
+ const useNativeHttp = config.useNativeHttp !== false;
147
182
  this.config = {
148
183
  apiKey: config.apiKey,
149
184
  baseUrl: config.baseUrl || 'https://ai-staging.chaoslabs.co',
150
185
  timeout: config.timeout || DEFAULT_TIMEOUT,
186
+ useNativeHttp,
151
187
  };
188
+ this.useNativeHttp = useNativeHttp;
152
189
  this.chat = new V1Chat(this.config);
153
190
  }
154
191
  /**
@@ -163,5 +200,14 @@ export class Chaos {
163
200
  get baseUrl() {
164
201
  return this.config.baseUrl;
165
202
  }
203
+ /**
204
+ * Get a StreamingHttpClient instance for direct streaming access.
205
+ */
206
+ getStreamingClient() {
207
+ return new StreamingHttpClient({
208
+ baseUrl: this.config.baseUrl,
209
+ timeout: this.config.timeout,
210
+ });
211
+ }
166
212
  }
167
213
  export default Chaos;
@@ -0,0 +1,136 @@
1
+ import type { Chaos } from './client.js';
2
+ import type { InputItem, Response, RequestMetadata } from './types.js';
3
+ /**
4
+ * Options for creating a new conversation.
5
+ */
6
+ export interface ConversationOptions {
7
+ /** Model to use (defaults to WALLET_MODEL) */
8
+ model?: string;
9
+ /** Maximum number of messages to keep in history */
10
+ maxHistoryLength?: number;
11
+ /** User identifier */
12
+ userId: string;
13
+ /** Wallet identifier */
14
+ walletId: string;
15
+ /** Optional initial session ID (auto-generated if not provided) */
16
+ sessionId?: string;
17
+ }
18
+ /**
19
+ * Statistics about the conversation.
20
+ */
21
+ export interface ConversationStats {
22
+ /** Number of user turns */
23
+ userTurns: number;
24
+ /** Number of assistant turns */
25
+ assistantTurns: number;
26
+ /** Total messages in history */
27
+ totalMessages: number;
28
+ /** Session ID */
29
+ sessionId: string;
30
+ /** When the conversation started */
31
+ startedAt: Date;
32
+ /** When the last message was sent */
33
+ lastMessageAt: Date | null;
34
+ }
35
+ /**
36
+ * Manages a multi-turn conversation with history tracking.
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { Chaos, Conversation } from '@chaoslabs/ai-sdk';
41
+ *
42
+ * const chaos = new Chaos({ apiKey: 'ck-...' });
43
+ * const conversation = new Conversation(chaos, {
44
+ * userId: 'user-123',
45
+ * walletId: '0x...',
46
+ * });
47
+ *
48
+ * // Send messages - history is tracked automatically
49
+ * const response1 = await conversation.send("What's my portfolio value?");
50
+ * const response2 = await conversation.send("Which asset has the highest allocation?");
51
+ *
52
+ * // Get conversation stats
53
+ * console.log(conversation.stats);
54
+ *
55
+ * // Reset for a new conversation
56
+ * conversation.reset();
57
+ * ```
58
+ */
59
+ export declare class Conversation {
60
+ private client;
61
+ private model;
62
+ private maxHistoryLength;
63
+ private history;
64
+ private metadata;
65
+ private userTurns;
66
+ private assistantTurns;
67
+ private startedAt;
68
+ private lastMessageAt;
69
+ constructor(client: Chaos, options: ConversationOptions);
70
+ /**
71
+ * Get the current session ID.
72
+ */
73
+ get sessionId(): string;
74
+ /**
75
+ * Get the conversation history.
76
+ */
77
+ get messages(): readonly InputItem[];
78
+ /**
79
+ * Get conversation statistics.
80
+ */
81
+ get stats(): ConversationStats;
82
+ /**
83
+ * Send a message and get a response.
84
+ * Automatically manages conversation history.
85
+ */
86
+ send(message: string): Promise<Response>;
87
+ /**
88
+ * Add a user message to the history without sending.
89
+ * Useful for restoring conversation state.
90
+ */
91
+ addUserMessage(content: string): void;
92
+ /**
93
+ * Add an assistant message to the history.
94
+ * Useful for restoring conversation state.
95
+ */
96
+ addAssistantMessage(content: string): void;
97
+ /**
98
+ * Add a system message to the history.
99
+ */
100
+ addSystemMessage(content: string): void;
101
+ /**
102
+ * Reset the conversation to start fresh.
103
+ * Generates a new session ID and clears history.
104
+ */
105
+ reset(): void;
106
+ /**
107
+ * Clear the history but keep the same session ID.
108
+ */
109
+ clearHistory(): void;
110
+ /**
111
+ * Fork this conversation to create a branch.
112
+ * The new conversation has the same history but a new session ID.
113
+ */
114
+ fork(): Conversation;
115
+ /**
116
+ * Serialize the conversation to JSON for persistence.
117
+ */
118
+ toJSON(): {
119
+ sessionId: string;
120
+ history: InputItem[];
121
+ metadata: RequestMetadata;
122
+ stats: ConversationStats;
123
+ };
124
+ /**
125
+ * Restore conversation state from JSON.
126
+ */
127
+ static fromJSON(client: Chaos, data: {
128
+ sessionId: string;
129
+ history: InputItem[];
130
+ metadata: RequestMetadata;
131
+ }): Conversation;
132
+ /**
133
+ * Trim history to stay within maxHistoryLength.
134
+ */
135
+ private trimHistory;
136
+ }