ql-agent-sdk 1.0.0 → 1.0.2
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 +702 -100
- package/dist/index.cjs +15 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +100 -1
- package/dist/index.d.ts +100 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# ql-agent-sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for the **Quanton Labs** agent platform API (v1).
|
|
3
|
+
TypeScript SDK for the **Quanton Labs** agent platform API (v1). Integrate your application with 8 specialized AI agents, capabilities, workflows, and background tasks.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- **Zero runtime dependencies** — built on native `fetch` and Web Streams
|
|
6
|
+
- **Full TypeScript** — all request/response types exported
|
|
7
|
+
- **ESM + CJS** — works in Node, Bun, Deno, Next.js, and edge runtimes
|
|
8
|
+
|
|
9
|
+
> Base URL defaults to `https://app.quantonlabs.com`. Override with `baseUrl` option or the `QL_BASE_URL` environment variable.
|
|
10
|
+
|
|
11
|
+
---
|
|
6
12
|
|
|
7
13
|
## Install
|
|
8
14
|
|
|
@@ -10,20 +16,25 @@ TypeScript SDK for the **Quanton Labs** agent platform API (v1). Integrates your
|
|
|
10
16
|
npm install ql-agent-sdk
|
|
11
17
|
# or
|
|
12
18
|
pnpm add ql-agent-sdk
|
|
19
|
+
# or
|
|
20
|
+
yarn add ql-agent-sdk
|
|
13
21
|
```
|
|
14
22
|
|
|
15
|
-
Requires **Node.js 18+** (native `fetch`).
|
|
23
|
+
Requires **Node.js 18+** (native `fetch`).
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
18
28
|
|
|
19
29
|
```ts
|
|
20
30
|
import { QLClient } from "ql-agent-sdk";
|
|
21
31
|
|
|
22
32
|
const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
|
|
23
33
|
|
|
24
|
-
// Discover agents
|
|
34
|
+
// Discover available agents
|
|
25
35
|
const { agents } = await ql.agents.list();
|
|
26
36
|
console.log(agents.map((a) => a.id));
|
|
37
|
+
// → ["governing", "finance", "marketing", "inventory", "customer", "developer", "operations", "research"]
|
|
27
38
|
|
|
28
39
|
// Chat with an agent (non-streaming)
|
|
29
40
|
const reply = await ql.agents.chat({
|
|
@@ -33,163 +44,705 @@ const reply = await ql.agents.chat({
|
|
|
33
44
|
console.log(reply.content);
|
|
34
45
|
```
|
|
35
46
|
|
|
47
|
+
---
|
|
48
|
+
|
|
36
49
|
## Configuration
|
|
37
50
|
|
|
38
51
|
```ts
|
|
39
|
-
new QLClient({
|
|
40
|
-
apiKey,
|
|
41
|
-
baseUrl,
|
|
42
|
-
fetch: customFetch,
|
|
52
|
+
const ql = new QLClient({
|
|
53
|
+
apiKey, // required — your Bearer API key (ql_live_...)
|
|
54
|
+
baseUrl, // optional — defaults to QL_BASE_URL or https://app.quantonlabs.com
|
|
55
|
+
fetch: customFetch, // optional — override for tests/proxies/custom runtimes
|
|
43
56
|
});
|
|
44
57
|
```
|
|
45
58
|
|
|
46
|
-
`apiKey` is held in a private field
|
|
59
|
+
The `apiKey` is held in a private field. It is **never** returned by the client, included in error messages, or logged.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Authentication
|
|
64
|
+
|
|
65
|
+
All API calls use **Bearer token authentication** with your API key:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Authorization: Bearer ql_live_your_key_here
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Create keys in your Quanton Labs dashboard → **API Keys** page. Each key is scoped to a single workspace.
|
|
72
|
+
|
|
73
|
+
### Scope Requirements
|
|
74
|
+
|
|
75
|
+
| Scope | Grants access to |
|
|
76
|
+
|-------|-----------------|
|
|
77
|
+
| `workspace` | `ql.workspace.get()` |
|
|
78
|
+
| `agents` | `ql.agents.list()`, `ql.agents.get()` |
|
|
79
|
+
| `capabilities` | `ql.agents.capabilities()`, `ql.agents.runCapability()` |
|
|
80
|
+
| `chat` | `ql.agents.chat()`, `ql.agents.chatStream()` |
|
|
81
|
+
| `messages` | `ql.agents.history()` |
|
|
82
|
+
| `workflows` | `ql.workflows.run()`, `ql.workflows.getRun()` |
|
|
83
|
+
| `tasks` | `ql.tasks.create()`, `ql.tasks.get()` |
|
|
84
|
+
| `*` | All of the above (default for new keys) |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## API Reference
|
|
89
|
+
|
|
90
|
+
### Response Envelope
|
|
91
|
+
|
|
92
|
+
All non-streaming endpoints return a consistent envelope:
|
|
93
|
+
|
|
94
|
+
**Success:**
|
|
95
|
+
```json
|
|
96
|
+
{ "ok": true, "data": { ... } }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Error:**
|
|
100
|
+
```json
|
|
101
|
+
{ "ok": false, "error": { "code": "not_found", "message": "Agent not found: foo" } }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The SDK unwraps `data` for you and throws a typed `QLError` on errors. You never see the envelope directly.
|
|
47
105
|
|
|
48
|
-
|
|
106
|
+
---
|
|
49
107
|
|
|
50
108
|
### Workspace
|
|
109
|
+
|
|
110
|
+
#### `ql.workspace.get()` → `Promise<Workspace>`
|
|
111
|
+
|
|
112
|
+
**Scope:** `workspace`
|
|
113
|
+
|
|
114
|
+
Returns the workspace bound to your API key.
|
|
115
|
+
|
|
116
|
+
**Response (200):**
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"_id": "6adf926f6f87c9ab86f32c0e",
|
|
120
|
+
"name": "Robin",
|
|
121
|
+
"plan": "standard",
|
|
122
|
+
"createdAt": "2026-10-26T17:48:31.038Z",
|
|
123
|
+
"updatedAt": "2026-10-27T11:56:44.074Z"
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**TypeScript:**
|
|
51
128
|
```ts
|
|
52
|
-
const ws = await ql.workspace.get();
|
|
129
|
+
const ws = await ql.workspace.get();
|
|
130
|
+
ws._id; // string
|
|
131
|
+
ws.name; // string
|
|
132
|
+
ws.plan; // "standard" | "premium" | "enterprise" | undefined
|
|
133
|
+
ws.createdAt; // string (ISO 8601)
|
|
53
134
|
```
|
|
54
135
|
|
|
136
|
+
---
|
|
137
|
+
|
|
55
138
|
### Agents
|
|
56
|
-
```ts
|
|
57
|
-
const { agents } = await ql.agents.list(); // AgentListResult
|
|
58
|
-
const detail = await ql.agents.get("finance"); // AgentDetail (agent + capabilities)
|
|
59
|
-
const caps = await ql.agents.capabilities("finance"); // CapabilitiesResult
|
|
60
139
|
|
|
61
|
-
|
|
140
|
+
#### `ql.agents.list()` → `Promise<AgentListResult>`
|
|
141
|
+
|
|
142
|
+
**Scope:** `agents`
|
|
143
|
+
|
|
144
|
+
Lists all 8 available agents with their capability counts.
|
|
145
|
+
|
|
146
|
+
**Response (200):**
|
|
147
|
+
```json
|
|
148
|
+
{
|
|
149
|
+
"agents": [
|
|
150
|
+
{
|
|
151
|
+
"id": "governing",
|
|
152
|
+
"name": "Governing Agent",
|
|
153
|
+
"domain": "orchestration",
|
|
154
|
+
"description": "Coordinates all other agents...",
|
|
155
|
+
"icon": "brain",
|
|
156
|
+
"tags": ["coordination", "reasoning"],
|
|
157
|
+
"capabilityCount": 20
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### `ql.agents.get(agentId)` → `Promise<AgentDetail>`
|
|
164
|
+
|
|
165
|
+
**Scope:** `agents`
|
|
166
|
+
|
|
167
|
+
Returns an agent's details and full capability list.
|
|
168
|
+
|
|
169
|
+
**Parameters:**
|
|
170
|
+
- `agentId: string` — agent slug (e.g. `"finance"`, `"governing"`) or ObjectId
|
|
171
|
+
|
|
172
|
+
**Response (200):**
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"agent": {
|
|
176
|
+
"id": "finance",
|
|
177
|
+
"name": "Finance Agent",
|
|
178
|
+
"domain": "finance",
|
|
179
|
+
"description": "Handles financial analysis...",
|
|
180
|
+
"icon": "dollar-sign",
|
|
181
|
+
"tags": ["accounting", "budgeting"]
|
|
182
|
+
},
|
|
183
|
+
"capabilities": [
|
|
184
|
+
{
|
|
185
|
+
"id": "invoice",
|
|
186
|
+
"name": "Invoice Generator",
|
|
187
|
+
"description": "Generate formatted invoices from data",
|
|
188
|
+
"category": "documents",
|
|
189
|
+
"params": [
|
|
190
|
+
{ "key": "data", "label": "Data", "kind": "textarea", "required": true }
|
|
191
|
+
],
|
|
192
|
+
"templates": [
|
|
193
|
+
{ "label": "Monthly Report", "values": { "data": "Revenue | 12000\nCosts | 4000" } }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Errors:**
|
|
201
|
+
- `404 not_found` — agent slug doesn't exist
|
|
202
|
+
|
|
203
|
+
#### `ql.agents.capabilities(agentId)` → `Promise<CapabilitiesResult>`
|
|
204
|
+
|
|
205
|
+
**Scope:** `capabilities`
|
|
206
|
+
|
|
207
|
+
Returns just the capability list for an agent (lighter than `get`).
|
|
208
|
+
|
|
209
|
+
**Response (200):**
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"agentId": "finance",
|
|
213
|
+
"capabilities": [ ... ]
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### `ql.agents.runCapability(agentId, input)` → `Promise<RunCapabilityResult>`
|
|
218
|
+
|
|
219
|
+
**Scope:** `capabilities`
|
|
220
|
+
|
|
221
|
+
Executes a specific capability (feature) on an agent.
|
|
222
|
+
|
|
223
|
+
**Parameters:**
|
|
224
|
+
- `agentId: string` — agent slug
|
|
225
|
+
- `input.capId: string` — capability ID (from the capabilities list)
|
|
226
|
+
- `input.params?: Record<string, unknown>` — capability-specific parameters
|
|
227
|
+
|
|
228
|
+
**Response (200):**
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"result": {
|
|
232
|
+
"type": "html",
|
|
233
|
+
"html": "<div class=\"invoice\">...</div>"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The `result` is a **discriminated union** — check `result.type`:
|
|
239
|
+
|
|
240
|
+
| `type` | Fields | When |
|
|
241
|
+
|--------|--------|------|
|
|
242
|
+
| `"text"` | `content: string` | Plain text output |
|
|
243
|
+
| `"json"` | `data: unknown` | Structured JSON |
|
|
244
|
+
| `"html"` | `html: string` | HTML content (renderable) |
|
|
245
|
+
| `"file"` | `url, filename, mime` | Generated file (image, PDF, etc.) |
|
|
246
|
+
|
|
247
|
+
**Example:**
|
|
248
|
+
```ts
|
|
62
249
|
const { result } = await ql.agents.runCapability("finance", {
|
|
63
250
|
capId: "invoice",
|
|
64
251
|
params: { data: "Revenue | 12000\nCosts | 4000" },
|
|
65
252
|
});
|
|
66
|
-
if (result.type === "html") render(result.html);
|
|
67
|
-
else if (result.type === "json") use(result.data);
|
|
68
|
-
else if (result.type === "text") print(result.content);
|
|
69
|
-
// result.type === "file" -> { url, filename, mime }
|
|
70
253
|
|
|
71
|
-
|
|
72
|
-
|
|
254
|
+
if (result.type === "html") {
|
|
255
|
+
document.body.innerHTML = result.html;
|
|
256
|
+
} else if (result.type === "json") {
|
|
257
|
+
console.log(result.data);
|
|
258
|
+
} else if (result.type === "text") {
|
|
259
|
+
console.log(result.content);
|
|
260
|
+
} else if (result.type === "file") {
|
|
261
|
+
window.open(result.url); // download
|
|
262
|
+
}
|
|
73
263
|
```
|
|
74
264
|
|
|
75
|
-
|
|
76
|
-
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
### Chat
|
|
77
268
|
|
|
269
|
+
#### `ql.agents.chat(input)` → `Promise<ChatResult>`
|
|
270
|
+
|
|
271
|
+
**Scope:** `chat`
|
|
272
|
+
|
|
273
|
+
Non-streaming chat with an agent. Waits for the full response.
|
|
274
|
+
|
|
275
|
+
**Parameters:**
|
|
276
|
+
- `agentId: string` — agent slug
|
|
277
|
+
- `messages: ChatMessage[]` — conversation history (newest last)
|
|
278
|
+
- `userId?: string` — optional internal user ID (defaults to workspace)
|
|
279
|
+
- `response_format?: object` — optional OpenRouter format hint
|
|
280
|
+
|
|
281
|
+
**Response (200):**
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"content": "Based on your data, Q3 revenue was $120K...",
|
|
285
|
+
"toolCalls": 2,
|
|
286
|
+
"iterations": 3,
|
|
287
|
+
"duration": "4.21s"
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
| Field | Type | Description |
|
|
292
|
+
|-------|------|-------------|
|
|
293
|
+
| `content` | `string` | The agent's full text response |
|
|
294
|
+
| `toolCalls` | `number` | How many tool calls were made during reasoning |
|
|
295
|
+
| `iterations` | `number` | Tool loop iterations |
|
|
296
|
+
| `duration` | `string` | Human-readable execution time |
|
|
297
|
+
|
|
298
|
+
**Example:**
|
|
299
|
+
```ts
|
|
300
|
+
const reply = await ql.agents.chat({
|
|
301
|
+
agentId: "finance",
|
|
302
|
+
messages: [
|
|
303
|
+
{ role: "user", content: "What was our profit margin last month?" },
|
|
304
|
+
],
|
|
305
|
+
});
|
|
306
|
+
console.log(reply.content);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
#### `ql.agents.chatStream(input, opts?)` → `AsyncIterable<ChatStreamEvent>`
|
|
312
|
+
|
|
313
|
+
**Scope:** `chat`
|
|
314
|
+
|
|
315
|
+
Streaming chat — yields incremental events as the agent responds.
|
|
316
|
+
|
|
317
|
+
**Parameters:**
|
|
318
|
+
- Same as `chat()` above
|
|
319
|
+
- `opts.signal?: AbortSignal` — abort the stream at any time
|
|
320
|
+
|
|
321
|
+
**Yielded events:**
|
|
322
|
+
```ts
|
|
323
|
+
type ChatStreamEvent =
|
|
324
|
+
| { type: "delta"; content?: string; reasoning?: string } // incremental piece
|
|
325
|
+
| { type: "done"; content: string; reasoning?: string }; // final accumulated text
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
| Event | When | Fields |
|
|
329
|
+
|-------|------|--------|
|
|
330
|
+
| `delta` | For each token/chunk from the model | `content` (text delta), `reasoning` (thinking delta, if model supports it) |
|
|
331
|
+
| `done` | Once at the end | `content` (full accumulated text), `reasoning` (full reasoning, if any) |
|
|
332
|
+
|
|
333
|
+
**Example:**
|
|
78
334
|
```ts
|
|
79
335
|
import { QLClient } from "ql-agent-sdk";
|
|
80
336
|
|
|
81
337
|
const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
|
|
82
338
|
const controller = new AbortController();
|
|
83
339
|
|
|
84
|
-
//
|
|
340
|
+
// Optional: auto-abort after 30s
|
|
85
341
|
const timer = setTimeout(() => controller.abort(), 30_000);
|
|
86
342
|
|
|
87
343
|
let full = "";
|
|
88
344
|
try {
|
|
89
345
|
for await (const ev of ql.agents.chatStream(
|
|
90
|
-
{ agentId: "governing", messages: [{ role: "user", content: "
|
|
346
|
+
{ agentId: "governing", messages: [{ role: "user", content: "Explain quantum computing" }] },
|
|
91
347
|
{ signal: controller.signal },
|
|
92
348
|
)) {
|
|
93
349
|
if (ev.type === "delta") {
|
|
94
|
-
process.stdout.write(ev.content ?? "");
|
|
350
|
+
process.stdout.write(ev.content ?? ""); // print incrementally
|
|
95
351
|
full += ev.content ?? "";
|
|
96
352
|
} else if (ev.type === "done") {
|
|
97
|
-
full = ev.content;
|
|
353
|
+
full = ev.content; // final text (replaces accumulated)
|
|
98
354
|
}
|
|
99
355
|
}
|
|
100
356
|
} catch (err) {
|
|
101
|
-
if ((err as any)?.code === "aborted") console.log("stopped by user");
|
|
357
|
+
if ((err as any)?.code === "aborted") console.log("\n[stopped by user]");
|
|
102
358
|
} finally {
|
|
103
359
|
clearTimeout(timer);
|
|
104
360
|
}
|
|
105
361
|
```
|
|
106
362
|
|
|
107
|
-
|
|
363
|
+
**SSE wire format** (if you ever need to parse it manually):
|
|
364
|
+
```
|
|
365
|
+
data: {"id":"gen-...","choices":[{"delta":{"content":"Hel"}}]}
|
|
366
|
+
|
|
367
|
+
data: {"id":"gen-...","choices":[{"delta":{"content":"lo"}}]}
|
|
368
|
+
|
|
369
|
+
data: [DONE]
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
Events are separated by blank lines. `data: [DONE]` signals the end.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
### History / Messages
|
|
377
|
+
|
|
378
|
+
#### `ql.agents.history(agentId, opts?)` → `Promise<HistoryPage>`
|
|
379
|
+
|
|
380
|
+
**Scope:** `messages`
|
|
381
|
+
|
|
382
|
+
Returns paginated conversation history for an agent (newest first).
|
|
383
|
+
|
|
384
|
+
**Parameters:**
|
|
385
|
+
- `agentId: string` — agent slug
|
|
386
|
+
- `opts.limit?: number` — page size, 1–100 (default 20)
|
|
387
|
+
- `opts.before?: string` — cursor from previous page's `nextBefore`
|
|
388
|
+
- `opts.userId?: string` — optional filter by user
|
|
389
|
+
|
|
390
|
+
**Response (200):**
|
|
391
|
+
```json
|
|
392
|
+
{
|
|
393
|
+
"agentId": "finance",
|
|
394
|
+
"limit": 20,
|
|
395
|
+
"nextBefore": "6ae0d96274f1cf895ef06e0c",
|
|
396
|
+
"items": [
|
|
397
|
+
{
|
|
398
|
+
"id": "6ae0d963...",
|
|
399
|
+
"role": "user",
|
|
400
|
+
"content": "What was our profit margin?",
|
|
401
|
+
"toolCalls": [],
|
|
402
|
+
"status": "completed",
|
|
403
|
+
"timestamp": "2026-10-27T14:32:00.000Z"
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
"id": "6ae0d962...",
|
|
407
|
+
"role": "assistant",
|
|
408
|
+
"content": "Based on your records...",
|
|
409
|
+
"reasoning": "Let me check the financial data...",
|
|
410
|
+
"toolCalls": [{ "name": "db_query", ... }],
|
|
411
|
+
"status": "completed",
|
|
412
|
+
"timestamp": "2026-10-27T14:32:05.000Z"
|
|
413
|
+
}
|
|
414
|
+
]
|
|
415
|
+
}
|
|
416
|
+
```
|
|
108
417
|
|
|
109
|
-
|
|
110
|
-
|
|
418
|
+
| Field | Description |
|
|
419
|
+
|-------|-------------|
|
|
420
|
+
| `nextBefore` | Cursor for next page. `null` when no more pages. |
|
|
421
|
+
| `items[].role` | `"user"` or `"assistant"` |
|
|
422
|
+
| `items[].content` | Message text (may be null for tool-only messages) |
|
|
423
|
+
| `items[].reasoning` | Model's thinking (if supported by the model) |
|
|
424
|
+
| `items[].toolCalls` | Array of tool invocations made during this message |
|
|
425
|
+
| `items[].status` | `"completed"`, `"processing"`, or `"failed"` |
|
|
111
426
|
|
|
427
|
+
**Walking all pages:**
|
|
112
428
|
```ts
|
|
113
|
-
const
|
|
429
|
+
const allMessages = [];
|
|
114
430
|
let before: string | undefined;
|
|
431
|
+
|
|
115
432
|
do {
|
|
116
433
|
const page = await ql.agents.history("finance", { limit: 50, before });
|
|
117
|
-
|
|
118
|
-
before = page.nextBefore ?? undefined;
|
|
434
|
+
allMessages.push(...page.items);
|
|
435
|
+
before = page.nextBefore ?? undefined; // null when exhausted
|
|
119
436
|
} while (before);
|
|
437
|
+
|
|
438
|
+
console.log(`${allMessages.length} total messages`);
|
|
120
439
|
```
|
|
121
440
|
|
|
441
|
+
---
|
|
442
|
+
|
|
122
443
|
### Workflows
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
444
|
+
|
|
445
|
+
#### `ql.workflows.run(workflowId, input?)` → `Promise<WorkflowRunStart>`
|
|
446
|
+
|
|
447
|
+
**Scope:** `workflows`
|
|
448
|
+
|
|
449
|
+
Triggers a workflow execution. Returns immediately with a run ID (HTTP 202).
|
|
450
|
+
|
|
451
|
+
**Parameters:**
|
|
452
|
+
- `workflowId: string` — the workflow's ObjectId
|
|
453
|
+
- `input?: { input?: Record<string, unknown> }` — optional input payload for the workflow
|
|
454
|
+
|
|
455
|
+
**Response (202):**
|
|
456
|
+
```json
|
|
457
|
+
{
|
|
458
|
+
"runId": "6ae101c8...",
|
|
459
|
+
"status": "running"
|
|
460
|
+
}
|
|
126
461
|
```
|
|
127
462
|
|
|
128
|
-
|
|
463
|
+
**Errors:**
|
|
464
|
+
- `400 validation_error` — invalid workflow ID format
|
|
465
|
+
- `404 not_found` — workflow doesn't exist in this workspace
|
|
466
|
+
|
|
467
|
+
#### `ql.workflows.getRun(runId)` → `Promise<WorkflowRun>`
|
|
468
|
+
|
|
469
|
+
**Scope:** `workflows`
|
|
470
|
+
|
|
471
|
+
Polls a workflow run's status.
|
|
472
|
+
|
|
473
|
+
**Response (200):**
|
|
474
|
+
```json
|
|
475
|
+
{
|
|
476
|
+
"runId": "6ae101c8...",
|
|
477
|
+
"status": "completed",
|
|
478
|
+
"output": { "result": "..." },
|
|
479
|
+
"error": null,
|
|
480
|
+
"durationMs": 4523,
|
|
481
|
+
"startedAt": "2026-10-27T14:32:00.000Z",
|
|
482
|
+
"finishedAt": "2026-10-27T14:32:04.523Z"
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
| `status` value | Meaning |
|
|
487
|
+
|----------------|---------|
|
|
488
|
+
| `"queued"` | Waiting to start |
|
|
489
|
+
| `"running"` | Currently executing |
|
|
490
|
+
| `"completed"` | Finished successfully — check `output` |
|
|
491
|
+
| `"failed"` | Execution error — check `error` |
|
|
492
|
+
| `"cancelled"` | Was cancelled |
|
|
493
|
+
|
|
494
|
+
**Polling example:**
|
|
129
495
|
```ts
|
|
130
|
-
const
|
|
131
|
-
|
|
496
|
+
const start = await ql.workflows.run("6adf929d6f87c9ab86f32c19", { input: { a: 1 } });
|
|
497
|
+
|
|
498
|
+
// Poll until done
|
|
499
|
+
let run;
|
|
500
|
+
do {
|
|
501
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
502
|
+
run = await ql.workflows.getRun(start.runId);
|
|
503
|
+
} while (run.status === "running" || run.status === "queued");
|
|
504
|
+
|
|
505
|
+
if (run.status === "completed") console.log(run.output);
|
|
506
|
+
else if (run.status === "failed") console.error(run.error);
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
### Tasks (Background)
|
|
512
|
+
|
|
513
|
+
#### `ql.tasks.create(input)` → `Promise<TaskCreated>`
|
|
514
|
+
|
|
515
|
+
**Scope:** `tasks`
|
|
516
|
+
|
|
517
|
+
Creates a background task. The task **starts executing immediately** on the server — you don't need to call another endpoint.
|
|
518
|
+
|
|
519
|
+
**Parameters:**
|
|
520
|
+
- `agentId: string` — which agent handles this task
|
|
521
|
+
- `title: string` — human-readable name
|
|
522
|
+
- `description?: string` — optional longer description
|
|
523
|
+
- `input?: unknown` — payload for the agent (string, or `{ message }`, or `{ prompt }`)
|
|
524
|
+
- `priority?: "low" | "medium" | "high" | "urgent"` — scheduling priority
|
|
525
|
+
|
|
526
|
+
**Response (201):**
|
|
527
|
+
```json
|
|
528
|
+
{
|
|
529
|
+
"taskId": "6ae101b7...",
|
|
530
|
+
"status": "queued"
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Errors:**
|
|
535
|
+
- `400 validation_error` — missing `agentId` or `title`
|
|
536
|
+
- `404 not_found` — unknown agent slug
|
|
537
|
+
|
|
538
|
+
#### `ql.tasks.get(taskId)` → `Promise<Task>`
|
|
539
|
+
|
|
540
|
+
**Scope:** `tasks`
|
|
541
|
+
|
|
542
|
+
Single poll of a task's current state.
|
|
543
|
+
|
|
544
|
+
**Response (200):**
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"taskId": "6ae101b7...",
|
|
548
|
+
"status": "completed",
|
|
549
|
+
"title": "Monthly Report",
|
|
550
|
+
"progress": {
|
|
551
|
+
"current": 100,
|
|
552
|
+
"total": 100,
|
|
553
|
+
"message": "Completed",
|
|
554
|
+
"updatedAt": "2026-10-27T19:53:23.117Z"
|
|
555
|
+
},
|
|
556
|
+
"result": "Here is your monthly report...",
|
|
557
|
+
"output": "Here is your monthly report...",
|
|
558
|
+
"error": null,
|
|
559
|
+
"createdAt": "2026-10-27T19:53:16.756Z",
|
|
560
|
+
"completedAt": "2026-10-27T19:53:23.117Z"
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
| `status` value | Meaning | Check field |
|
|
565
|
+
|----------------|---------|-------------|
|
|
566
|
+
| `"queued"` | Waiting to start | — |
|
|
567
|
+
| `"running"` | In progress | `progress.message` for current step |
|
|
568
|
+
| `"retrying"` | Failed, will retry automatically | `error` for what went wrong |
|
|
569
|
+
| `"completed"` | Done successfully | `result` / `output` for the answer |
|
|
570
|
+
| `"failed"` | Permanently failed (max retries) | `error` for the reason |
|
|
571
|
+
| `"cancelled"` | Was cancelled | — |
|
|
572
|
+
|
|
573
|
+
#### `ql.tasks.handle(taskId)` → `TaskHandle`
|
|
574
|
+
|
|
575
|
+
Returns a handle with `poll()` and `wait()` methods.
|
|
576
|
+
|
|
577
|
+
**`handle.poll(signal?)`** — single fetch, returns current `Task` snapshot.
|
|
578
|
+
|
|
579
|
+
**`handle.wait(opts?)`** — polls until terminal status with backoff.
|
|
580
|
+
|
|
581
|
+
| Option | Default | Description |
|
|
582
|
+
|--------|---------|-------------|
|
|
583
|
+
| `intervalMs` | 2000 | Initial delay between polls (ms) |
|
|
584
|
+
| `maxIntervalMs` | 15000 | Backoff cap (grows 1.5× per poll) |
|
|
585
|
+
| `timeoutMs` | undefined (wait forever) | Throw after this many ms total |
|
|
586
|
+
| `signal` | undefined | AbortSignal for cancellation |
|
|
587
|
+
| `onTick` | undefined | Callback after each poll with latest snapshot |
|
|
132
588
|
|
|
133
|
-
|
|
134
|
-
|
|
589
|
+
**Terminal states returned by `wait()`:**
|
|
590
|
+
- `task.status === "completed"` → check `task.result`
|
|
591
|
+
- `task.status === "failed"` → check `task.error`
|
|
592
|
+
- `task.status === "cancelled"` → task was cancelled server-side
|
|
135
593
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
594
|
+
**Non-terminal exits (throws QLError):**
|
|
595
|
+
- `err.code === "timeout"` → exceeded `timeoutMs`
|
|
596
|
+
- `err.code === "aborted"` → `signal` was aborted
|
|
597
|
+
|
|
598
|
+
**Example:**
|
|
599
|
+
```ts
|
|
600
|
+
const created = await ql.tasks.create({
|
|
601
|
+
agentId: "finance",
|
|
602
|
+
title: "Quarterly Report",
|
|
603
|
+
input: { message: "Generate a Q3 financial summary from our database." },
|
|
604
|
+
priority: "high",
|
|
142
605
|
});
|
|
606
|
+
|
|
607
|
+
const handle = ql.tasks.handle(created.taskId);
|
|
608
|
+
|
|
609
|
+
try {
|
|
610
|
+
const done = await handle.wait({
|
|
611
|
+
intervalMs: 2000,
|
|
612
|
+
maxIntervalMs: 10000,
|
|
613
|
+
timeoutMs: 5 * 60_000, // 5 minute timeout
|
|
614
|
+
onTick: (task) => {
|
|
615
|
+
if (task.status === "running" && task.progress?.message) {
|
|
616
|
+
process.stdout.write(`\r${task.progress.message}`);
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
if (done.status === "completed") {
|
|
622
|
+
console.log("\n✓ Done:", done.result);
|
|
623
|
+
} else if (done.status === "failed") {
|
|
624
|
+
console.error("✗ Failed:", done.error);
|
|
625
|
+
}
|
|
626
|
+
} catch (err) {
|
|
627
|
+
if ((err as any).code === "timeout") console.error("Timed out after 5 min");
|
|
628
|
+
if ((err as any).code === "aborted") console.log("Cancelled by user");
|
|
629
|
+
}
|
|
143
630
|
```
|
|
144
631
|
|
|
145
|
-
> **
|
|
632
|
+
> **Note on execution:** Tasks execute in the server's process. If the server restarts mid-execution, the task will be recovered and retried on next startup (up to `maxRetries` times). For mission-critical workloads with strict durability requirements, consider a persistent queue backend.
|
|
146
633
|
|
|
147
|
-
|
|
634
|
+
---
|
|
148
635
|
|
|
149
|
-
|
|
636
|
+
## Error Handling
|
|
637
|
+
|
|
638
|
+
All API failures throw a typed **`QLError`**:
|
|
150
639
|
|
|
151
640
|
```ts
|
|
152
641
|
import { QLClient, QLError } from "ql-agent-sdk";
|
|
153
642
|
|
|
154
643
|
try {
|
|
155
|
-
await ql.agents.chat({ ... });
|
|
644
|
+
await ql.agents.chat({ agentId: "finance", messages: [...] });
|
|
156
645
|
} catch (err) {
|
|
157
646
|
if (err instanceof QLError) {
|
|
158
|
-
err.status;
|
|
159
|
-
err.code;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
647
|
+
console.error(err.status); // HTTP status code (401, 403, 404, etc.)
|
|
648
|
+
console.error(err.code); // stable string code (see table below)
|
|
649
|
+
console.error(err.message); // human-readable (never includes API key)
|
|
650
|
+
|
|
651
|
+
// Convenience booleans:
|
|
652
|
+
err.isAuthError; // true for 401/403
|
|
653
|
+
err.isAborted; // true for abort
|
|
654
|
+
err.isTimeout; // true for wait() timeout
|
|
655
|
+
err.isNetworkError; // true for connection failures
|
|
163
656
|
}
|
|
164
657
|
}
|
|
165
658
|
```
|
|
166
659
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
|
170
|
-
|
|
171
|
-
|
|
|
172
|
-
|
|
|
173
|
-
|
|
|
174
|
-
|
|
|
660
|
+
### Error Code Reference
|
|
661
|
+
|
|
662
|
+
| HTTP Status | `code` | When it happens |
|
|
663
|
+
|-------------|--------|-----------------|
|
|
664
|
+
| 401 | `unauthorized` | Missing, invalid, or revoked API key |
|
|
665
|
+
| 403 | `forbidden` | Key lacks the required scope for this endpoint |
|
|
666
|
+
| 404 | `not_found` | Resource doesn't exist in your workspace (agent, workflow, task) |
|
|
667
|
+
| 409 | `conflict` | Conflict (e.g. duplicate resource) |
|
|
668
|
+
| 400 | `validation_error` | Malformed request body, missing required fields, invalid params |
|
|
669
|
+
| 500 | `internal_error` | Server-side error (AI provider failure, DB issue, etc.) |
|
|
670
|
+
| — | `network` | No HTTP response received (connection refused, DNS failure, timeout) |
|
|
671
|
+
| — | `aborted` | Caller aborted via `AbortSignal` |
|
|
672
|
+
| — | `timeout` | `wait()` exceeded its `timeoutMs` budget |
|
|
175
673
|
|
|
176
|
-
|
|
674
|
+
### Common Error Scenarios
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
// 1. Expired/revoked key
|
|
678
|
+
try {
|
|
679
|
+
await ql.workspace.get();
|
|
680
|
+
} catch (e) {
|
|
681
|
+
if (e instanceof QLError && e.code === "unauthorized") {
|
|
682
|
+
console.log("API key is invalid or revoked. Generate a new one in your dashboard.");
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// 2. Key doesn't have chat scope
|
|
687
|
+
try {
|
|
688
|
+
await ql.agents.chat({ agentId: "finance", messages: [...] });
|
|
689
|
+
} catch (e) {
|
|
690
|
+
if (e instanceof QLError && e.code === "forbidden") {
|
|
691
|
+
console.log("This key needs the 'chat' scope. Create a new key with broader scopes.");
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// 3. AI provider error (transient)
|
|
696
|
+
try {
|
|
697
|
+
await ql.agents.chat({ agentId: "governing", messages: [...] });
|
|
698
|
+
} catch (e) {
|
|
699
|
+
if (e instanceof QLError && e.status === 500) {
|
|
700
|
+
console.log("AI service temporarily unavailable. Retry later.");
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
---
|
|
177
706
|
|
|
178
707
|
## TypeScript
|
|
179
708
|
|
|
180
|
-
|
|
709
|
+
Full type declarations are included. All public types are exported:
|
|
181
710
|
|
|
182
711
|
```ts
|
|
183
712
|
import type {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
713
|
+
QLClient,
|
|
714
|
+
QLClientOptions,
|
|
715
|
+
QLError,
|
|
716
|
+
// Request types
|
|
717
|
+
ChatInput,
|
|
718
|
+
RunCapabilityInput,
|
|
719
|
+
TaskCreateInput,
|
|
720
|
+
WaitOptions,
|
|
721
|
+
HistoryParams,
|
|
722
|
+
WorkflowRunInput,
|
|
723
|
+
// Response types
|
|
724
|
+
Workspace,
|
|
725
|
+
AgentSummary,
|
|
726
|
+
AgentDetail,
|
|
727
|
+
CapabilityInfo,
|
|
728
|
+
CapabilityResult,
|
|
729
|
+
ChatResult,
|
|
730
|
+
ChatStreamEvent,
|
|
731
|
+
HistoryPage,
|
|
732
|
+
MessageItem,
|
|
733
|
+
WorkflowRun,
|
|
734
|
+
Task,
|
|
735
|
+
TaskCreated,
|
|
736
|
+
// Constants
|
|
737
|
+
API_ERROR_CODES,
|
|
187
738
|
} from "ql-agent-sdk";
|
|
188
739
|
```
|
|
189
740
|
|
|
190
|
-
|
|
741
|
+
---
|
|
191
742
|
|
|
192
|
-
|
|
743
|
+
## Using in Next.js App Router
|
|
744
|
+
|
|
745
|
+
The SDK is safe to use **server-side** in Route Handlers or Server Components:
|
|
193
746
|
|
|
194
747
|
```ts
|
|
195
748
|
// app/api/agent/route.ts
|
|
@@ -200,48 +753,97 @@ export const runtime = "nodejs"; // uses native fetch + Web Streams
|
|
|
200
753
|
const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
|
|
201
754
|
|
|
202
755
|
export async function POST(req: Request) {
|
|
756
|
+
const { message } = await req.json();
|
|
757
|
+
|
|
203
758
|
try {
|
|
204
|
-
const
|
|
205
|
-
const res = await ql.agents.chat({
|
|
759
|
+
const reply = await ql.agents.chat({
|
|
206
760
|
agentId: "governing",
|
|
207
|
-
messages: [{ role: "user", content:
|
|
761
|
+
messages: [{ role: "user", content: message }],
|
|
208
762
|
});
|
|
209
|
-
return Response.json({
|
|
763
|
+
return Response.json({ text: reply.content });
|
|
210
764
|
} catch (err) {
|
|
211
765
|
if (err instanceof QLError) {
|
|
212
|
-
return Response.json({
|
|
766
|
+
return Response.json({ error: err.message }, { status: err.status ?? 500 });
|
|
213
767
|
}
|
|
214
768
|
throw err;
|
|
215
769
|
}
|
|
216
770
|
}
|
|
217
771
|
```
|
|
218
772
|
|
|
219
|
-
For **streaming**
|
|
773
|
+
For **streaming** in a Route Handler:
|
|
774
|
+
```ts
|
|
775
|
+
export async function POST(req: Request) {
|
|
776
|
+
const { message } = await req.json();
|
|
777
|
+
const ql = new QLClient({ apiKey: process.env.QL_API_KEY! });
|
|
778
|
+
|
|
779
|
+
const stream = new ReadableStream({
|
|
780
|
+
async start(controller) {
|
|
781
|
+
try {
|
|
782
|
+
for await (const ev of ql.agents.chatStream({
|
|
783
|
+
agentId: "governing",
|
|
784
|
+
messages: [{ role: "user", content: message }],
|
|
785
|
+
})) {
|
|
786
|
+
if (ev.type === "delta" && ev.content) {
|
|
787
|
+
controller.enqueue(new TextEncoder().encode(ev.content));
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
} finally {
|
|
791
|
+
controller.close();
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
return new Response(stream, { headers: { "Content-Type": "text/plain" } });
|
|
797
|
+
}
|
|
798
|
+
```
|
|
220
799
|
|
|
221
|
-
|
|
800
|
+
---
|
|
222
801
|
|
|
223
|
-
|
|
224
|
-
npm install
|
|
225
|
-
npm run typecheck # tsc --noEmit
|
|
226
|
-
npm run lint # eslint (typescript-eslint)
|
|
227
|
-
npm test # vitest (unit: fetch, errors, SSE, polling, timeout, cancellation, pagination)
|
|
228
|
-
npm run build # tsup -> dist (ESM + CJS + .d.ts)
|
|
229
|
-
```
|
|
802
|
+
## Agent Reference
|
|
230
803
|
|
|
231
|
-
|
|
804
|
+
The platform includes 8 specialized agents. Each has 20 capabilities in its domain:
|
|
232
805
|
|
|
233
|
-
|
|
806
|
+
| ID | Domain | Examples |
|
|
807
|
+
|----|--------|----------|
|
|
808
|
+
| `governing` | Orchestration | Coordinates other agents, complex multi-step reasoning |
|
|
809
|
+
| `finance` | Finance | Invoices, P&L, budgeting, cash flow, tax estimates |
|
|
810
|
+
| `marketing` | Marketing | Social media content, ad copy, SEO, campaign planning |
|
|
811
|
+
| `inventory` | Supply Chain | Stock levels, reorder points, supplier management |
|
|
812
|
+
| `customer` | Customer Success | Support drafts, onboarding sequences, churn analysis |
|
|
813
|
+
| `developer` | Engineering | Code generation, API docs, bug triage, test writing |
|
|
814
|
+
| `operations` | Operations | SOPs, process mapping, KPI dashboards, compliance |
|
|
815
|
+
| `research` | Research | Market analysis, competitor intel, literature reviews |
|
|
234
816
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
npm publish --access public # or --access restricted for a scoped/owned package
|
|
817
|
+
Discover available capabilities at runtime:
|
|
818
|
+
```ts
|
|
819
|
+
const detail = await ql.agents.get("finance");
|
|
820
|
+
console.log(detail.capabilities.map((c) => c.id));
|
|
240
821
|
```
|
|
241
822
|
|
|
242
|
-
|
|
823
|
+
---
|
|
243
824
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
825
|
+
## Environment Variables
|
|
826
|
+
|
|
827
|
+
| Variable | Purpose |
|
|
828
|
+
|----------|---------|
|
|
829
|
+
| `QL_API_KEY` | Your API key (convention — the SDK reads it from the `apiKey` option, not env directly) |
|
|
830
|
+
| `QL_BASE_URL` | Override the base URL (useful for staging/self-hosted) |
|
|
831
|
+
|
|
832
|
+
---
|
|
833
|
+
|
|
834
|
+
## Troubleshooting
|
|
835
|
+
|
|
836
|
+
| Symptom | Cause | Fix |
|
|
837
|
+
|---------|-------|-----|
|
|
838
|
+
| `QLError: unauthorized` | Key is invalid, revoked, or expired | Generate a new key in your dashboard |
|
|
839
|
+
| `QLError: forbidden` | Key scope doesn't cover the endpoint | Create a key with the required scope or `*` |
|
|
840
|
+
| `QLError: not_found` (agent) | Typo in agent slug | Use `ql.agents.list()` to see valid IDs |
|
|
841
|
+
| `QLError: network` | Can't reach the API | Check your network / base URL / firewall |
|
|
842
|
+
| Task stuck in `"running"` | Server restarted mid-execution | Wait for stale recovery (≤5 min) or poll again later |
|
|
843
|
+
| Stream produces no output | Model returned empty response | Check `reasoning` field or try a different agent |
|
|
844
|
+
|
|
845
|
+
---
|
|
846
|
+
|
|
847
|
+
## License
|
|
848
|
+
|
|
849
|
+
MIT
|