@blocks-network/sdk 0.1.47 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +1 -621
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,621 +1 @@
1
- # @blocks-network/sdk
2
-
3
- Blocks Network SDK for Node.js -- build and run A2A agents on Blocks Network.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @blocks-network/sdk
9
- ```
10
-
11
- ## Quick Start
12
-
13
- Create a `handler.ts` file:
14
-
15
- ```typescript
16
- import type { TaskContext } from '@blocks-network/sdk';
17
-
18
- export default async function handler(task: any, ctx: TaskContext) {
19
- const input = task.requestParts?.[0]?.text ?? '';
20
- ctx.reportStatus('working', `Processing: ${input}`);
21
- return { artifacts: [{ data: `Echo: ${input}`, mimeType: 'text/plain' }] };
22
- }
23
- ```
24
-
25
- Create an `agent-card.json`:
26
-
27
- ```json
28
- {
29
- "name": "My Agent",
30
- "description": "An example agent",
31
- "version": "1.0.0",
32
- "provider": { "organization": "Your Org" },
33
- "defaultInputModes": ["application/json"],
34
- "defaultOutputModes": ["text/plain"],
35
- "capabilities": { "streaming": false },
36
- "skills": [{ "id": "main", "name": "Main Skill" }],
37
- "runtime": {
38
- "handler": "./handler.ts"
39
- }
40
- }
41
- ```
42
-
43
- ## Running Agents
44
-
45
- The canonical way to run an agent is via the
46
- [Blocks CLI](../../cli/README.md):
47
-
48
- ```bash
49
- blocks run
50
- ```
51
-
52
- This validates `agent-card.json`, loads `.env`, and starts the agent
53
- runtime. The CLI delegates to the SDK's `blocks-run` binary under the
54
- hood.
55
-
56
- For direct invocation without the CLI:
57
-
58
- ```bash
59
- npx blocks-run
60
- ```
61
-
62
- Both approaches load environment variables from `.env` in the current
63
- working directory. Run `blocks publish` first to populate `BLOCKS_API_KEY`.
64
-
65
- ## agent-card.json
66
-
67
- The agent card follows the A2A specification with a `runtime` extension:
68
-
69
- ```json
70
- {
71
- "name": "My Agent",
72
- "description": "An example agent",
73
- "version": "1.0.0",
74
- "provider": { "organization": "Your Org" },
75
- "defaultInputModes": ["application/json"],
76
- "defaultOutputModes": ["text/plain"],
77
- "capabilities": { "streaming": false },
78
- "skills": [{ "id": "main", "name": "Main Skill" }],
79
- "runtime": {
80
- "handler": "./handler.ts",
81
- "handlerExport": "default",
82
- "concurrency": 1
83
- }
84
- }
85
- ```
86
-
87
- Note: `identity.agentName` must use only alphanumeric characters and
88
- underscores (no hyphens). The pattern is `^[a-zA-Z0-9_]+$`.
89
-
90
- ## API
91
-
92
- ### `await startAgentInstance(options)`
93
-
94
- Start an agent instance with full control over configuration. This is
95
- the primary runtime API.
96
-
97
- ## Authentication
98
-
99
- The SDK reads `BLOCKS_API_KEY` from the environment and uses it to
100
- authenticate with the backend. Set this in your `.env` file. The
101
- Go CLI's `blocks publish` command generates the API key and writes
102
- it to `.env` automatically. `blocks login` can also do this when
103
- invoked with `--write-env` (or by answering "y" to its prompt).
104
-
105
- PAM tokens for channel access are managed by the SDK at runtime
106
- (granted at registration, refreshed per-task on the control channel).
107
- No CLI involvement is needed for PAM.
108
-
109
- ## Examples
110
-
111
- For complete, runnable example agents, see the
112
- [Node examples](../../examples/node/README.md). Examples cover
113
- request/response, streaming, orchestration, pipe tasks, and advanced
114
- wrapper patterns.
115
-
116
- ## Consumer API
117
-
118
- The SDK provides a consumer-side API for submitting tasks, connecting to
119
- existing tasks, handling events, and downloading artifacts.
120
-
121
- ### `TaskClient.create(options)`
122
-
123
- Static async factory that creates a configured `TaskClient` from
124
- environment variables or CDM config.
125
-
126
- ```typescript
127
- const client = await TaskClient.create({ listing: 'playground' });
128
- ```
129
-
130
- - `listing` (required): `'playground'`, `'private'`, or `'public'`
131
- - Resolution: explicit options > `BLOCKS_*` env vars > CDM config
132
- - Auth: one of the token provider modes below
133
-
134
- ### Token Provider Modes
135
-
136
- `TaskClient.create()` supports three token provider modes for automatic
137
- token acquisition and refresh.
138
-
139
- **Mode 1: API key (server-side)**
140
-
141
- ```typescript
142
- const client = await TaskClient.create({
143
- listing: 'playground',
144
- apiKey: process.env.BLOCKS_API_KEY,
145
- onAuthError: (err) => console.error('Auth failed:', err.message),
146
- });
147
- ```
148
-
149
- The SDK exchanges the API key for a short-lived JWT via
150
- `POST /api/v1/auth/agent/consumer-token` and refreshes it
151
- automatically at 80% of its TTL. Use this for backend services, scripts,
152
- and cron jobs.
153
-
154
- **Mode 2: Token endpoint (client-side proxy)**
155
-
156
- Simplest form — a bare URL string. The SDK sends `POST <url>` with
157
- `Content-Type: application/json` and body `{}`:
158
-
159
- ```typescript
160
- const client = await TaskClient.create({
161
- listing: 'playground',
162
- tokenEndpoint: '/api/blocks-token',
163
- });
164
- ```
165
-
166
- Config-object form — pass a `TokenEndpointConfig` when your proxy
167
- needs cookies, custom headers, or a non-empty request body. Every
168
- field is optional except `url`:
169
-
170
- ```typescript
171
- import type { TokenEndpointConfig } from '@blocks-network/sdk';
172
-
173
- const tokenEndpoint: TokenEndpointConfig = {
174
- url: '/api/blocks-token',
175
- credentials: 'include', // send session cookies
176
- headers: { 'X-CSRF-Token': readCsrfMeta() }, // merged with Content-Type
177
- body: { sessionId: getCurrentSessionId() }, // replaces the default {}
178
- };
179
-
180
- const client = await TaskClient.create({
181
- listing: 'playground',
182
- tokenEndpoint,
183
- });
184
- ```
185
-
186
- `credentials` accepts any of `'include' | 'same-origin' | 'omit'`
187
- (matches `fetch`'s `RequestCredentials`). User-supplied `headers`
188
- merge on top of the SDK default `Content-Type: application/json`
189
- (user values win). `body` is JSON-serialized and replaces the default
190
- empty-object body. The same config is used for both the initial
191
- token acquisition and subsequent refreshes.
192
-
193
- The SDK sends POST requests whenever it needs a token. The endpoint
194
- identifies the caller, mints a Blocks consumer JWT, and returns
195
- `{ token, expiresIn, userId }`. The endpoint must include `userId`
196
- so `client.getUserId()` works. No long-lived credential ever reaches
197
- the client.
198
-
199
- `tokenEndpoint` has two first-class deployment shapes:
200
-
201
- 1. **Customer-owned backend proxy.** Your own service holds the
202
- Blocks API key, authenticates the browser caller however you
203
- choose (session cookie / OAuth / etc.), and forwards to the
204
- Blocks backend's `POST /api/v1/auth/agent/consumer-token`.
205
- 2. **Dashboard embedder (`afui_mvp` pattern).** The Blocks backend's
206
- own `POST /api/v1/auth/agent/consumer-token` endpoint, called
207
- directly from a signed-in dashboard with the user's session
208
- cookie plus `X-Active-Org` and `X-CSRF-Token` headers. No proxy,
209
- no API key in the browser. See `dev_docs/SDK_CONTRACT.md` §8.6.4g
210
- for the full wiring.
211
-
212
- Both shapes speak the same Mode 2 contract and are consumed
213
- uniformly by this SDK.
214
-
215
- > **Node/Python asymmetry.** `credentials` is Node-only because Python's
216
- > `urllib.request` has no equivalent of `fetch`'s credentials mode.
217
- > Python consumers pass cookies explicitly via
218
- > `headers={'Cookie': 'session=...'}`. See the
219
- > [Python README](../python/README.md) for the parity recipe.
220
-
221
- **Mode 3: Custom function**
222
-
223
- ```typescript
224
- const client = await TaskClient.create({
225
- listing: 'playground',
226
- tokenProvider: async () => {
227
- const resp = await fetch('/api/my-auth');
228
- const { token, expiresIn, userId } = await resp.json();
229
- return { token, expiresIn, userId };
230
- },
231
- });
232
- ```
233
-
234
- For OAuth2, custom SSO, or any auth architecture. The function is
235
- called on init and before each expiry.
236
-
237
- **Refresh and error handling**
238
-
239
- All modes refresh proactively at 80% TTL and reactively on HTTP 401.
240
- On 3 consecutive failures, `onAuthError` fires. The stale token
241
- remains usable until the next 401. `client.destroy()` stops the
242
- refresh timer but does not invalidate the current token.
243
-
244
- `ownerId` is auto-populated from the authenticated identity when
245
- omitted. Explicit `ownerId` still works and overrides the default.
246
- The backend rejects mismatches between `ownerId` and the
247
- authenticated identity.
248
-
249
- ### `client.connect({ taskId })`
250
-
251
- Connect to an existing task. Returns a `TaskSession` pre-populated with
252
- stream refs, artifact refs, and task state from history.
253
-
254
- ```typescript
255
- const session = await client.connect({ taskId: 'task-abc-123' });
256
- ```
257
-
258
- - Requires an authenticated `TaskClient` (for example one created with
259
- `apiKey`, `tokenEndpoint`, or `tokenProvider`). Fails with a clear
260
- error if not set.
261
- - Terminal tasks: session is not subscribed, read state via
262
- `listArtifacts()` and `session.state`.
263
- - Active tasks: session subscribes, live events flow through callbacks.
264
-
265
- ### `session.listArtifacts()`
266
-
267
- Returns all `ArtifactRef` instances seen so far (from history and live
268
- events).
269
-
270
- ```typescript
271
- const artifacts: ArtifactRef[] = session.listArtifacts();
272
- ```
273
-
274
- ### `session.downloadArtifact(ref)`
275
-
276
- Download an artifact. Handles inline (base64) and file-backed artifacts
277
- transparently.
278
-
279
- ```typescript
280
- const result: DownloadedArtifact = await session.downloadArtifact(ref);
281
- // result.data: Uint8Array, result.mimeType: string, result.fileName?: string
282
- ```
283
-
284
- Also available as a standalone function: `downloadArtifact(ref, client)`.
285
-
286
- ### `session.onError(cb)`
287
-
288
- Register a handler for callback errors. Returns an `Unsubscribe`
289
- function.
290
-
291
- ```typescript
292
- const unsub = session.onError((error, context) => {
293
- console.error(`Error in ${context.callbackType}:`, error.message);
294
- });
295
- ```
296
-
297
- `CallbackErrorContext` includes `entryPoint`, `callbackType`, and
298
- `event`. Without `onError` handlers, callback errors are logged at warn
299
- level.
300
-
301
- ### `session.waitForTerminal(timeoutMs?)`
302
-
303
- Wait for a terminal event. Returns a `Promise<TerminalEvent>`. Resolves
304
- immediately for already-terminal sessions (pre-closed idempotent hits,
305
- terminal `connect()`).
306
-
307
- ```typescript
308
- import { TaskClient, textPart } from '@blocks-network/sdk';
309
-
310
- const client = await TaskClient.create({ listing: 'playground', apiKey });
311
- const session = await client.sendMessage({
312
- agentName: 'acme_echo',
313
- requestParts: [textPart('Hello')],
314
- });
315
- // ownerId auto-populated from auth
316
- session.onProgress((e) => console.log(e.message));
317
- const terminal = await session.waitForTerminal(60_000);
318
- console.log('Completed:', terminal.state);
319
- await session.saveArtifacts('./artifacts');
320
- session.close();
321
- client.destroy();
322
- ```
323
-
324
- ### `session.saveArtifacts(dir)`
325
-
326
- Download all accumulated artifacts to a directory. Creates the directory
327
- if it does not exist. Returns `Promise<string[]>` of written file paths.
328
-
329
- ### `client.getAgentCard(agentName)`
330
-
331
- Fetch an agent's card from the registry. Returns `Promise<AgentCard | null>`.
332
-
333
- ### Part Helpers
334
-
335
- ```typescript
336
- import { textPart, filePart, filePartFromPath } from '@blocks-network/sdk';
337
-
338
- const parts = [
339
- textPart('Hello'),
340
-
341
- // Universal, browser-safe — accepts Uint8Array, ArrayBuffer, Blob, File:
342
- filePart(new Uint8Array([1, 2, 3]), { fileName: 'raw.bin' }),
343
-
344
- // Browser consumers hand a File straight from <input type="file">:
345
- // filePart(fileInput.files[0]),
346
- // Blob works too:
347
- // filePart(new Blob([bytes], { type: 'image/png' })),
348
-
349
- // Node-only — reads from disk via a lazy `node:fs` import (async):
350
- await filePartFromPath('./data.csv'),
351
- ];
352
- ```
353
-
354
- `filePart` is synchronous and has no `node:fs` dependency, so the
355
- package is safe to import from browser bundles. The legacy
356
- `filePart('./path')` signature is gone — path-based construction
357
- now lives on `filePartFromPath`, which bundlers targeting `browser`
358
- will error on because of the lazy `node:fs` import. `Buffer` values
359
- continue to work as `filePart` input at runtime because `Buffer`
360
- extends `Uint8Array`.
361
-
362
- ### Stream Consumer APIs
363
-
364
- All consumer iterators (`bytes()`, `events()`, `readable()`, `inbound`) deliver messages in sequence order. The SDK's reorder buffer transparently corrects out-of-order delivery and drops duplicate messages. To customize or disable reordering, pass `reorderTimeoutMs` to `open()`:
365
-
366
- ```typescript
367
- // Default: reorder buffer with 750ms gap timeout
368
- const stream = ref.open();
369
-
370
- // Custom timeout
371
- const stream = ref.open({ reorderTimeoutMs: 2000 });
372
-
373
- // Disable reordering (legacy arrival-order passthrough)
374
- const stream = ref.open({ reorderTimeoutMs: 0 });
375
- ```
376
-
377
- ```typescript
378
- // Decoded byte iterator (yields Uint8Array, browser-safe)
379
- for await (const chunk of stream.bytes()) {
380
- process.stdout.write(chunk); // Node
381
- }
382
-
383
- // Same iterator, browser-friendly text decoding
384
- const decoder = new TextDecoder();
385
- let text = '';
386
- for await (const chunk of stream.bytes()) {
387
- text += decoder.decode(chunk, { stream: true });
388
- }
389
-
390
- // Flattened event iterator (browser-safe)
391
- for await (const event of stream.events<MyEventType>()) {
392
- console.log(event);
393
- }
394
-
395
- // Node-only: Readable adapter for pipe() integration. Do not call
396
- // in browser bundles — returns a Node.js `Readable`.
397
- const readable = await stream.readable();
398
- readable.pipe(createWriteStream('./output.bin'));
399
- ```
400
-
401
- ### Handling Stream Errors
402
-
403
- Every `StreamClient` exposes an `onError(cb)` registration method.
404
- The callback fires whenever the stream's subscribe loop surfaces an
405
- error-category status event: PAM revocation, network issues,
406
- timeouts, etc. The payload is a typed `StreamError`:
407
-
408
- ```typescript
409
- import type { StreamError } from '@blocks-network/sdk/stream';
410
-
411
- const stream = ref.open();
412
- stream.onError((err: StreamError) => {
413
- console.warn(
414
- `[stream] ${err.category} fatal=${err.fatal} channel=${err.channel}`,
415
- );
416
- });
417
-
418
- for await (const chunk of stream.bytes()) {
419
- process.stdout.write(chunk);
420
- }
421
- ```
422
-
423
- Two categories are **fatal** and cause the SDK to force-terminate
424
- the stream so `for await` / `for msg in ...` loops exit cleanly
425
- instead of hanging waiting for a `stream_end` that will never
426
- arrive:
427
-
428
- - `PNAccessDeniedCategory` — PAM revocation (admin-terminate,
429
- token denied). This is the signal that the server-side grant is
430
- gone even if the cached T7c's `exp` claim has not elapsed.
431
- - `PNBadRequestCategory` — auth configuration or malformed grant.
432
-
433
- All other error categories (network transients, timeouts, etc.)
434
- fire `onError` with `fatal: false` and leave the stream running so
435
- the built-in retry machinery can recover.
436
-
437
- ### Opening Task Streams
438
-
439
- On an active task, `StreamRef.open()` is the standard way to
440
- subscribe to a stream. Use `onStream((ref) => { const s = ref.open(); ... })`
441
- to open streams reactively as they are announced, or call
442
- `session.openAllStreams()` once to open every readable stream in one
443
- shot:
444
-
445
- ```typescript
446
- import { TaskClient, textPart } from '@blocks-network/sdk';
447
-
448
- const session = await client.sendMessage({
449
- agentName: 'multi_stream_agent',
450
- requestParts: [textPart('start')],
451
- });
452
-
453
- // Option 1 — react to each stream as it is announced
454
- session.onStream((ref) => {
455
- const stream = ref.open();
456
- void consume(stream, ref.descriptor.declaredStream);
457
- });
458
-
459
- // Option 2 — open every readable stream in one call, then branch
460
- await session.waitForStream(); // ensure at least one is announced
461
- const streams = session.openAllStreams(); // returns StreamClient[] in insertion order
462
- for (const s of streams) {
463
- void consume(s, /* whichever ref you care about */);
464
- }
465
- ```
466
-
467
- `openAllStreams()` is idempotent. Calling it again returns the same
468
- `StreamClient` objects for already-opened refs and skips outbound-only
469
- streams. It is an **active-session** convenience — it does not
470
- resurrect unopened streams after terminal; see the next section.
471
-
472
- **Drain window for already-open streams.** When the task reaches
473
- terminal, any stream that was already opened continues draining
474
- cleanly for up to **30 seconds** (raised from 2 seconds in prior
475
- versions) so consumers have time to finish iterating `for await`
476
- loops. Tune the window per session:
477
-
478
- ```typescript
479
- // Narrower window for fast-shutdown flows
480
- const session = await client.sendMessage({
481
- agentName: 'llm_streamer',
482
- requestParts: [textPart('stream please')],
483
- drainWindowMs: 5_000, // 5 seconds
484
- });
485
-
486
- // Wider window for long-tail consumers, or on connect()
487
- const resumed = await client.connect({
488
- taskId: 'task-abc',
489
- drainWindowMs: 60_000, // 60 seconds
490
- });
491
- ```
492
-
493
- The option is supported on both `sendMessage()` and `connect()`.
494
-
495
- ### Reconnecting to Terminal Tasks
496
-
497
- Stream data is **live-only** — the transport layer does not persist
498
- stream payloads. When `client.connect({ taskId })` returns a session for a
499
- task that has already finished, a stream that was **never opened
500
- while the task was active** throws a typed `StreamUnavailableError`
501
- from `StreamRef.open()` instead of subscribing to a dead channel.
502
- `openAllStreams()` on the same session silently skips those
503
- never-opened refs:
504
-
505
- ```typescript
506
- import { TaskClient, StreamUnavailableError } from '@blocks-network/sdk';
507
-
508
- const session = await client.connect({ taskId: 'task-abc-123' });
509
-
510
- for (const ref of session.listStreams()) {
511
- try {
512
- const stream = ref.open();
513
- // ... consume stream ...
514
- } catch (err) {
515
- if (err instanceof StreamUnavailableError) {
516
- // Stream data is gone, but descriptor and artifacts remain:
517
- console.log('stream', ref.descriptor.declaredStream, err.terminalState);
518
- } else {
519
- throw err;
520
- }
521
- }
522
- }
523
-
524
- // Artifacts produced by the finished task are still available:
525
- const artifacts = session.listArtifacts();
526
- await session.saveArtifacts('./recovered');
527
- ```
528
-
529
- `StreamUnavailableError` carries named fields `taskId`, `streamId`,
530
- `declaredStream`, and `terminalState`. Inspection of
531
- `ref.descriptor` (format, metadata, declared name) continues to
532
- work on terminal-session refs without raising.
533
-
534
- > `openAllStreams()` is **not** a post-terminal reopen escape hatch.
535
- > If you want every stream opened, call it while the task is still
536
- > active (for example immediately after `session.waitForStream()` or
537
- > inside an `onStream` callback). On a terminal session it silently
538
- > returns any streams that were already active and skips the rest.
539
-
540
- ### Resource Management
541
-
542
- ```typescript
543
- // TypeScript 5.2+ using keyword
544
- {
545
- using client = await TaskClient.create({ listing: 'playground', apiKey });
546
- // client.destroy() called automatically at scope exit
547
- }
548
-
549
- {
550
- await using session = await client.sendMessage({ ... });
551
- // session.asyncClose() called automatically at scope exit
552
- }
553
- ```
554
-
555
- ## Browser Support
556
-
557
- The SDK works in modern browsers (Chrome, Firefox, Safari, Edge).
558
- Import from the package root — no special browser entrypoint needed:
559
-
560
- import { TaskClient, TaskSession, StreamRef } from '@blocks-network/sdk';
561
-
562
- The Node SDK package.json declares `engines.node >= 20.0.0` (Node 20
563
- LTS) alongside the `browser` exports field. Node 20+ has native
564
- `FormData`, `fetch`, `Blob`, `Uint8Array`, `TextEncoder`, and
565
- `TextDecoder`, which the SDK uses directly — no polyfill required on
566
- either platform.
567
-
568
- **Consumer APIs** (TaskClient, TaskSession, StreamRef, StreamClient)
569
- are browser-safe — no Node.js `Buffer` polyfill needed:
570
-
571
- - `sendMessage()` accepts `Uint8Array`, `ArrayBuffer`, `Blob`, and
572
- `File` on `requestParts[].file`. Hand a `File` from
573
- `<input type="file">` straight to `filePart(file)`; hand a `Blob`
574
- from `fetch('/somewhere').blob()` straight to `filePart(blob)`.
575
- - `uploadToStorage` (large-file path) uses native `FormData` with a
576
- `Blob` field. `fetch` computes the multipart boundary
577
- automatically — no manual `Content-Type: multipart/form-data`
578
- header, no `Buffer.concat`.
579
- - `downloadArtifact()` handles multiple download shapes (raw
580
- `Uint8Array`, `Blob`, and legacy file objects with
581
- `toArrayBuffer()`) with typeof-guarded branches — no
582
- `Buffer.isBuffer` OR-order short-circuits.
583
- - `decodeInlineArtifact()` uses `atob` + `Uint8Array` and
584
- round-trips through `bytesToBase64` on encode, so inline artifacts
585
- decode correctly in browsers even though `Buffer` is not defined.
586
- - `filePart(data)` is synchronous and browser-safe; the file-path
587
- convenience lives on the Node-only async `filePartFromPath(path)`
588
- helper.
589
- - `stream.bytes()` yields `Uint8Array` chunks (decoded via
590
- `TextEncoder` / `atob`, no `Buffer.from`). Decode to text with
591
- `new TextDecoder().decode(chunk)`. `stream.events()` and
592
- `stream.inbound` are also browser-safe. **`stream.readable()`
593
- returns a Node.js `Readable` and is Node-only** — browser
594
- consumers should use `stream.bytes()` or `stream.inbound` instead.
595
-
596
- A jsdom-driven CI test (`tests/browser-execution.test.ts`) exercises
597
- the consumer paths end-to-end so browser regressions are caught
598
- before release. The bundle-smoke test (`tests/browser-bundle.test.ts`)
599
- continues to guard against accidental top-level `node:*` imports.
600
-
601
- **Provider APIs** (startAgentInstance) work in browsers for
602
- request-only, short-lived handlers but are not officially supported.
603
- Browser tab lifecycle (sleep, close, background throttling) makes
604
- long-lived agents unreliable.
605
-
606
- ### Configuration
607
-
608
- In browsers, use a customer-owned proxy endpoint or custom provider —
609
- the SDK does not read from `process.env` in browser environments:
610
-
611
- const client = await TaskClient.create({
612
- listing: 'playground',
613
- tokenEndpoint: {
614
- url: '/api/blocks-token',
615
- credentials: 'include', // send the app's session cookie
616
- },
617
- });
618
-
619
- ## License
620
-
621
- Blocks Network
1
+ # Placeholder
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocks-network/sdk",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
4
4
  "type": "module",
5
5
  "description": "Blocks Network SDK — build and run A2A agents on Blocks Network",
6
6
  "exports": {