@happyrobot-ai/sdk 0.1.3 → 0.1.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/README.md CHANGED
@@ -115,12 +115,14 @@ const { workflow } = await createFromTemplate(client, {
115
115
  | `client.mcp` | `MCPResource` | MCP server management |
116
116
  | `client.billing` | `BillingResource` | Billing usage details and totals |
117
117
  | `client.apiKey` | `ApiKeyResource` | API key introspection |
118
+ | `client.artifacts` | `ArtifactsResource` | Resolve fresh artifact URLs |
118
119
  | `client.adversarialSuites` | `AdversarialSuitesResource` | Adversarial suite management and execution |
119
120
  | `client.adversarialTests` | `AdversarialTestsResource` | Adversarial test management and execution |
120
121
  | `client.northstars` | `NorthstarsResource` | Northstar quality criteria management |
121
122
  | `client.customEvals` | `CustomEvalsResource` | Custom eval management and execution |
122
123
  | `client.issues` | `IssuesResource` | Quality issue (flag) status management |
123
124
  | `client.auditRemarks` | `AuditRemarksResource` | Audit remark feedback management |
125
+ | `client.chat` | `ChatResource` | Chat session management, messaging, and file uploads |
124
126
 
125
127
  ---
126
128
 
@@ -300,6 +302,20 @@ await client.messages.createFlag("message-id", { type: "incorrect", note: "..."
300
302
 
301
303
  ---
302
304
 
305
+ ## `client.artifacts`
306
+
307
+ ```ts
308
+ const resolved = await client.artifacts.resolve({
309
+ s3_keys: ["artifacts/<media_id>/extracted_text.txt"],
310
+ });
311
+ ```
312
+
313
+ | Method | HTTP | Path | Description |
314
+ |---|---|---|---|
315
+ | `resolve(body)` | POST | `/artifacts/resolve` | Resolve fresh presigned URLs for artifacts |
316
+
317
+ ---
318
+
303
319
  ## `client.variables`
304
320
 
305
321
  Variables are scoped to a workflow.
@@ -706,6 +722,154 @@ await client.auditRemarks.deleteFeedback("audit-remark-id");
706
722
 
707
723
  ---
708
724
 
725
+ ## Chat (`client.chat` + `HappyRobotChatClient`)
726
+
727
+ Build custom chat UIs powered by your workflows. Uses a two-tier auth model:
728
+
729
+ 1. **Your server** creates a scoped client token using the API key (keeps it secret)
730
+ 2. **Your browser** uses `HappyRobotChatClient` with that token for all chat operations
731
+
732
+ ### Server-side: Create a client token
733
+
734
+ ```ts
735
+ // --- YOUR SERVER (e.g. Next.js API route, Express handler) ---
736
+ import { HappyRobotClient } from "@happyrobot-ai/sdk";
737
+
738
+ const client = new HappyRobotClient({ apiKey: process.env.HAPPYROBOT_API_KEY });
739
+
740
+ app.post("/api/chat-token", async (req, res) => {
741
+ const { token, expires_at } = await client.chat.createToken({
742
+ workflow_id: "your-workflow-id",
743
+ });
744
+ res.json({ token, expires_at });
745
+ });
746
+ ```
747
+
748
+ ### Browser-side: `HappyRobotChatClient`
749
+
750
+ ```ts
751
+ // --- YOUR BROWSER CODE (React, Vue, vanilla JS, etc.) ---
752
+ import { HappyRobotChatClient } from "@happyrobot-ai/sdk";
753
+
754
+ // 1. Get a client token from your server
755
+ const { token } = await fetch("/api/chat-token", { method: "POST" }).then(r => r.json());
756
+
757
+ // 2. Initialize the browser-side client
758
+ const chat = new HappyRobotChatClient({ token });
759
+
760
+ // 3. Create a session
761
+ const { session_id } = await chat.createSession();
762
+
763
+ // 4. Connect bidirectional WebSocket
764
+ const connection = chat.connect(session_id, {
765
+ onResponseStart: () => {
766
+ // Show typing indicator
767
+ },
768
+ onResponseChunk: (content) => {
769
+ // Append streamed text to the UI
770
+ },
771
+ onResponseEnd: (content) => {
772
+ // Full response complete — hide typing indicator
773
+ },
774
+ onSessionClosed: (event) => {
775
+ // Session ended: event.status, event.reason, event.duration
776
+ },
777
+ onTokenExpired: () => {
778
+ // Token expired — fetch a new token from your server and reconnect
779
+ },
780
+ });
781
+
782
+ // 5. Send messages through the WebSocket
783
+ const ack = await connection.sendMessage({ content: "Hello!" });
784
+ console.log(ack.message.id); // server-assigned message ID
785
+
786
+ // 6. Send messages with file attachments
787
+ const artifact = await chat.uploadFile(fileBlob, "photo.png", "image/png");
788
+ await connection.sendMessage({ content: "Here's the photo", artifacts: [artifact] });
789
+
790
+ // 7. Get history (page reload, reconnect)
791
+ const { messages } = await chat.getHistory(session_id);
792
+
793
+ // 8. Clean up
794
+ connection.close();
795
+ ```
796
+
797
+ ### Browser-side: File uploads
798
+
799
+ ```ts
800
+ // Option A: Use the convenience method (handles all 3 steps)
801
+ const artifact = await chat.uploadFile(fileBlob, "photo.png", "image/png");
802
+ await connection.sendMessage({
803
+ content: "Here's the photo",
804
+ artifacts: [artifact],
805
+ });
806
+
807
+ // Option B: Manual 3-step flow for more control
808
+ const upload = await chat.getPresignedUpload({ filename: "photo.png", mime_type: "image/png" });
809
+ await fetch(upload.upload_url, { method: "PUT", body: fileBlob, headers: { "Content-Type": "image/png" } });
810
+ await chat.completeUpload({
811
+ artifact_id: upload.artifact_id,
812
+ s3_uri: upload.s3_uri,
813
+ filename: "photo.png",
814
+ mime_type: "image/png",
815
+ size_bytes: fileBlob.size,
816
+ });
817
+ await connection.sendMessage({
818
+ content: "Here's the photo",
819
+ artifacts: [{ media_id: upload.artifact_id, mime_type: "image/png", filename: "photo.png", size_bytes: fileBlob.size }],
820
+ });
821
+ ```
822
+
823
+ ### `HappyRobotClient` (server-side)
824
+
825
+ | Method | Description |
826
+ |---|---|
827
+ | `client.chat.createToken({ workflow_id, env? })` | Create a scoped client token (1 hour expiry) |
828
+
829
+ ### `HappyRobotChatClient` (browser-side)
830
+
831
+ | Method | Description |
832
+ |---|---|
833
+ | `chat.createSession()` | Create a new chat session |
834
+ | `chat.connect(sessionId, handlers)` | Open bidirectional WebSocket — returns `ChatConnection` |
835
+ | `chat.sendMessage(sessionId, { content, artifacts? })` | Send a user message via HTTP (fallback if WS unavailable) |
836
+ | `chat.getHistory(sessionId)` | Get message history |
837
+ | `chat.uploadFile(file, filename, mimeType)` | Upload a file (convenience method) |
838
+ | `chat.getPresignedUpload({ filename, mime_type })` | Get presigned S3 upload URL |
839
+ | `chat.completeUpload({ artifact_id, s3_uri, ... })` | Register uploaded artifact |
840
+
841
+ ### `ChatConnection`
842
+
843
+ | Method | Description |
844
+ |---|---|
845
+ | `connection.sendMessage({ content, artifacts? })` | Send a message via WebSocket. Returns a `Promise` that resolves with the server ack |
846
+ | `connection.close()` | Close the WebSocket connection |
847
+ | `connection.ws` | The underlying `WebSocket` instance |
848
+
849
+ ### WebSocket Events
850
+
851
+ **Server → Client:**
852
+
853
+ | Event | Fields | Description |
854
+ |---|---|---|
855
+ | `connected` | `session_id` | Connection established |
856
+ | `response-start` | `content` | AI started generating a response |
857
+ | `response-chunk` | `content` | Partial response text |
858
+ | `response-end` | `content` | Complete response text |
859
+ | `session-closed` | `session_id`, `status`, `reason`, `duration`, `timestamp` | Session ended |
860
+ | `token-expired` | — | JWT expired — reconnect with a fresh token |
861
+ | `message-ack` | `id`, `message` | Server confirmed the message was sent |
862
+ | `message-error` | `id`, `error` | Server failed to send the message |
863
+ | `heartbeat` | — | Keep-alive (every 15s) |
864
+
865
+ **Client → Server:**
866
+
867
+ | Event | Fields | Description |
868
+ |---|---|---|
869
+ | `message` | `content`, `artifacts?`, `id?` | Send a user message. `id` is echoed back in ack/error |
870
+
871
+ ---
872
+
709
873
  ## Pagination
710
874
 
711
875
  List methods that are paginated return a `PaginatedResponse<T>`:
@@ -0,0 +1,106 @@
1
+ /**
2
+ * HappyRobotChatClient — lightweight browser-side client for chat sessions.
3
+ *
4
+ * Initialized with a scoped client token (created server-side via
5
+ * `client.chat.createToken()`). Does NOT require an API key.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { HappyRobotChatClient } from "@happyrobot-ai/sdk";
10
+ *
11
+ * // Token comes from your server
12
+ * const chat = new HappyRobotChatClient({ token });
13
+ *
14
+ * const { session_id } = await chat.createSession();
15
+ *
16
+ * chat.connect(session_id, {
17
+ * onResponseStart: () => { },
18
+ * onResponseChunk: (content) => { },
19
+ * onResponseEnd: (content) => { },
20
+ * onSessionClosed: (event) => { },
21
+ * });
22
+ *
23
+ * await chat.sendMessage(session_id, { content: "Hello!" });
24
+ * ```
25
+ */
26
+ import type { ChatClientConfig, CreateChatSessionResponse, SendChatMessageBody, SendChatMessageResponse, ChatHistoryResponse, PresignedUploadQuery, PresignedUploadResponse, CompleteUploadBody, CompleteUploadResponse, ChatWsSessionClosedEvent, ChatWsMessageAckEvent } from "./types/chat.types";
27
+ export interface ChatConnectionHandlers {
28
+ /** AI started generating a response. */
29
+ onResponseStart?: () => void;
30
+ /** Partial response text chunk. */
31
+ onResponseChunk?: (content: string) => void;
32
+ /** Complete response text. */
33
+ onResponseEnd?: (content: string) => void;
34
+ /** Session was closed by the server. */
35
+ onSessionClosed?: (event: ChatWsSessionClosedEvent) => void;
36
+ /** Connection established. */
37
+ onConnected?: (sessionId: string) => void;
38
+ /** Token expired — reconnect with a fresh token to continue. */
39
+ onTokenExpired?: () => void;
40
+ /** WebSocket error. */
41
+ onError?: (error: Event) => void;
42
+ /** WebSocket closed. */
43
+ onClose?: (event: {
44
+ code: number;
45
+ reason: string;
46
+ }) => void;
47
+ }
48
+ export interface ChatConnection {
49
+ /** Close the WebSocket connection only (no server-side cleanup). */
50
+ close(): void;
51
+ /**
52
+ * End the session on the server (stops the AI agent) and close the WebSocket.
53
+ * Use this when the user is done with the conversation.
54
+ */
55
+ endSession(): Promise<void>;
56
+ /** Send a message through the WebSocket. Returns the ack from the server. */
57
+ sendMessage(body: SendChatMessageBody): Promise<ChatWsMessageAckEvent>;
58
+ /** The underlying WebSocket instance. */
59
+ readonly ws: WebSocket;
60
+ }
61
+ export declare class HappyRobotChatClient {
62
+ private readonly token;
63
+ private readonly baseUrl;
64
+ private readonly fetchFn;
65
+ constructor(config: ChatClientConfig);
66
+ /** Create a new chat session. */
67
+ createSession(): Promise<CreateChatSessionResponse>;
68
+ /** Send a user message to a chat session. */
69
+ sendMessage(sessionId: string, body: SendChatMessageBody): Promise<SendChatMessageResponse>;
70
+ /** Get message history for a chat session. */
71
+ getHistory(sessionId: string): Promise<ChatHistoryResponse>;
72
+ /**
73
+ * Get a presigned S3 URL for file upload.
74
+ *
75
+ * Upload flow:
76
+ * 1. `getPresignedUpload()` — get the upload URL
77
+ * 2. `PUT` the file directly to `upload_url`
78
+ * 3. `completeUpload()` — register the artifact
79
+ * 4. Include the artifact in `sendMessage()` via the `artifacts` array
80
+ */
81
+ getPresignedUpload(query: PresignedUploadQuery): Promise<PresignedUploadResponse>;
82
+ /** Register an uploaded artifact after direct S3 upload. */
83
+ completeUpload(body: CompleteUploadBody): Promise<CompleteUploadResponse>;
84
+ /**
85
+ * Upload a file and return the artifact metadata ready for `sendMessage()`.
86
+ *
87
+ * Convenience method that combines `getPresignedUpload()`, S3 upload, and
88
+ * `completeUpload()` into a single call.
89
+ */
90
+ uploadFile(file: Blob, filename: string, mimeType: string): Promise<{
91
+ media_id: string;
92
+ mime_type: string;
93
+ filename: string;
94
+ size_bytes: number;
95
+ }>;
96
+ /**
97
+ * Connect a bidirectional WebSocket for sending messages and receiving
98
+ * real-time AI response streaming.
99
+ *
100
+ * Returns a `ChatConnection` with `sendMessage()` and `close()` methods.
101
+ * Pass event handlers to react to streaming chunks, completed responses,
102
+ * and session lifecycle events.
103
+ */
104
+ connect(sessionId: string, handlers?: ChatConnectionHandlers): ChatConnection;
105
+ private request;
106
+ }
package/chat-client.js ADDED
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ /**
3
+ * HappyRobotChatClient — lightweight browser-side client for chat sessions.
4
+ *
5
+ * Initialized with a scoped client token (created server-side via
6
+ * `client.chat.createToken()`). Does NOT require an API key.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { HappyRobotChatClient } from "@happyrobot-ai/sdk";
11
+ *
12
+ * // Token comes from your server
13
+ * const chat = new HappyRobotChatClient({ token });
14
+ *
15
+ * const { session_id } = await chat.createSession();
16
+ *
17
+ * chat.connect(session_id, {
18
+ * onResponseStart: () => { },
19
+ * onResponseChunk: (content) => { },
20
+ * onResponseEnd: (content) => { },
21
+ * onSessionClosed: (event) => { },
22
+ * });
23
+ *
24
+ * await chat.sendMessage(session_id, { content: "Hello!" });
25
+ * ```
26
+ */
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.HappyRobotChatClient = void 0;
29
+ const DEFAULT_BASE_URL = "https://platform.happyrobot.ai/api/v2";
30
+ class HappyRobotChatClient {
31
+ token;
32
+ baseUrl;
33
+ fetchFn;
34
+ constructor(config) {
35
+ if (!config.token) {
36
+ throw new Error("token is required");
37
+ }
38
+ this.token = config.token;
39
+ const customBaseUrl = config.baseUrl;
40
+ const isTesting = typeof process !== "undefined" && process.env?.HR_TESTING === "true";
41
+ if (customBaseUrl && !isTesting) {
42
+ throw new Error("baseUrl can not be overridden");
43
+ }
44
+ this.baseUrl = (customBaseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
45
+ this.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);
46
+ }
47
+ /** Create a new chat session. */
48
+ async createSession() {
49
+ return this.request("POST", "/chat/sessions");
50
+ }
51
+ /** Send a user message to a chat session. */
52
+ async sendMessage(sessionId, body) {
53
+ return this.request("POST", `/chat/sessions/${enc(sessionId)}/messages`, body);
54
+ }
55
+ /** Get message history for a chat session. */
56
+ async getHistory(sessionId) {
57
+ return this.request("GET", `/chat/sessions/${enc(sessionId)}/history`);
58
+ }
59
+ /**
60
+ * Get a presigned S3 URL for file upload.
61
+ *
62
+ * Upload flow:
63
+ * 1. `getPresignedUpload()` — get the upload URL
64
+ * 2. `PUT` the file directly to `upload_url`
65
+ * 3. `completeUpload()` — register the artifact
66
+ * 4. Include the artifact in `sendMessage()` via the `artifacts` array
67
+ */
68
+ async getPresignedUpload(query) {
69
+ const params = new URLSearchParams({
70
+ filename: query.filename,
71
+ mime_type: query.mime_type,
72
+ });
73
+ return this.request("GET", `/chat/upload/presigned?${params.toString()}`);
74
+ }
75
+ /** Register an uploaded artifact after direct S3 upload. */
76
+ async completeUpload(body) {
77
+ return this.request("POST", "/chat/upload/complete", body);
78
+ }
79
+ /**
80
+ * Upload a file and return the artifact metadata ready for `sendMessage()`.
81
+ *
82
+ * Convenience method that combines `getPresignedUpload()`, S3 upload, and
83
+ * `completeUpload()` into a single call.
84
+ */
85
+ async uploadFile(file, filename, mimeType) {
86
+ const upload = await this.getPresignedUpload({
87
+ filename,
88
+ mime_type: mimeType,
89
+ });
90
+ await this.fetchFn(upload.upload_url, {
91
+ method: "PUT",
92
+ body: file,
93
+ headers: { "Content-Type": mimeType },
94
+ });
95
+ const completed = await this.completeUpload({
96
+ artifact_id: upload.artifact_id,
97
+ s3_uri: upload.s3_uri,
98
+ filename,
99
+ mime_type: mimeType,
100
+ size_bytes: file.size,
101
+ });
102
+ return {
103
+ media_id: completed.artifact_id,
104
+ mime_type: mimeType,
105
+ filename,
106
+ size_bytes: file.size,
107
+ };
108
+ }
109
+ /**
110
+ * Connect a bidirectional WebSocket for sending messages and receiving
111
+ * real-time AI response streaming.
112
+ *
113
+ * Returns a `ChatConnection` with `sendMessage()` and `close()` methods.
114
+ * Pass event handlers to react to streaming chunks, completed responses,
115
+ * and session lifecycle events.
116
+ */
117
+ connect(sessionId, handlers = {}) {
118
+ const wsBase = this.baseUrl
119
+ .replace(/^http:/, "ws:")
120
+ .replace(/^https:/, "wss:");
121
+ const url = `${wsBase}/chat/sessions/${enc(sessionId)}/ws?token=${enc(this.token)}`;
122
+ const ws = new WebSocket(url);
123
+ // Pending message-ack/message-error callbacks keyed by client-generated id.
124
+ const pending = new Map();
125
+ ws.onmessage = (event) => {
126
+ try {
127
+ const data = JSON.parse(event.data);
128
+ switch (data.type) {
129
+ case "connected":
130
+ handlers.onConnected?.(data.session_id);
131
+ break;
132
+ case "response-start":
133
+ handlers.onResponseStart?.();
134
+ break;
135
+ case "response-chunk":
136
+ handlers.onResponseChunk?.(data.content);
137
+ break;
138
+ case "response-end":
139
+ handlers.onResponseEnd?.(data.content);
140
+ break;
141
+ case "session-closed":
142
+ handlers.onSessionClosed?.(data);
143
+ break;
144
+ case "token-expired":
145
+ handlers.onTokenExpired?.();
146
+ break;
147
+ case "message-ack": {
148
+ const id = data.id;
149
+ if (id && pending.has(id)) {
150
+ pending.get(id).resolve(data);
151
+ pending.delete(id);
152
+ }
153
+ break;
154
+ }
155
+ case "message-error": {
156
+ const id = data.id;
157
+ if (id && pending.has(id)) {
158
+ pending.get(id).reject(new Error(data.error));
159
+ pending.delete(id);
160
+ }
161
+ break;
162
+ }
163
+ case "heartbeat":
164
+ break;
165
+ }
166
+ }
167
+ catch {
168
+ // Ignore malformed messages
169
+ }
170
+ };
171
+ ws.onerror = (event) => {
172
+ handlers.onError?.(event);
173
+ };
174
+ ws.onclose = (event) => {
175
+ // Reject all pending messages on close
176
+ for (const [, { reject }] of pending) {
177
+ reject(new Error("WebSocket closed"));
178
+ }
179
+ pending.clear();
180
+ handlers.onClose?.(event);
181
+ };
182
+ const sendMessage = (body) => {
183
+ return new Promise((resolve, reject) => {
184
+ if (ws.readyState !== WebSocket.OPEN) {
185
+ reject(new Error("WebSocket is not open"));
186
+ return;
187
+ }
188
+ const id = crypto.randomUUID();
189
+ pending.set(id, { resolve, reject });
190
+ ws.send(JSON.stringify({
191
+ type: "message",
192
+ id,
193
+ content: body.content,
194
+ ...(body.artifacts && body.artifacts.length > 0
195
+ ? { artifacts: body.artifacts }
196
+ : {}),
197
+ }));
198
+ });
199
+ };
200
+ const endSession = async () => {
201
+ await this.request("POST", `/chat/sessions/${enc(sessionId)}/close`);
202
+ ws.close();
203
+ };
204
+ return {
205
+ close: () => ws.close(),
206
+ endSession,
207
+ sendMessage,
208
+ get ws() {
209
+ return ws;
210
+ },
211
+ };
212
+ }
213
+ // ── Internal ──
214
+ async request(method, path, body) {
215
+ const url = `${this.baseUrl}${path}`;
216
+ const headers = {
217
+ Authorization: `Bearer ${this.token}`,
218
+ Accept: "application/json",
219
+ };
220
+ if (body !== undefined) {
221
+ headers["Content-Type"] = "application/json";
222
+ }
223
+ const response = await this.fetchFn(url, {
224
+ method,
225
+ headers,
226
+ body: body !== undefined ? JSON.stringify(body) : undefined,
227
+ });
228
+ if (!response.ok) {
229
+ const errorBody = await response.text().catch(() => "");
230
+ let parsed = {};
231
+ try {
232
+ parsed = JSON.parse(errorBody);
233
+ }
234
+ catch {
235
+ // ignore
236
+ }
237
+ throw new Error(parsed.error ||
238
+ parsed.message ||
239
+ `Request failed with status ${response.status}`);
240
+ }
241
+ const text = await response.text();
242
+ if (!text)
243
+ return undefined;
244
+ return JSON.parse(text);
245
+ }
246
+ }
247
+ exports.HappyRobotChatClient = HappyRobotChatClient;
248
+ function enc(s) {
249
+ return encodeURIComponent(s);
250
+ }
251
+ //# sourceMappingURL=chat-client.js.map
@@ -0,0 +1,3 @@
1
+ // ESM wrapper — re-exports CJS module
2
+ export { default } from "./chat-client.js";
3
+ export * from "./chat-client.js";
package/client.d.ts CHANGED
@@ -32,6 +32,8 @@ import { NorthstarsResource } from "./resources/northstars";
32
32
  import { CustomEvalsResource } from "./resources/custom-evals";
33
33
  import { IssuesResource } from "./resources/issues";
34
34
  import { AuditRemarksResource } from "./resources/audit-remarks";
35
+ import { ChatResource } from "./resources/chat";
36
+ import { ArtifactsResource } from "./resources/artifacts";
35
37
  export declare class HappyRobotClient {
36
38
  private readonly http;
37
39
  /** Workflow CRUD, publishing, runs, and templates. */
@@ -78,5 +80,9 @@ export declare class HappyRobotClient {
78
80
  readonly issues: IssuesResource;
79
81
  /** Audit remark feedback management. */
80
82
  readonly auditRemarks: AuditRemarksResource;
83
+ /** Chat session management and messaging. */
84
+ readonly chat: ChatResource;
85
+ /** Artifact URL resolution. */
86
+ readonly artifacts: ArtifactsResource;
81
87
  constructor(config: ClientConfig);
82
88
  }
package/client.js CHANGED
@@ -35,6 +35,8 @@ const northstars_1 = require("./resources/northstars");
35
35
  const custom_evals_1 = require("./resources/custom-evals");
36
36
  const issues_1 = require("./resources/issues");
37
37
  const audit_remarks_1 = require("./resources/audit-remarks");
38
+ const chat_1 = require("./resources/chat");
39
+ const artifacts_1 = require("./resources/artifacts");
38
40
  class HappyRobotClient {
39
41
  http;
40
42
  /** Workflow CRUD, publishing, runs, and templates. */
@@ -81,6 +83,10 @@ class HappyRobotClient {
81
83
  issues;
82
84
  /** Audit remark feedback management. */
83
85
  auditRemarks;
86
+ /** Chat session management and messaging. */
87
+ chat;
88
+ /** Artifact URL resolution. */
89
+ artifacts;
84
90
  constructor(config) {
85
91
  this.http = new http_1.HttpClient(config);
86
92
  this.workflows = new workflows_1.WorkflowsResource(this.http);
@@ -105,6 +111,8 @@ class HappyRobotClient {
105
111
  this.customEvals = new custom_evals_1.CustomEvalsResource(this.http);
106
112
  this.issues = new issues_1.IssuesResource(this.http);
107
113
  this.auditRemarks = new audit_remarks_1.AuditRemarksResource(this.http);
114
+ this.chat = new chat_1.ChatResource(this.http);
115
+ this.artifacts = new artifacts_1.ArtifactsResource(this.http);
108
116
  }
109
117
  }
110
118
  exports.HappyRobotClient = HappyRobotClient;
package/core/http.js CHANGED
@@ -22,8 +22,9 @@ class HttpClient {
22
22
  }
23
23
  this.apiKey = config.apiKey;
24
24
  const customBaseUrl = config.baseUrl;
25
- if (customBaseUrl && !config.apiKey.startsWith("sk_test_")) {
26
- throw new Error("baseUrl can only be overridden with a test API key (sk_test_...)");
25
+ const isTesting = typeof process !== "undefined" && process.env?.HR_TESTING === "true";
26
+ if (customBaseUrl && !isTesting) {
27
+ throw new Error("baseUrl can not be overridden");
27
28
  }
28
29
  this.baseUrl = (customBaseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
29
30
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
package/index.d.ts CHANGED
@@ -22,6 +22,8 @@
22
22
  * ```
23
23
  */
24
24
  export { HappyRobotClient } from "./client";
25
+ export { HappyRobotChatClient } from "./chat-client";
26
+ export type { ChatConnectionHandlers, ChatConnection } from "./chat-client";
25
27
  export type { ClientConfig, RequestConfig, PaginatedResponse, PaginationMetadata, PaginationQuery } from "./core/types";
26
28
  export { HappyRobotError, ApiError, AuthenticationError, NotFoundError, ValidationError, RateLimitError, TimeoutError, NetworkError, } from "./core/errors";
27
29
  export type { ApiErrorBody } from "./core/errors";
@@ -51,6 +53,8 @@ export { NorthstarsResource } from "./resources/northstars";
51
53
  export { CustomEvalsResource } from "./resources/custom-evals";
52
54
  export { IssuesResource } from "./resources/issues";
53
55
  export { AuditRemarksResource } from "./resources/audit-remarks";
56
+ export { ChatResource } from "./resources/chat";
57
+ export { ArtifactsResource } from "./resources/artifacts";
54
58
  export type * from "./types/workflows.types";
55
59
  export type * from "./types/versions.types";
56
60
  export type * from "./types/nodes.types";
@@ -71,3 +75,5 @@ export type * from "./types/northstars.types";
71
75
  export type * from "./types/custom-evals.types";
72
76
  export type * from "./types/issues.types";
73
77
  export type * from "./types/audit-remarks.types";
78
+ export type * from "./types/chat.types";
79
+ export type * from "./types/artifacts.types";
package/index.js CHANGED
@@ -23,10 +23,12 @@
23
23
  * ```
24
24
  */
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.AuditRemarksResource = exports.IssuesResource = exports.CustomEvalsResource = exports.NorthstarsResource = exports.AdversarialTestsResource = exports.AdversarialSuitesResource = exports.ApiKeyResource = exports.BillingResource = exports.MCPResource = exports.WorkflowFoldersResource = exports.KnowledgeBasesResource = exports.ContactsResource = exports.IntegrationsResource = exports.SipTrunksResource = exports.PhoneNumbersResource = exports.VariablesResource = exports.MessagesResource = exports.SessionsResource = exports.RunsResource = exports.NodesResource = exports.VersionsResource = exports.WorkflowsResource = exports.iterateSSE = exports.paginate = exports.NetworkError = exports.TimeoutError = exports.RateLimitError = exports.ValidationError = exports.NotFoundError = exports.AuthenticationError = exports.ApiError = exports.HappyRobotError = exports.HappyRobotClient = void 0;
27
- // ── Client ──
26
+ exports.ArtifactsResource = exports.ChatResource = exports.AuditRemarksResource = exports.IssuesResource = exports.CustomEvalsResource = exports.NorthstarsResource = exports.AdversarialTestsResource = exports.AdversarialSuitesResource = exports.ApiKeyResource = exports.BillingResource = exports.MCPResource = exports.WorkflowFoldersResource = exports.KnowledgeBasesResource = exports.ContactsResource = exports.IntegrationsResource = exports.SipTrunksResource = exports.PhoneNumbersResource = exports.VariablesResource = exports.MessagesResource = exports.SessionsResource = exports.RunsResource = exports.NodesResource = exports.VersionsResource = exports.WorkflowsResource = exports.iterateSSE = exports.paginate = exports.NetworkError = exports.TimeoutError = exports.RateLimitError = exports.ValidationError = exports.NotFoundError = exports.AuthenticationError = exports.ApiError = exports.HappyRobotError = exports.HappyRobotChatClient = exports.HappyRobotClient = void 0;
27
+ // ── Clients ──
28
28
  var client_1 = require("./client");
29
29
  Object.defineProperty(exports, "HappyRobotClient", { enumerable: true, get: function () { return client_1.HappyRobotClient; } });
30
+ var chat_client_1 = require("./chat-client");
31
+ Object.defineProperty(exports, "HappyRobotChatClient", { enumerable: true, get: function () { return chat_client_1.HappyRobotChatClient; } });
30
32
  var errors_1 = require("./core/errors");
31
33
  Object.defineProperty(exports, "HappyRobotError", { enumerable: true, get: function () { return errors_1.HappyRobotError; } });
32
34
  Object.defineProperty(exports, "ApiError", { enumerable: true, get: function () { return errors_1.ApiError; } });
@@ -85,4 +87,8 @@ var issues_1 = require("./resources/issues");
85
87
  Object.defineProperty(exports, "IssuesResource", { enumerable: true, get: function () { return issues_1.IssuesResource; } });
86
88
  var audit_remarks_1 = require("./resources/audit-remarks");
87
89
  Object.defineProperty(exports, "AuditRemarksResource", { enumerable: true, get: function () { return audit_remarks_1.AuditRemarksResource; } });
90
+ var chat_1 = require("./resources/chat");
91
+ Object.defineProperty(exports, "ChatResource", { enumerable: true, get: function () { return chat_1.ChatResource; } });
92
+ var artifacts_1 = require("./resources/artifacts");
93
+ Object.defineProperty(exports, "ArtifactsResource", { enumerable: true, get: function () { return artifacts_1.ArtifactsResource; } });
88
94
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyrobot-ai/sdk",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "TypeScript SDK for the HappyRobot Public API",
5
5
  "main": "./index.js",
6
6
  "module": "./index.mjs",
@@ -37,7 +37,8 @@
37
37
  "license": "MIT",
38
38
  "repository": {
39
39
  "type": "git",
40
- "url": "https://github.com/happyrobot-ai/app-v2"
40
+ "url": "git+https://github.com/happyrobot-ai/app-v2.git",
41
+ "directory": "packages/public_api"
41
42
  },
42
43
  "dependencies": {}
43
44
  }
@@ -0,0 +1,7 @@
1
+ import type { HttpClient } from "../core/http";
2
+ import type { ResolveArtifactsBody, ResolveArtifactsResponse } from "../types/artifacts.types";
3
+ export declare class ArtifactsResource {
4
+ private readonly http;
5
+ constructor(http: HttpClient);
6
+ resolve(body: ResolveArtifactsBody): Promise<ResolveArtifactsResponse>;
7
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ArtifactsResource = void 0;
4
+ class ArtifactsResource {
5
+ http;
6
+ constructor(http) {
7
+ this.http = http;
8
+ }
9
+ async resolve(body) {
10
+ return this.http.request({
11
+ method: "POST",
12
+ path: "/artifacts/resolve",
13
+ body,
14
+ });
15
+ }
16
+ }
17
+ exports.ArtifactsResource = ArtifactsResource;
18
+ //# sourceMappingURL=artifacts.js.map
@@ -0,0 +1,3 @@
1
+ // ESM wrapper — re-exports CJS module
2
+ export { default } from "./artifacts.js";
3
+ export * from "./artifacts.js";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Chat resource — client.chat.*
3
+ *
4
+ * Server-side only. Creates scoped client tokens that are passed to the
5
+ * browser-side `HappyRobotChatClient`.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const client = new HappyRobotClient({ apiKey: "sk_live_..." });
10
+ * const { token } = await client.chat.createToken({ workflow_id: "..." });
11
+ * // Pass `token` to the browser → new HappyRobotChatClient({ token })
12
+ * ```
13
+ */
14
+ import type { HttpClient } from "../core/http";
15
+ import type { CreateChatTokenBody, CreateChatTokenResponse } from "../types/chat.types";
16
+ export declare class ChatResource {
17
+ private readonly http;
18
+ constructor(http: HttpClient);
19
+ /**
20
+ * Create a scoped client token for browser-side chat operations.
21
+ *
22
+ * Call this from your server, then pass the returned `token` to the browser
23
+ * where it can be used to initialize `HappyRobotChatClient`.
24
+ */
25
+ createToken(body: CreateChatTokenBody): Promise<CreateChatTokenResponse>;
26
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ /**
3
+ * Chat resource — client.chat.*
4
+ *
5
+ * Server-side only. Creates scoped client tokens that are passed to the
6
+ * browser-side `HappyRobotChatClient`.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const client = new HappyRobotClient({ apiKey: "sk_live_..." });
11
+ * const { token } = await client.chat.createToken({ workflow_id: "..." });
12
+ * // Pass `token` to the browser → new HappyRobotChatClient({ token })
13
+ * ```
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.ChatResource = void 0;
17
+ class ChatResource {
18
+ http;
19
+ constructor(http) {
20
+ this.http = http;
21
+ }
22
+ /**
23
+ * Create a scoped client token for browser-side chat operations.
24
+ *
25
+ * Call this from your server, then pass the returned `token` to the browser
26
+ * where it can be used to initialize `HappyRobotChatClient`.
27
+ */
28
+ async createToken(body) {
29
+ return this.http.request({
30
+ method: "POST",
31
+ path: "/chat/tokens",
32
+ body,
33
+ });
34
+ }
35
+ }
36
+ exports.ChatResource = ChatResource;
37
+ //# sourceMappingURL=chat.js.map
@@ -0,0 +1,3 @@
1
+ // ESM wrapper — re-exports CJS module
2
+ export { default } from "./chat.js";
3
+ export * from "./chat.js";