@meistrari/tela-sdk-js 1.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,13 +7,20 @@ The Tela SDK for JavaScript provides a simple and powerful way to interact with
7
7
  - [Installation](#installation)
8
8
  - [Usage](#usage)
9
9
  - [Examples](#examples)
10
- - [Simple Completion](#simple-completion)
11
- - [Chat Completion](#chat-completion)
12
- - [Webhook Completion](#webhook-completion)
13
- - [Streaming Completion](#streaming-completion)
14
- - [API Reference](#api-reference)
15
- - [Contributing](#contributing)
16
- - [License](#license)
10
+ - [Canvas API](#canvas-api)
11
+ - [Synchronous Execution](#synchronous-execution)
12
+ - [Asynchronous Execution with Polling](#asynchronous-execution-with-polling)
13
+ - [Streaming Execution](#streaming-execution)
14
+ - [Event-Driven Execution](#event-driven-execution)
15
+ - [Schema Validation](#schema-validation)
16
+ - [Multiple Files](#multiple-files)
17
+ - [Webhook Notifications](#webhook-notifications)
18
+ - [Execution Tags](#execution-tags)
19
+ - [File Handling](#file-handling)
20
+ - [Migration Guide from v1.x to v2](#migration-guide-from-v1x-to-v2)
21
+ - [Breaking Changes](#breaking-changes)
22
+ - [Migration Checklist](#migration-checklist)
23
+ - [New Features in v2](#new-features-in-v2)
17
24
 
18
25
  ## Installation
19
26
 
@@ -30,130 +37,1017 @@ pnpm add @meistrari/tela-sdk-js
30
37
  First, you need to import the SDK and initialize it with your API key:
31
38
 
32
39
  ```typescript
33
- import { createTelaClient } from '@meistrari/tela-sdk-js'
40
+ import { TelaSDK } from '@meistrari/tela-sdk-js'
34
41
 
35
- const tela = createTelaClient({ apiKey: 'your-api-key' })
42
+ const tela = new TelaSDK({ apiKey: 'your-api-key' })
36
43
  ```
37
44
 
38
45
  ## Examples
39
46
 
40
- ### Simple Completion
47
+ ### Canvas API
41
48
 
42
- This example demonstrates how to create a simple completion using a PDF document:
49
+ The Canvas API provides a type-safe way to execute prompts with schema validation and multiple execution modes.
50
+
51
+ #### Synchronous Execution
52
+
53
+ Execute a canvas and wait for the complete result:
43
54
 
44
55
  ```typescript
45
- import { createTelaClient } from '@meistrari/tela-sdk-js'
46
- import type { TelaFile } from '@meistrari/tela-sdk-js'
56
+ import { TelaSDK } from '@meistrari/tela-sdk-js'
47
57
 
48
- const tela = createTelaClient({
49
- apiKey: process.env.TELA_API_KEY,
58
+ const tela = new TelaSDK({
59
+ apiKey: 'your-api-key'
50
60
  })
51
61
 
52
- async function run() {
53
- const completion = await tela.completions.create<{ document: TelaFile }, { fileSummary: string }>({
54
- canvasId: process.env.TELA_CANVAS_ID,
55
- variables: {
56
- document: tela.createFile('https://www.wmaccess.com/downloads/sample-invoice.pdf'),
57
- },
58
- stream: false,
59
- })
60
- console.log(JSON.stringify(completion, null, 2))
62
+ // Get canvas with input/output schemas
63
+ const canvas = await tela.canvas.get({
64
+ id: 'canvas-id',
65
+ input: schema => schema.object({
66
+ query: schema.string()
67
+ }),
68
+ output: schema => schema.object({
69
+ result: schema.string()
70
+ }),
71
+ })
72
+
73
+ // Execute synchronously - direct result access
74
+ const result = await canvas.execute({ query: 'What is the capital of France?' }).result
75
+
76
+ console.log(result) // { result: "Paris" }
77
+
78
+ // Alternative: get execution object for more control
79
+ const execution = await canvas.execute({ query: 'What is the capital of France?' })
80
+ const result2 = await execution.result
81
+ ```
82
+
83
+ #### Asynchronous Execution with Polling
84
+
85
+ For long-running operations, use async mode with automatic polling:
86
+
87
+ ```typescript
88
+ const canvas = await tela.canvas.get({
89
+ id: 'canvas-id',
90
+ input: schema => schema.object({
91
+ document: schema.file(),
92
+ query: schema.string()
93
+ }),
94
+ output: schema => schema.object({
95
+ summary: schema.string()
96
+ }),
97
+ })
98
+
99
+ // Execute asynchronously with polling - direct result access
100
+ const result = await canvas.execute(
101
+ {
102
+ document: TelaFile.create(documentBlob, { name: 'report.pdf' }),
103
+ query: 'Summarize this document'
104
+ },
105
+ {
106
+ async: true,
107
+ pollingInterval: 1000, // Poll every 1 second
108
+ pollingTimeout: 60000, // Timeout after 60 seconds
109
+ }
110
+ ).result
111
+
112
+ console.log(result.summary)
113
+ ```
114
+
115
+ #### Streaming Execution
116
+
117
+ Stream results as they're generated:
118
+
119
+ ```typescript
120
+ const canvas = await tela.canvas.get({
121
+ id: 'canvas-id',
122
+ input: schema => schema.object({
123
+ prompt: schema.string()
124
+ }),
125
+ output: schema => schema.object({
126
+ response: schema.string()
127
+ }),
128
+ })
129
+
130
+ // Execute with streaming - direct iteration
131
+ for await (const chunk of canvas.execute(
132
+ { prompt: 'Write a story about a robot' },
133
+ { stream: true }
134
+ ).result) {
135
+ process.stdout.write(chunk.response || '')
61
136
  }
62
- run().catch(console.error)
63
137
  ```
64
138
 
65
- ### Chat Completion
139
+ #### Event-Driven Execution
66
140
 
67
- This example shows how to create a chat completion with a simple message:
141
+ Monitor execution lifecycle with events. **Events are only useful with async executions** - synchronous and streaming executions complete too quickly for event-driven monitoring to be practical.
68
142
 
69
143
  ```typescript
70
- import { createTelaClient } from '@meistrari/tela-sdk-js'
71
- const tela = createTelaClient({
72
- apiKey: process.env.TELA_API_KEY,
144
+ const canvas = await tela.canvas.get({
145
+ id: 'canvas-id',
146
+ input: schema => schema.object({
147
+ query: schema.string()
148
+ }),
149
+ output: schema => schema.object({
150
+ result: schema.string()
151
+ }),
73
152
  })
74
- async function run() {
75
- const completion = await tela.completions.create({
76
- canvasId: process.env.TELA_CANVAS_ID,
77
- messages: [{ role: 'user', content: 'Hello!' }],
78
- })
79
- console.log(JSON.stringify(completion, null, 2))
153
+
154
+ // Start async execution to attach event listeners
155
+ const execution = await canvas.execute(
156
+ { query: 'What is the capital of France?' },
157
+ {
158
+ async: true,
159
+ pollingInterval: 1000,
160
+ pollingTimeout: 60000,
161
+ }
162
+ )
163
+
164
+ // Access current status at any time
165
+ console.log(execution.status) // 'created'
166
+
167
+ // Listen for status changes
168
+ execution.on('statusChange', (status) => {
169
+ console.log(`Status: ${status}`) // created → running → succeeded
170
+ })
171
+
172
+ // Listen for polling updates
173
+ execution.on('poll', (pollResult) => {
174
+ console.log(`Poll - Status: ${pollResult.status}`)
175
+ })
176
+
177
+ // Listen for successful completion
178
+ execution.on('success', (result) => {
179
+ console.log('Success!', result)
180
+ })
181
+
182
+ // Listen for errors (prevents throwing)
183
+ execution.on('error', (error) => {
184
+ console.error('Failed:', error.message)
185
+ // With error listener: error event is emitted, promise resolves to undefined
186
+ })
187
+
188
+ // Start event-driven polling (non-blocking)
189
+ execution.poll()
190
+
191
+ // Events will fire as polling progresses
192
+ // The result will be available in the success event callback
193
+ // Continue with other work while polling happens in the background...
194
+ ```
195
+
196
+ **Why Async Only?**
197
+
198
+ Events are designed for tracking progress over time. Synchronous executions complete immediately (blocking until done), so by the time you can access the execution object, all events have already fired. Streaming executions provide their own iteration mechanism via async generators, making events redundant.
199
+
200
+ **Status Property:**
201
+
202
+ The `status` property provides the current execution state:
203
+ - **Async**: `created` → `running` → `succeeded` or `failed`
204
+
205
+ Status is set to `succeeded` only after successful validation. If validation fails, status will be `failed` even if the API request succeeded.
206
+
207
+ **Available Events (Async Executions Only):**
208
+ - `success` - Emitted when execution completes successfully (after validation)
209
+ - `error` - Emitted on failure (if listeners exist, errors won't throw)
210
+ - `statusChange` - Emitted on status transitions
211
+ - `poll` - Emitted during polling (initial response + each poll)
212
+
213
+ **Error Handling:**
214
+ - **With error listener**: Errors emitted as events, promise resolves to `undefined`
215
+ - **Without error listener**: Errors throw and reject the promise
216
+
217
+ **Important:** You must call `execution.poll()` or `await execution.result` to start polling - events won't fire until polling begins.
218
+
219
+ See [Event Examples](./examples/events/) for detailed usage patterns.
220
+
221
+ #### Schema Validation
222
+
223
+ The Canvas API validates your inputs and outputs against the server schema:
224
+
225
+ ```typescript
226
+ const canvas = await tela.canvas.get({
227
+ id: 'canvas-id',
228
+ input: schema => schema.object({
229
+ text: schema.string(),
230
+ file: schema.file(), // TelaFile validator
231
+ settings: schema.object({
232
+ temperature: schema.number(),
233
+ maxTokens: schema.number(),
234
+ }),
235
+ }),
236
+ output: schema => schema.object({
237
+ analysis: schema.string(),
238
+ confidence: schema.number(),
239
+ }),
240
+ skipSchemaValidation: false, // Enable schema validation warnings during canvas retrieval (default)
241
+ })
242
+
243
+ // TypeScript will ensure you provide the correct types
244
+ const result = await canvas.execute({
245
+ text: 'Analyze this document',
246
+ file: TelaFile.create(blob, { name: 'data.pdf' }),
247
+ settings: {
248
+ temperature: 0.7,
249
+ maxTokens: 1000,
250
+ }
251
+ }).result
252
+
253
+ // Skip result validation to trust API response without Zod validation
254
+ const resultWithoutValidation = await canvas.execute(
255
+ {
256
+ text: 'Analyze this document',
257
+ file: TelaFile.create(blob, { name: 'data.pdf' }),
258
+ settings: {
259
+ temperature: 0.7,
260
+ maxTokens: 1000,
261
+ }
262
+ },
263
+ {
264
+ skipResultValidation: true // Skip Zod validation on results, just typecast
265
+ }
266
+ ).result
267
+ ```
268
+
269
+ #### Multiple Files
270
+
271
+ Process multiple files in a single canvas execution:
272
+
273
+ ```typescript
274
+ const canvas = await tela.canvas.get({
275
+ id: 'canvas-id',
276
+ input: schema => schema.object({
277
+ documents: schema.array(schema.file()),
278
+ instructions: schema.string()
279
+ }),
280
+ output: schema => schema.object({
281
+ comparison: schema.string()
282
+ }),
283
+ })
284
+
285
+ const result = await canvas.execute({
286
+ documents: [
287
+ TelaFile.create('https://example.com/doc1.pdf', { range: [0, 5] }),
288
+ TelaFile.create('https://example.com/doc2.pdf', { range: [0, 5] }),
289
+ TelaFile.create(localFileBlob, { name: 'doc3.pdf' })
290
+ ],
291
+ instructions: 'Compare these documents and identify key differences'
292
+ }).result
293
+
294
+ console.log(result.comparison)
295
+ ```
296
+
297
+ #### Webhook Notifications
298
+
299
+ Receive completion notifications via webhook for async executions:
300
+
301
+ ```typescript
302
+ const canvas = await tela.canvas.get({
303
+ id: 'canvas-id',
304
+ input: schema => schema.object({
305
+ document: schema.file(),
306
+ query: schema.string()
307
+ }),
308
+ output: schema => schema.object({
309
+ analysis: schema.string()
310
+ }),
311
+ })
312
+
313
+ // Execute with webhook notification - you can still poll if needed
314
+ const result = await canvas.execute(
315
+ {
316
+ document: TelaFile.create('https://example.com/large-report.pdf'),
317
+ query: 'Analyze this report'
318
+ },
319
+ {
320
+ async: true,
321
+ webhookUrl: 'https://your-server.com/webhook',
322
+ pollingTimeout: 300000, // 5 minutes
323
+ }
324
+ ).result
325
+ // OR wait for the webhook notification on your server instead of polling
326
+ ```
327
+
328
+ #### Execution Tags
329
+
330
+ Add tags to your canvas executions for filtering, categorization, and analytics:
331
+
332
+ ```typescript
333
+ const canvas = await tela.canvas.get({
334
+ id: 'canvas-id',
335
+ input: schema => schema.object({
336
+ query: schema.string()
337
+ }),
338
+ output: schema => schema.object({
339
+ result: schema.string()
340
+ }),
341
+ })
342
+
343
+ // Execute with tags (works with all execution modes)
344
+ const result = await canvas.execute(
345
+ { query: 'Analyze customer feedback' },
346
+ {
347
+ tags: ['production', 'analytics', 'customer-feedback']
348
+ }
349
+ ).result
350
+
351
+ // Tags work with async executions
352
+ const asyncResult = await canvas.execute(
353
+ { query: 'Process large dataset' },
354
+ {
355
+ async: true,
356
+ tags: ['batch-processing', 'analytics']
357
+ }
358
+ ).result
359
+
360
+ // Tags work with streaming executions
361
+ for await (const chunk of canvas.execute(
362
+ { query: 'Generate report' },
363
+ {
364
+ stream: true,
365
+ tags: ['streaming', 'reports']
366
+ }
367
+ ).result) {
368
+ process.stdout.write(chunk.result || '')
369
+ }
370
+ ```
371
+
372
+ **Use Cases:**
373
+ - **Environment tracking**: Tag executions by environment (`production`, `staging`, `development`)
374
+ - **Feature categorization**: Organize executions by feature (`analytics`, `reporting`, `summarization`)
375
+ - **User segmentation**: Track executions by user tier or department
376
+ - **Cost allocation**: Attribute API usage to specific projects or teams
377
+ - **Performance monitoring**: Filter and analyze execution metrics by tag
378
+
379
+ Tags are sent to the Tela API and can be used for filtering and analytics in your Tela dashboard.
380
+
381
+ #### Fetching Existing Executions
382
+
383
+ Retrieve and monitor async executions by their ID, useful for job queues, resuming workflows, or multi-user dashboards:
384
+
385
+ ```typescript
386
+ // Start an async execution
387
+ const execution = await canvas.execute(
388
+ { query: 'Process this data' },
389
+ { async: true }
390
+ )
391
+
392
+ // Store the execution ID (e.g., in a database)
393
+ const executionId = execution.id
394
+
395
+ // Later, fetch the execution by ID
396
+ const fetchedExecution = await canvas.getExecution(executionId, {
397
+ pollingInterval: 2000, // Optional: custom polling interval
398
+ pollingTimeout: 120000 // Optional: custom timeout
399
+ })
400
+
401
+ console.log(fetchedExecution.status) // 'running', 'succeeded', or 'failed'
402
+
403
+ // Use event-driven polling for non-blocking progress tracking
404
+ // Note: poll() works for ANY async execution, not just fetched ones!
405
+ fetchedExecution.on('statusChange', (status) => {
406
+ console.log(`Status: ${status}`)
407
+ })
408
+
409
+ fetchedExecution.on('success', (result) => {
410
+ console.log('Completed!', result)
411
+ })
412
+
413
+ fetchedExecution.on('error', (error) => {
414
+ console.error('Failed:', error)
415
+ })
416
+
417
+ // Start polling without blocking (returns immediately)
418
+ fetchedExecution.poll()
419
+
420
+ // Continue with other work while polling runs in background...
421
+ console.log('Polling in background, doing other work...')
422
+
423
+ // Later, you can still await the result if needed
424
+ const result = await fetchedExecution.result
425
+ ```
426
+
427
+ **Alternative: Use `poll()` on newly started executions**
428
+
429
+ You can also use event-driven polling immediately when starting an execution:
430
+
431
+ ```typescript
432
+ const execution = await canvas.execute(
433
+ { query: 'Process this data' },
434
+ { async: true }
435
+ )
436
+
437
+ // Set up event listeners
438
+ execution.on('statusChange', status => console.log('Status:', status))
439
+ execution.on('success', result => console.log('Done!', result))
440
+ execution.on('error', error => console.error('Failed:', error))
441
+
442
+ // Start non-blocking polling
443
+ execution.poll()
444
+
445
+ // Do other work...
446
+ console.log('Execution polling in background')
447
+ ```
448
+
449
+ **Key Features:**
450
+
451
+ - **`canvas.getExecution(id, options?)`** - Fetch execution by UUID
452
+ - Only async executions have UUIDs and can be fetched
453
+ - Returns execution in current state (running, succeeded, or failed)
454
+ - Supports custom polling options
455
+
456
+ - **`execution.poll()`** - Start event-driven polling
457
+ - Returns `void` (non-blocking)
458
+ - Emits events: `statusChange`, `poll`, `success`, `error`
459
+ - Perfect for UI updates, dashboards, and concurrent monitoring
460
+
461
+ **Important Notes:**
462
+
463
+ ⚠️ **Events require polling or awaiting `.result`**: Events are only emitted when polling is active. You must either:
464
+ - Call `execution.poll()` to start event-driven polling, OR
465
+ - Await `execution.result` which starts polling automatically
466
+
467
+ ```typescript
468
+ // ❌ Events won't fire - no polling started
469
+ const execution = await canvas.execute({ query: 'test' }, { async: true })
470
+ execution.on('success', result => console.log('Done')) // This will never fire!
471
+
472
+ // ✅ Correct - poll() starts event-driven polling
473
+ const execution = await canvas.execute({ query: 'test' }, { async: true })
474
+ execution.on('success', result => console.log('Done'))
475
+ execution.poll() // Now events will fire
476
+
477
+ // ✅ Also correct - awaiting result starts polling
478
+ const execution = await canvas.execute({ query: 'test' }, { async: true })
479
+ execution.on('success', result => console.log('Done'))
480
+ await execution.result // Polling starts, events fire
481
+ ```
482
+
483
+ ⚠️ **Already completed executions don't emit events**: If you fetch an execution that has already finished (succeeded or failed), the `success` and `error` events won't fire because there's no polling needed. Instead, check the status and access the result directly:
484
+
485
+ ```typescript
486
+ const execution = await canvas.getExecution(executionId)
487
+
488
+ if (execution.status === 'succeeded') {
489
+ // Access result directly - success event won't fire
490
+ const result = await execution.result
491
+ console.log('Already completed:', result)
492
+ }
493
+ else if (execution.status === 'failed') {
494
+ // Handle failure - error event won't fire
495
+ try {
496
+ await execution.result
497
+ }
498
+ catch (error) {
499
+ console.error('Already failed:', error)
500
+ }
501
+ }
502
+ else {
503
+ // Still running - events will fire when you start polling
504
+ execution.on('success', result => console.log('Completed!', result))
505
+ execution.on('error', error => console.error('Failed:', error))
506
+ execution.poll()
80
507
  }
81
- run().catch(console.error)
82
508
  ```
83
509
 
84
- ### Webhook Completion
510
+ **Use Cases:**
511
+ - Job queue management systems
512
+ - Resuming monitoring after page refresh or server restart
513
+ - Multi-user dashboards showing execution status
514
+ - Background processing with real-time updates
515
+ - Retry mechanisms with persistent execution IDs
516
+
517
+ See [Fetch and Poll Examples](./examples/events/) for detailed usage patterns.
518
+
519
+ ### File Handling
85
520
 
86
- This example demonstrates how to create a completion that sends the result to a webhook URL:
521
+ The `TelaFile` API provides flexible file handling with support for various input types:
87
522
 
88
523
  ```typescript
89
- import { createTelaClient } from '@meistrari/tela-sdk-js'
524
+ import { TelaFile } from '@meistrari/tela-sdk-js'
525
+
526
+ // From a Blob or File
527
+ const fileFromBlob = TelaFile.create(blob, { name: 'custom.pdf' })
528
+
529
+ // From a URL
530
+ const fileFromUrl = TelaFile.create('https://example.com/document.pdf')
90
531
 
91
- const tela = createTelaClient({
92
- apiKey: process.env.TELA_API_KEY,
532
+ // From bytes with explicit MIME type
533
+ const fileFromBytes = TelaFile.create(
534
+ new Uint8Array(['...']),
535
+ {
536
+ mimeType: 'application/pdf',
537
+ name: 'document.pdf'
538
+ }
539
+ )
540
+
541
+ // From a ReadableStream with explicit MIME type
542
+ const fileFromStream = TelaFile.create(
543
+ readableStream,
544
+ {
545
+ mimeType: 'image/jpeg',
546
+ name: 'photo.jpg'
547
+ }
548
+ )
549
+
550
+ // Vault reference (for files already uploaded)
551
+ const vaultFile = TelaFile.create('vault://file-id')
552
+
553
+ // With page range for PDFs
554
+ const fileWithRange = TelaFile.create(
555
+ 'https://example.com/document.pdf',
556
+ {
557
+ range: [0, 5], // Pages 0-5
558
+ parserType: 'pdf'
559
+ }
560
+ )
561
+ ```
562
+
563
+ ## Migration Guide from v1.x to v2
564
+
565
+ Version 2.0 of the Tela SDK introduces significant improvements to type safety, schema validation, and API design. This guide will help you migrate your code from v1.x to v2.
566
+
567
+ ### Breaking Changes
568
+
569
+ #### 1. API Surface Changed: `completions.create()` → `canvas.get()` + `execute()`
570
+
571
+ The v1 API used a single `completions.create()` method. V2 introduces a two-step pattern: first retrieve a canvas, then execute it.
572
+
573
+ **v1.x:**
574
+ ```typescript
575
+ const tela = new TelaSDK({ apiKey: 'your-api-key' })
576
+
577
+ const completion = await tela.completions.create({
578
+ canvasId: 'canvas-id',
579
+ variables: {
580
+ query: 'What is the capital of France?'
581
+ }
93
582
  })
94
583
 
95
- async function run() {
96
- const webhook = await tela.completions.create({
97
- canvasId: process.env.TELA_CANVAS_ID,
98
- variables: {
99
- document: tela.createFile('https://www.wmaccess.com/downloads/sample-invoice.pdf'),
100
- },
101
- webhookUrl: 'https://webhook.site/4294967295',
102
- stream: false,
584
+ console.log(completion.choices[0].message.content)
585
+ ```
586
+
587
+ **v2:**
588
+ ```typescript
589
+ const tela = new TelaSDK({ apiKey: 'your-api-key' })
590
+
591
+ // First, get the canvas
592
+ const canvas = await tela.canvas.get({
593
+ id: 'canvas-id'
594
+ })
595
+
596
+ // Then execute it
597
+ const result = await canvas.execute({
598
+ query: 'What is the capital of France?'
599
+ }).result
600
+
601
+ console.log(result)
602
+ ```
603
+
604
+ #### 2. Schema Validation and Type Safety (Optional)
605
+
606
+ V2 introduces **optional** runtime and compile-time type safety with Zod schema validation. Schemas validate data at runtime (catching invalid data) and provide TypeScript types at compile-time. You can use the SDK without schemas if you prefer.
607
+
608
+ **v1.x (no schema validation):**
609
+ ```typescript
610
+ const completion = await tela.completions.create<
611
+ { query: string },
612
+ { result: string }
613
+ >({
614
+ canvasId: 'canvas-id',
615
+ variables: {
616
+ query: 'Hello'
617
+ }
618
+ })
619
+ ```
620
+
621
+ **v2 (without schemas - works just like v1.x):**
622
+ ```typescript
623
+ const canvas = await tela.canvas.get({
624
+ id: 'canvas-id'
625
+ })
626
+
627
+ const result = await canvas.execute({ query: 'Hello' }).result
628
+ // result is typed as unknown, no runtime validation
629
+ ```
630
+
631
+ **v2 (with optional schema validation for runtime + compile-time safety):**
632
+ ```typescript
633
+ const canvas = await tela.canvas.get({
634
+ id: 'canvas-id',
635
+ input: schema => schema.object({
636
+ query: schema.string()
637
+ }),
638
+ output: schema => schema.object({
639
+ result: schema.string()
640
+ })
641
+ })
642
+
643
+ // Runtime validation ensures data matches schema
644
+ // TypeScript knows the exact types at compile-time
645
+ const result = await canvas.execute({ query: 'Hello' }).result
646
+ // result.result is a string (validated and typed)
647
+ ```
648
+
649
+ #### 3. File Handling Changes
650
+
651
+ The `TelaFile` constructor now requires `mimeType` for `Uint8Array` and `ReadableStream` inputs (for compatibility with storage solutions).
652
+
653
+ **v1.x:**
654
+ ```typescript
655
+ import { TelaFile } from '@meistrari/tela-sdk-js'
656
+
657
+ const file = new TelaFile(blob, { range: [0, 5] })
658
+ const fileFromBytes = new TelaFile(new Uint8Array([/* ... */])) // mimeType optional
659
+ ```
660
+
661
+ **v2:**
662
+ ```typescript
663
+ import { TelaFile } from '@meistrari/tela-sdk-js'
664
+
665
+ // No change needed for Blob, File, or URL strings
666
+ const file = new TelaFile(blob, { range: [0, 5] })
667
+
668
+ // For Uint8Array or ReadableStream, mimeType is now required:
669
+ const fileFromBytes = new TelaFile(
670
+ new Uint8Array([/* ... */]),
671
+ {
672
+ mimeType: 'application/pdf',
673
+ name: 'document.pdf'
674
+ }
675
+ )
676
+
677
+ // Alternative: use tela.createFile() helper
678
+ const fileFromStream = tela.createFile(readableStream, { mimeType: 'image/jpeg' })
679
+ ```
680
+
681
+ #### 4. Streaming API Changes
682
+
683
+ Streaming now returns an async generator directly via the `.result` property.
684
+
685
+ **v1.x:**
686
+ ```typescript
687
+ const stream = await tela.completions.create({
688
+ canvasId: 'canvas-id',
689
+ stream: true,
690
+ variables: { query: 'Tell me a story' }
691
+ })
692
+
693
+ for await (const chunk of stream) {
694
+ process.stdout.write(chunk.message.content || '')
695
+ }
696
+ ```
697
+
698
+ **v2:**
699
+ ```typescript
700
+ const canvas = await tela.canvas.get({
701
+ id: 'canvas-id',
702
+ input: schema => schema.object({
703
+ query: schema.string()
704
+ }),
705
+ output: schema => schema.object({
706
+ response: schema.string()
103
707
  })
104
- console.log(JSON.stringify(webhook, null, 2))
708
+ })
709
+
710
+ const execution = canvas.execute(
711
+ { query: 'Tell me a story' },
712
+ { stream: true }
713
+ )
714
+
715
+ for await (const chunk of execution.result) {
716
+ process.stdout.write(chunk.response || '')
105
717
  }
106
- run().catch(console.error)
107
718
  ```
108
719
 
109
- ### Streaming Completion
720
+ #### 5. Async Execution with Polling
110
721
 
111
- This example shows how to handle streaming responses:
722
+ V2 introduces async execution with automatic polling. V1.x only supported synchronous execution (the request would block until completion).
112
723
 
724
+ **v1.x:**
113
725
  ```typescript
114
- import fs from 'node:fs'
115
- import { createTelaClient } from '@meistrari/tela-sdk-js'
116
- import type { TelaFile } from '@meistrari/tela-sdk-js'
726
+ // Synchronous only - blocks until the execution completes
727
+ const response = await tela.completions.create({
728
+ canvasId: 'canvas-id',
729
+ variables: { query: 'Analyze this' }
730
+ })
731
+ // This could take a very long time for long-running executions
732
+ console.log(response.choices[0].message.content)
733
+ ```
117
734
 
118
- const tela = createTelaClient({
119
- apiKey: process.env.TELA_API_KEY,
735
+ **v2:**
736
+ ```typescript
737
+ const canvas = await tela.canvas.get({
738
+ id: 'canvas-id',
739
+ input: schema => schema.object({ query: schema.string() }),
740
+ output: schema => schema.object({ result: schema.string() })
120
741
  })
121
742
 
122
- const PATH = 'examples/stream/sample-invoice.pdf'
123
- const fileStream = fs.createReadStream(PATH)
743
+ // Automatic polling
744
+ const execution = canvas.execute(
745
+ { query: 'Analyze this' },
746
+ {
747
+ async: true,
748
+ pollingInterval: 1000, // Poll every 1 second
749
+ pollingTimeout: 60000, // Timeout after 60 seconds
750
+ webhookUrl: 'https://your-server.com/webhook' // Optional
751
+ }
752
+ )
124
753
 
125
- async function run() {
126
- const completion = await tela.completions.create<{ document: TelaFile }, { summary: string }>({
127
- canvasId: process.env.TELA_CANVAS_ID,
128
- variables: {
129
- document: tela.createFile(fileStream),
130
- },
131
- stream: true,
754
+ const result = await execution.result
755
+ console.log(result.result)
756
+ ```
757
+
758
+ #### 6. Response Structure Changes
759
+
760
+ The response structure has been simplified.
761
+
762
+ **v1.x:**
763
+ ```typescript
764
+ const completion = await tela.completions.create({
765
+ canvasId: 'canvas-id',
766
+ variables: { query: 'Hello' }
767
+ })
768
+
769
+ // Access via nested structure
770
+ console.log(completion.choices[0].message.content)
771
+ ```
772
+
773
+ **v2:**
774
+ ```typescript
775
+ const canvas = await tela.canvas.get({
776
+ id: 'canvas-id',
777
+ output: schema => schema.object({
778
+ answer: schema.string()
132
779
  })
133
- for await (const chunk of completion) {
134
- console.log(JSON.stringify(chunk))
780
+ })
781
+
782
+ const response = await canvas.execute({ query: 'Hello' }).result
783
+ // Direct access to structured output
784
+ console.log(response.answer)
785
+ ```
786
+
787
+ ### Migration Checklist
788
+
789
+ - [ ] Replace `tela.completions.create()` with `tela.canvas.get()` + `canvas.execute()`
790
+ - [ ] (Optional) Add input/output schemas using the `schema` builder function for type safety
791
+ - [ ] Add `mimeType` parameter when creating `TelaFile` from `Uint8Array` or `ReadableStream`
792
+ - [ ] Update streaming code to use `.result` property for iteration
793
+ - [ ] Update async execution to use polling parameters instead of manual polling
794
+ - [ ] Update response access patterns to match new structure
795
+ - [ ] (If using schemas) Test schema validation warnings to ensure schemas match server configuration
796
+
797
+ ### New Features in v2
798
+
799
+ #### Event-Driven Execution Monitoring
800
+
801
+ V2 introduces a comprehensive event system for monitoring execution lifecycle across all execution modes (sync, async, streaming):
802
+
803
+ ```typescript
804
+ const execution = await canvas.execute(
805
+ { query: 'Analyze this' },
806
+ { async: true }
807
+ )
808
+
809
+ // Monitor progress with events
810
+ execution.on('statusChange', (status) => {
811
+ console.log(`Status: ${status}`)
812
+ })
813
+
814
+ execution.on('poll', (pollResult) => {
815
+ console.log(`Polling - ${pollResult.status}`)
816
+ })
817
+
818
+ execution.on('success', (result) => {
819
+ console.log('Completed!', result)
820
+ })
821
+
822
+ // Graceful error handling
823
+ execution.on('error', (error) => {
824
+ console.error('Failed:', error.message)
825
+ // With listener: no throw, promise resolves to undefined
826
+ })
827
+
828
+ const result = await execution.result
829
+ ```
830
+
831
+ **Available Events:**
832
+ - `success` - Completion with validated result
833
+ - `error` - Failure notification (prevents throwing if listener exists)
834
+ - `statusChange` - Status transitions
835
+ - `poll` - Polling updates (async only)
836
+
837
+ See [Event Examples](./examples/events/) for comprehensive patterns and use cases.
838
+
839
+ #### Promise-like Execution API
840
+
841
+ V2 introduces a flexible execution API that supports both direct result access and execution object retrieval:
842
+
843
+ ```typescript
844
+ // Direct result access (recommended for simple cases)
845
+ const result = await canvas.execute({ query: 'Hello' }).result
846
+
847
+ // Get execution object for more control
848
+ const execution = await canvas.execute({ query: 'Hello' })
849
+ const result2 = await execution.result
850
+ ```
851
+
852
+ #### Schema Validation Warnings
853
+
854
+ V2 validates your schemas against the server configuration and warns you about mismatches:
855
+
856
+ ```typescript
857
+ const canvas = await tela.canvas.get({
858
+ id: 'canvas-id',
859
+ input: schema => schema.object({
860
+ wrongField: schema.string() // Will warn if not in server schema
861
+ }),
862
+ skipSchemaValidation: false // Enable warnings (default)
863
+ })
864
+ ```
865
+
866
+ #### Vault File References
867
+
868
+ V2 supports vault file references:
869
+
870
+ ```typescript
871
+ const vaultFile = new TelaFile('vault://file-id')
872
+ ```
873
+
874
+ #### Skip Result Validation
875
+
876
+ When you have schemas defined, you can skip runtime validation for better performance (you'll still get TypeScript types):
877
+
878
+ ```typescript
879
+ const canvas = await tela.canvas.get({
880
+ id: 'canvas-id',
881
+ input: schema => schema.object({
882
+ query: schema.string()
883
+ }),
884
+ output: schema => schema.object({
885
+ answer: schema.string()
886
+ })
887
+ })
888
+
889
+ const execution = canvas.execute(
890
+ { query: 'Hello' },
891
+ { skipResultValidation: true } // Skip runtime validation, trust API response
892
+ )
893
+
894
+ // Still typed as { answer: string }, but no validation at runtime
895
+ const result = await execution.result
896
+ ```
897
+
898
+ #### Access Raw API Responses
899
+
900
+ V2 provides access to the raw, unprocessed API response alongside the parsed result:
901
+
902
+ ```typescript
903
+ const canvas = await tela.canvas.get({
904
+ id: 'canvas-id',
905
+ output: schema => schema.object({
906
+ answer: schema.string()
907
+ })
908
+ })
909
+
910
+ const execution = await canvas.execute({ query: 'Hello' })
911
+
912
+ // Get the parsed, validated result
913
+ const result = await execution.result
914
+ console.log(result.answer) // Type-safe access
915
+
916
+ // Get the raw API response
917
+ const rawResult = await execution.rawResult
918
+ console.log(rawResult) // Complete API response with all metadata
919
+
920
+ // You can await either one first - both will be available
921
+ const raw = await execution.rawResult // Triggers execution
922
+ const parsed = await execution.result // Reuses same execution
923
+ ```
924
+
925
+ This is useful when you need:
926
+ - Full API response metadata (execution IDs, timestamps, etc.)
927
+ - Debugging and logging complete responses
928
+ - Access to data not in your schema
929
+
930
+ #### Fetch Existing Executions and Event-Driven Polling
931
+
932
+ V2 introduces the ability to fetch existing async executions by ID and monitor them with event-driven polling:
933
+
934
+ ```typescript
935
+ // Start an async execution
936
+ const execution = await canvas.execute(
937
+ { query: 'Process this data' },
938
+ { async: true }
939
+ )
940
+
941
+ // Store the execution ID (e.g., in a database)
942
+ const executionId = execution.id
943
+
944
+ // Later, fetch the execution by ID
945
+ const fetchedExecution = await canvas.getExecution(executionId, {
946
+ pollingInterval: 2000,
947
+ pollingTimeout: 120000
948
+ })
949
+
950
+ // Use event-driven polling (non-blocking)
951
+ fetchedExecution.on('statusChange', (status) => {
952
+ console.log(`Status changed: ${status}`)
953
+ })
954
+
955
+ fetchedExecution.on('success', (result) => {
956
+ console.log('Completed!', result)
957
+ })
958
+
959
+ fetchedExecution.on('error', (error) => {
960
+ console.error('Failed:', error)
961
+ })
962
+
963
+ // Start polling without blocking
964
+ fetchedExecution.poll()
965
+
966
+ // Continue with other work...
967
+ console.log('Polling in background')
968
+ ```
969
+
970
+ This enables powerful use cases:
971
+ - **Job queue management**: Start jobs, store IDs, fetch later to check status
972
+ - **Resumable workflows**: Continue monitoring after page refresh or server restart
973
+ - **Multi-user dashboards**: Monitor executions started by different users
974
+ - **Real-time UI updates**: Non-blocking polling with event listeners
975
+ - **Background processing**: Track multiple executions concurrently
976
+
977
+ See [Fetch and Poll Examples](./examples/events/) for detailed patterns.
978
+
979
+ #### Execution Tags
980
+
981
+ V2 introduces support for tagging executions for filtering, categorization, and analytics:
982
+
983
+ ```typescript
984
+ // Add tags to any execution mode
985
+ const result = await canvas.execute(
986
+ { query: 'Analyze customer feedback' },
987
+ {
988
+ tags: ['production', 'analytics', 'customer-feedback']
135
989
  }
990
+ ).result
991
+
992
+ // Tags work with async executions
993
+ const asyncResult = await canvas.execute(
994
+ { query: 'Process large dataset' },
995
+ {
996
+ async: true,
997
+ tags: ['batch-processing', 'analytics']
998
+ }
999
+ ).result
1000
+
1001
+ // Tags work with streaming executions
1002
+ for await (const chunk of canvas.execute(
1003
+ { query: 'Generate report' },
1004
+ {
1005
+ stream: true,
1006
+ tags: ['streaming', 'reports']
1007
+ }
1008
+ ).result) {
1009
+ process.stdout.write(chunk.result || '')
136
1010
  }
137
- run().catch(console.error)
138
1011
  ```
139
1012
 
140
- ## API Reference
1013
+ **Use Cases:**
1014
+ - **Environment tracking**: Tag executions by environment (`production`, `staging`, `development`)
1015
+ - **Feature categorization**: Organize executions by feature (`analytics`, `reporting`, `summarization`)
1016
+ - **User segmentation**: Track executions by user tier or department
1017
+ - **Cost allocation**: Attribute API usage to specific projects or teams
1018
+ - **Performance monitoring**: Filter and analyze execution metrics by tag
141
1019
 
142
- ### `createTelaClient`
1020
+ #### Application Execution Labels
143
1021
 
144
- #### `createTelaClient(options: TelaSDKOptions)`
1022
+ V2 adds support for setting workspace task titles when using deployed applications via `applicationId`:
145
1023
 
146
- Creates a new instance of the TelaSDK.
1024
+ ```typescript
1025
+ const canvas = await tela.canvas.get({
1026
+ applicationId: 'app-id' // Using deployed application instead of canvas ID
1027
+ })
147
1028
 
148
- - `options` (TelaSDKOptions): Configuration options for the SDK.
1029
+ // Set the title of the workspace task
1030
+ const result = await canvas.execute(
1031
+ { query: 'Process request' },
1032
+ {
1033
+ label: 'Customer Dashboard Query'
1034
+ }
1035
+ ).result
1036
+ ```
149
1037
 
150
- #### `completions.create<CanvasVariables, CanvasStructuredOutput>(params: CompletionCreateParams<CanvasVariables>, opts?: RequestOptions): Promise<CompletionCreateResponse<CanvasStructuredOutput>>`
1038
+ When you execute a canvas using `applicationId`, it creates a task in the application's workspace. The `label` field sets the title of that workspace task, making it easier to identify and track executions in your Tela workspace.
151
1039
 
152
- Creates a chat completion with various input options and response formats.
1040
+ **Note:** The `label` field is only applicable when using `applicationId`. A warning will be logged if you provide a label without an `applicationId`.
153
1041
 
154
- - `params` (CompletionCreateParams<CanvasVariables>): The parameters for creating a chat completion.
155
- - `opts` (RequestOptions): Additional request options.
1042
+ **Use Cases:**
1043
+ - **Task identification**: Give meaningful titles to workspace tasks for easier tracking
1044
+ - **Execution categorization**: Organize tasks by feature or workflow in the workspace
1045
+ - **User-friendly naming**: Display clear task names instead of generic execution IDs
1046
+ - **Dashboard clarity**: Make workspace task lists more readable and searchable
156
1047
 
157
- ## Contributing
1048
+ ### Need Help?
158
1049
 
159
- We welcome contributions to the Tela SDK! Please see our [contributing guide](CONTRIBUTING.md) for more information.
1050
+ If you encounter issues during migration, please:
1051
+ - Check the [examples](./examples/) directory for updated usage patterns
1052
+ - Review the [API documentation](./docs/)
1053
+ - Open an issue at [GitHub Issues](https://github.com/meistrari/tela-sdk-js/issues)