@perstack/api-client 0.0.45 → 0.0.48

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
@@ -1,126 +1,725 @@
1
1
  # @perstack/api-client
2
2
 
3
- The official TypeScript/JavaScript API client for Perstack.
4
-
5
- For API reference, see [Registry API](https://github.com/perstack-ai/perstack/blob/main/docs/references/registry-api.md).
3
+ Official TypeScript API client for the Perstack platform.
6
4
 
7
5
  ## Installation
8
6
 
9
7
  ```bash
10
8
  npm install @perstack/api-client
11
- # or
12
- pnpm add @perstack/api-client
13
- # or
14
- yarn add @perstack/api-client
15
9
  ```
16
10
 
17
- ## Usage
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { createApiClient } from "@perstack/api-client"
15
+
16
+ const client = createApiClient({
17
+ apiKey: "your-api-key",
18
+ })
19
+
20
+ // List applications
21
+ const result = await client.applications.list()
22
+ if (result.ok) {
23
+ console.log(result.data.data.applications)
24
+ } else {
25
+ console.error(result.error.message)
26
+ }
27
+ ```
28
+
29
+ ## Configuration
30
+
31
+ The `createApiClient` function accepts a configuration object:
32
+
33
+ ```typescript
34
+ interface ApiClientConfig {
35
+ apiKey: string // Required: Your Perstack API key
36
+ baseUrl?: string // Optional: API base URL (default: "https://api.perstack.ai")
37
+ timeout?: number // Optional: Request timeout in milliseconds (default: 30000)
38
+ }
39
+ ```
40
+
41
+ ## API Reference
42
+
43
+ The client provides access to four main API modules:
44
+
45
+ - `client.applications` - Application management
46
+ - `client.env` - Environment configuration (secrets and variables)
47
+ - `client.jobs` - Job execution and monitoring
48
+ - `client.experts` - Expert definitions and versioning
49
+
50
+ ### Result Type
18
51
 
19
- ### Initialization
52
+ All API methods return a discriminated union type for type-safe error handling:
20
53
 
21
54
  ```typescript
22
- import { ApiV1Client } from "@perstack/api-client";
55
+ type ApiResult<T> = { ok: true; data: T } | { ok: false; error: ApiError }
23
56
 
24
- const client = new ApiV1Client({
25
- baseUrl: "https://api.perstack.ai", // Optional, defaults to https://api.perstack.ai
26
- apiKey: "YOUR_API_KEY", // Required for authenticated requests
27
- });
57
+ interface ApiError {
58
+ code: number // HTTP status code (0 for network/validation errors)
59
+ message: string // Error message
60
+ reason?: unknown // Additional error details
61
+ aborted?: boolean // True if request was aborted
62
+ }
28
63
  ```
29
64
 
30
- ### Registry
65
+ ### Request Options
31
66
 
32
- Interact with the Expert Registry.
67
+ All API methods accept an optional `RequestOptions` object:
33
68
 
34
69
  ```typescript
35
- // Get all experts in the registry
36
- const experts = await client.registry.experts.getMany({});
70
+ interface RequestOptions {
71
+ signal?: AbortSignal // AbortController signal for cancellation
72
+ }
73
+ ```
37
74
 
38
- // Get a specific expert
39
- const expert = await client.registry.experts.get({
40
- owner: "perstack",
41
- slug: "software-engineer",
42
- });
75
+ For streaming endpoints, an extended `StreamRequestOptions` is available:
43
76
 
44
- // Get expert versions
45
- const versions = await client.registry.experts.getVersions({
46
- owner: "perstack",
47
- slug: "software-engineer",
48
- });
77
+ ```typescript
78
+ interface StreamRequestOptions extends RequestOptions {
79
+ streamIdleTimeout?: number // Idle timeout in ms between chunks (default: client timeout)
80
+ }
49
81
  ```
50
82
 
51
- ### Studio
83
+ ---
52
84
 
53
- Interact with the Studio (Experts, Jobs, Workspace).
85
+ ### Applications API
54
86
 
55
- #### Experts
87
+ Manage applications within your organization.
88
+
89
+ #### `client.applications.list(params?, options?)`
90
+
91
+ List all applications with optional filtering and pagination.
56
92
 
57
93
  ```typescript
58
- // Create a studio expert
59
- const newExpert = await client.studio.experts.create({
60
- slug: "my-custom-expert",
61
- description: "A custom expert for my needs",
62
- });
94
+ const result = await client.applications.list({
95
+ name: "my-app", // Filter by name
96
+ sort: "createdAt", // Sort by: "name" | "createdAt" | "updatedAt"
97
+ order: "desc", // Order: "asc" | "desc"
98
+ take: 10, // Number of results
99
+ skip: 0, // Offset for pagination
100
+ })
101
+ ```
102
+
103
+ #### `client.applications.get(id, options?)`
63
104
 
64
- // Get studio experts
65
- const myExperts = await client.studio.experts.getMany({});
105
+ Get a single application by ID.
106
+
107
+ ```typescript
108
+ const result = await client.applications.get("app-id")
109
+ if (result.ok) {
110
+ console.log(result.data.data.application)
111
+ }
66
112
  ```
67
113
 
68
- #### Expert Jobs
114
+ #### `client.applications.create(input, options?)`
69
115
 
70
- Manage expert execution jobs.
116
+ Create a new application.
71
117
 
72
118
  ```typescript
73
- // Start a job
74
- const job = await client.studio.expertJobs.start({
75
- expertId: "expert-id",
76
- input: {
77
- // ... input data
78
- },
79
- });
119
+ const result = await client.applications.create({
120
+ name: "My Application",
121
+ applicationGroupId: "group-id", // Optional
122
+ })
123
+ ```
124
+
125
+ #### `client.applications.update(id, input, options?)`
126
+
127
+ Update an existing application.
128
+
129
+ ```typescript
130
+ const result = await client.applications.update("app-id", {
131
+ name: "Updated Name",
132
+ status: "active", // "active" | "inactive"
133
+ })
134
+ ```
135
+
136
+ #### `client.applications.delete(id, options?)`
137
+
138
+ Delete an application.
139
+
140
+ ```typescript
141
+ const result = await client.applications.delete("app-id")
142
+ ```
143
+
144
+ ---
145
+
146
+ ### Environment API
147
+
148
+ Manage secrets and environment variables.
149
+
150
+ #### Secrets
151
+
152
+ ##### `client.env.secrets.list(options?)`
153
+
154
+ List all secrets.
155
+
156
+ ```typescript
157
+ const result = await client.env.secrets.list()
158
+ if (result.ok) {
159
+ for (const secret of result.data.data.secrets) {
160
+ console.log(secret.name) // Secret values are not returned
161
+ }
162
+ }
163
+ ```
164
+
165
+ ##### `client.env.secrets.get(name, options?)`
166
+
167
+ Get a secret by name.
168
+
169
+ ```typescript
170
+ const result = await client.env.secrets.get("API_KEY")
171
+ ```
172
+
173
+ ##### `client.env.secrets.create(input, options?)`
174
+
175
+ Create a new secret.
176
+
177
+ ```typescript
178
+ const result = await client.env.secrets.create({
179
+ name: "API_KEY",
180
+ value: "secret-value",
181
+ })
182
+ ```
183
+
184
+ ##### `client.env.secrets.update(name, input, options?)`
185
+
186
+ Update an existing secret.
187
+
188
+ ```typescript
189
+ const result = await client.env.secrets.update("API_KEY", {
190
+ value: "new-secret-value",
191
+ })
192
+ ```
193
+
194
+ ##### `client.env.secrets.delete(name, options?)`
195
+
196
+ Delete a secret.
197
+
198
+ ```typescript
199
+ const result = await client.env.secrets.delete("API_KEY")
200
+ ```
201
+
202
+ #### Variables
203
+
204
+ ##### `client.env.variables.list(options?)`
205
+
206
+ List all environment variables.
207
+
208
+ ```typescript
209
+ const result = await client.env.variables.list()
210
+ ```
211
+
212
+ ##### `client.env.variables.get(name, options?)`
213
+
214
+ Get a variable by name.
215
+
216
+ ```typescript
217
+ const result = await client.env.variables.get("DATABASE_URL")
218
+ ```
219
+
220
+ ##### `client.env.variables.create(input, options?)`
221
+
222
+ Create a new variable.
223
+
224
+ ```typescript
225
+ const result = await client.env.variables.create({
226
+ name: "DATABASE_URL",
227
+ value: "postgres://...",
228
+ })
229
+ ```
230
+
231
+ ##### `client.env.variables.update(name, input, options?)`
232
+
233
+ Update an existing variable.
234
+
235
+ ```typescript
236
+ const result = await client.env.variables.update("DATABASE_URL", {
237
+ value: "postgres://new-url...",
238
+ })
239
+ ```
240
+
241
+ ##### `client.env.variables.delete(name, options?)`
242
+
243
+ Delete a variable.
244
+
245
+ ```typescript
246
+ const result = await client.env.variables.delete("DATABASE_URL")
247
+ ```
248
+
249
+ ---
250
+
251
+ ### Jobs API
252
+
253
+ Execute and monitor expert jobs.
254
+
255
+ #### `client.jobs.list(params?, options?)`
256
+
257
+ List jobs with optional filtering and pagination.
258
+
259
+ ```typescript
260
+ const result = await client.jobs.list({
261
+ take: 20,
262
+ skip: 0,
263
+ sort: "createdAt",
264
+ order: "desc",
265
+ filter: "status:running",
266
+ })
267
+ ```
268
+
269
+ #### `client.jobs.get(id, options?)`
270
+
271
+ Get a job by ID.
272
+
273
+ ```typescript
274
+ const result = await client.jobs.get("job-id")
275
+ if (result.ok) {
276
+ console.log(result.data.data.job.status)
277
+ }
278
+ ```
279
+
280
+ #### `client.jobs.start(input, options?)`
281
+
282
+ Start a new job.
283
+
284
+ ```typescript
285
+ const result = await client.jobs.start({
286
+ applicationId: "app-id",
287
+ expertKey: "@org/expert@1.0.0",
288
+ query: "Help me with this task",
289
+ files: ["file1.txt", "file2.txt"], // Optional
290
+ provider: "anthropic",
291
+ model: "claude-sonnet-4-20250514", // Optional
292
+ reasoningBudget: "medium", // Optional: "low" | "medium" | "high"
293
+ maxSteps: 50, // Optional
294
+ maxRetries: 3, // Optional
295
+ })
296
+ ```
297
+
298
+ #### `client.jobs.update(id, input, options?)`
299
+
300
+ Update a job's status.
301
+
302
+ ```typescript
303
+ const result = await client.jobs.update("job-id", {
304
+ status: "paused",
305
+ })
306
+ ```
307
+
308
+ #### `client.jobs.continue(id, input, options?)`
309
+
310
+ Continue a paused job with additional input.
311
+
312
+ ```typescript
313
+ const result = await client.jobs.continue("job-id", {
314
+ query: "Continue with this additional context",
315
+ files: ["additional-file.txt"],
316
+ interactiveToolCallResult: true, // Optional: for tool call responses
317
+ provider: "anthropic", // Optional: override provider
318
+ model: "claude-sonnet-4-20250514", // Optional: override model
319
+ maxSteps: 10, // Optional: additional steps limit
320
+ })
321
+ ```
322
+
323
+ #### `client.jobs.cancel(id, options?)`
324
+
325
+ Cancel a running job.
326
+
327
+ ```typescript
328
+ const result = await client.jobs.cancel("job-id")
329
+ ```
330
+
331
+ #### Checkpoints
332
+
333
+ Track job progress through checkpoints.
334
+
335
+ ##### `client.jobs.checkpoints.list(jobId, params?, options?)`
336
+
337
+ List checkpoints for a job.
338
+
339
+ ```typescript
340
+ const result = await client.jobs.checkpoints.list("job-id", {
341
+ take: 50,
342
+ skip: 0,
343
+ sort: "createdAt",
344
+ order: "asc",
345
+ })
346
+ ```
347
+
348
+ ##### `client.jobs.checkpoints.get(jobId, checkpointId, options?)`
349
+
350
+ Get a specific checkpoint.
351
+
352
+ ```typescript
353
+ const result = await client.jobs.checkpoints.get("job-id", "checkpoint-id")
354
+ ```
355
+
356
+ ##### `client.jobs.checkpoints.stream(jobId, options?)`
357
+
358
+ Stream checkpoints in real-time using Server-Sent Events (SSE).
359
+
360
+ ```typescript
361
+ for await (const checkpoint of client.jobs.checkpoints.stream("job-id")) {
362
+ console.log("Activity:", checkpoint.activity)
363
+ }
364
+ ```
365
+
366
+ ---
367
+
368
+ ### Experts API
369
+
370
+ Manage expert definitions and versions.
371
+
372
+ #### `client.experts.list(params?, options?)`
373
+
374
+ List available experts.
375
+
376
+ ```typescript
377
+ const result = await client.experts.list({
378
+ filter: "search term",
379
+ category: "coding", // "general" | "coding" | "research" | "writing" | "data" | "automation"
380
+ includeDrafts: false,
381
+ limit: 20,
382
+ offset: 0,
383
+ })
384
+ ```
385
+
386
+ #### `client.experts.get(key, options?)`
387
+
388
+ Get an expert definition by key.
389
+
390
+ ```typescript
391
+ // Get latest version
392
+ const result = await client.experts.get("@org/expert")
393
+
394
+ // Get specific version
395
+ const result = await client.experts.get("@org/expert@1.0.0")
396
+
397
+ // Get by tag
398
+ const result = await client.experts.get("@org/expert@latest")
399
+ ```
400
+
401
+ #### `client.experts.getMeta(key, options?)`
402
+
403
+ Get expert metadata without the full definition.
404
+
405
+ ```typescript
406
+ const result = await client.experts.getMeta("@org/expert@1.0.0")
407
+ if (result.ok) {
408
+ console.log("Scope:", result.data.data.scope)
409
+ console.log("Version:", result.data.data.version)
410
+ }
411
+ ```
412
+
413
+ #### `client.experts.publish(scopeName, options?)`
414
+
415
+ Publish an expert scope (make it publicly visible).
416
+
417
+ ```typescript
418
+ const result = await client.experts.publish("@org/expert")
419
+ ```
420
+
421
+ #### `client.experts.unpublish(scopeName, options?)`
422
+
423
+ Unpublish an expert scope (make it private).
424
+
425
+ ```typescript
426
+ const result = await client.experts.unpublish("@org/expert")
427
+ ```
428
+
429
+ #### `client.experts.yank(key, options?)`
430
+
431
+ Yank (deprecate) a specific version.
432
+
433
+ ```typescript
434
+ const result = await client.experts.yank("@org/expert@1.0.0")
435
+ if (result.ok) {
436
+ console.log("Yanked:", result.data.data.yanked)
437
+ console.log("Latest tag updated:", result.data.data.latestTagUpdated)
438
+ }
439
+ ```
440
+
441
+ #### Drafts
442
+
443
+ Manage expert draft versions before publishing.
444
+
445
+ ##### `client.experts.drafts.list(scopeName, params?, options?)`
446
+
447
+ List drafts for a scope.
448
+
449
+ ```typescript
450
+ const result = await client.experts.drafts.list("@org/expert", {
451
+ limit: 10,
452
+ offset: 0,
453
+ })
454
+ ```
455
+
456
+ ##### `client.experts.drafts.get(scopeName, draftRef, options?)`
457
+
458
+ Get a specific draft.
459
+
460
+ ```typescript
461
+ const result = await client.experts.drafts.get("@org/expert", "draft-ref")
462
+ ```
463
+
464
+ ##### `client.experts.drafts.create(scopeName, input, options?)`
465
+
466
+ Create a new draft.
467
+
468
+ ```typescript
469
+ const result = await client.experts.drafts.create("@org/expert", {
470
+ applicationId: "app-id",
471
+ experts: [
472
+ {
473
+ key: "main",
474
+ name: "Main Expert",
475
+ description: "A helpful assistant",
476
+ instruction: "You are a helpful assistant...",
477
+ skills: {},
478
+ delegates: [],
479
+ },
480
+ ],
481
+ })
482
+ ```
483
+
484
+ ##### `client.experts.drafts.update(scopeName, draftRef, input, options?)`
80
485
 
81
- // Get job status
82
- const jobStatus = await client.studio.expertJobs.get({
83
- id: job.id,
84
- });
486
+ Update an existing draft.
85
487
 
86
- // Continue a job (if waiting for input)
87
- await client.studio.expertJobs.continue({
88
- id: job.id,
89
- input: {
90
- // ... user input
91
- },
92
- });
488
+ ```typescript
489
+ const result = await client.experts.drafts.update("@org/expert", "draft-ref", {
490
+ experts: [
491
+ {
492
+ key: "main",
493
+ name: "Updated Expert",
494
+ instruction: "Updated instructions...",
495
+ },
496
+ ],
497
+ })
93
498
  ```
94
499
 
95
- #### Workspace
500
+ ##### `client.experts.drafts.delete(scopeName, draftRef, options?)`
96
501
 
97
- Manage workspace resources (Items, Variables, Secrets).
502
+ Delete a draft.
98
503
 
99
504
  ```typescript
100
- // Get workspace details
101
- const workspace = await client.studio.workspace.get();
505
+ const result = await client.experts.drafts.delete("@org/expert", "draft-ref")
506
+ ```
507
+
508
+ ##### `client.experts.drafts.assignVersion(scopeName, draftRef, input, options?)`
102
509
 
103
- // Create a workspace item (file)
104
- await client.studio.workspace.items.create({
105
- path: "/path/to/file.txt",
106
- content: "Hello, World!",
107
- });
510
+ Promote a draft to a versioned release.
108
511
 
109
- // Create a secret
110
- await client.studio.workspace.secrets.create({
111
- key: "OPENAI_API_KEY",
112
- value: "sk-...",
113
- });
512
+ ```typescript
513
+ const result = await client.experts.drafts.assignVersion("@org/expert", "draft-ref", {
514
+ version: "1.0.0",
515
+ tag: "latest", // Optional: assign a tag
516
+ })
114
517
  ```
115
518
 
519
+ #### Versions
520
+
521
+ List expert versions.
522
+
523
+ ##### `client.experts.versions.list(scopeName, options?)`
524
+
525
+ List all versions for a scope.
526
+
527
+ ```typescript
528
+ const result = await client.experts.versions.list("@org/expert")
529
+ if (result.ok) {
530
+ for (const version of result.data.data.versions) {
531
+ console.log(`${version.version}: ${version.tag || "(no tag)"}`)
532
+ }
533
+ }
534
+ ```
535
+
536
+ ---
537
+
116
538
  ## Error Handling
117
539
 
118
- The client throws errors for failed requests.
540
+ The client uses a result type pattern for predictable error handling:
541
+
542
+ ```typescript
543
+ const result = await client.applications.get("app-id")
544
+
545
+ if (!result.ok) {
546
+ switch (result.error.code) {
547
+ case 401:
548
+ console.error("Authentication failed")
549
+ break
550
+ case 404:
551
+ console.error("Application not found")
552
+ break
553
+ case 0:
554
+ // Network error, timeout, or validation error
555
+ if (result.error.aborted) {
556
+ console.error("Request was cancelled")
557
+ } else {
558
+ console.error("Network error:", result.error.message)
559
+ }
560
+ break
561
+ default:
562
+ console.error(`Error ${result.error.code}: ${result.error.message}`)
563
+ }
564
+ return
565
+ }
566
+
567
+ // TypeScript knows result.data exists here
568
+ console.log(result.data.data.application)
569
+ ```
570
+
571
+ ### Request Cancellation
572
+
573
+ Use `AbortController` to cancel requests:
574
+
575
+ ```typescript
576
+ const controller = new AbortController()
577
+
578
+ // Cancel after 5 seconds
579
+ setTimeout(() => controller.abort(), 5000)
580
+
581
+ const result = await client.jobs.start(
582
+ { applicationId: "app-id", expertKey: "@org/expert", provider: "anthropic" },
583
+ { signal: controller.signal }
584
+ )
585
+
586
+ if (!result.ok && result.error.aborted) {
587
+ console.log("Request was cancelled")
588
+ }
589
+ ```
590
+
591
+ ---
592
+
593
+ ## SSE Streaming
594
+
595
+ For real-time checkpoint streaming, the client provides an async generator:
596
+
597
+ ```typescript
598
+ import { createApiClient } from "@perstack/api-client"
599
+
600
+ const client = createApiClient({ apiKey: "your-api-key" })
601
+
602
+ // Start a job
603
+ const jobResult = await client.jobs.start({
604
+ applicationId: "app-id",
605
+ expertKey: "@org/expert",
606
+ provider: "anthropic",
607
+ })
608
+
609
+ if (!jobResult.ok) {
610
+ throw new Error(jobResult.error.message)
611
+ }
612
+
613
+ const jobId = jobResult.data.data.job.id
614
+
615
+ // Stream checkpoints
616
+ for await (const result of client.jobs.checkpoints.stream(jobId)) {
617
+ if (!result.ok) {
618
+ if (result.error.aborted) {
619
+ console.error("Stream timed out or was cancelled")
620
+ } else {
621
+ console.error("Stream error:", result.error.message)
622
+ }
623
+ break
624
+ }
625
+ console.log("Checkpoint:", result.data.id)
626
+ }
627
+ ```
628
+
629
+ ### Stream Timeout Configuration
630
+
631
+ The streaming endpoints support an idle timeout that aborts the stream if no data is received within the specified period:
632
+
633
+ ```typescript
634
+ for await (const result of client.jobs.checkpoints.stream(jobId, {
635
+ streamIdleTimeout: 60000, // 60 second idle timeout
636
+ })) {
637
+ if (!result.ok) {
638
+ if (result.error.aborted) {
639
+ console.error("Stream timed out or was cancelled")
640
+ }
641
+ break
642
+ }
643
+ console.log("Checkpoint:", result.data.id)
644
+ }
645
+ ```
646
+
647
+ By default, the stream idle timeout uses the client's configured timeout value (30 seconds if not specified).
648
+
649
+ ### Advanced SSE Parsing
650
+
651
+ For custom SSE parsing, the package exports utility functions:
119
652
 
120
653
  ```typescript
121
- try {
122
- await client.registry.experts.get({ owner: "invalid", slug: "expert" });
123
- } catch (error) {
124
- console.error("Failed to fetch expert:", error);
654
+ import { parseSSE, parseCheckpointSSE, parseSSEWithSchema } from "@perstack/api-client"
655
+ import { z } from "zod"
656
+
657
+ // Generic SSE parser
658
+ const reader = response.body.getReader()
659
+ for await (const event of parseSSE<MyEventType>(reader)) {
660
+ console.log(event)
661
+ }
662
+
663
+ // Checkpoint-specific parser with validation
664
+ for await (const event of parseCheckpointSSE(reader)) {
665
+ if (event.type === "checkpoint") {
666
+ console.log("Checkpoint:", event.data)
667
+ } else if (event.type === "done") {
668
+ console.log("Stream complete:", event.data.status)
669
+ } else if (event.type === "error") {
670
+ console.error("Parse error:", event.data.message)
671
+ }
672
+ }
673
+
674
+ // Custom schema validation
675
+ const mySchema = z.object({ id: z.string(), value: z.number() })
676
+ for await (const event of parseSSEWithSchema(reader, mySchema)) {
677
+ console.log(event.id, event.value)
125
678
  }
126
679
  ```
680
+
681
+ ---
682
+
683
+ ## TypeScript Support
684
+
685
+ The package is written in TypeScript and exports all types:
686
+
687
+ ```typescript
688
+ import type {
689
+ // Client types
690
+ ApiClient,
691
+ ApiClientConfig,
692
+ ApiResult,
693
+ ApiError,
694
+ RequestOptions,
695
+ StreamRequestOptions,
696
+
697
+ // Application types
698
+ Application,
699
+ ApplicationStatus,
700
+ CreateApplicationInput,
701
+ UpdateApplicationInput,
702
+ ListApplicationsParams,
703
+
704
+ // Job types
705
+ Job,
706
+ JobStatus,
707
+ StartJobInput,
708
+ UpdateJobInput,
709
+ ContinueJobInput,
710
+ Checkpoint,
711
+ CheckpointStatus,
712
+
713
+ // Expert types
714
+ Expert,
715
+ ExpertDefinition,
716
+ ExpertVersion,
717
+ ExpertScope,
718
+ } from "@perstack/api-client"
719
+ ```
720
+
721
+ ---
722
+
723
+ ## License
724
+
725
+ MIT