@blocks-network/sdk 0.1.47 → 0.1.49

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,6 +1,6 @@
1
1
  # @blocks-network/sdk
2
2
 
3
- Blocks Network SDK for Node.js -- build and run A2A agents on Blocks Network.
3
+ Blocks Network SDK for Node.js -- build and run A2A agents on PubNub.
4
4
 
5
5
  ## Installation
6
6
 
@@ -87,6 +87,16 @@ The agent card follows the A2A specification with a `runtime` extension:
87
87
  Note: `identity.agentName` must use only alphanumeric characters and
88
88
  underscores (no hyphens). The pattern is `^[a-zA-Z0-9_]+$`.
89
89
 
90
+ ## PubNub Subscribe Strategy
91
+
92
+ The Node SDK uses PubNub Event Engine (`enableEventEngine: true`) on all
93
+ subscribing PubNub clients (control client, per-task client, and per-stream
94
+ client). Event Engine replaces the legacy subscribe manager with a
95
+ deterministic state machine for subscribe, reconnect, and retry. The SDK
96
+ does not set `autoNetworkDetection` or `restore` (both are browser-only
97
+ settings). No explicit `retryConfiguration` is added; the PubNub JS SDK
98
+ applies a default exponential retry policy when Event Engine is enabled.
99
+
90
100
  ## API
91
101
 
92
102
  ### `await startAgentInstance(options)`
@@ -102,7 +112,7 @@ Go CLI's `blocks publish` command generates the API key and writes
102
112
  it to `.env` automatically. `blocks login` can also do this when
103
113
  invoked with `--write-env` (or by answering "y" to its prompt).
104
114
 
105
- PAM tokens for channel access are managed by the SDK at runtime
115
+ PAM tokens for PubNub channel access are managed by the SDK at runtime
106
116
  (granted at registration, refreshed per-task on the control channel).
107
117
  No CLI involvement is needed for PAM.
108
118
 
@@ -281,7 +291,7 @@ const result: DownloadedArtifact = await session.downloadArtifact(ref);
281
291
  // result.data: Uint8Array, result.mimeType: string, result.fileName?: string
282
292
  ```
283
293
 
284
- Also available as a standalone function: `downloadArtifact(ref, client)`.
294
+ Also available as a standalone function: `downloadArtifact(ref, pubnub)`.
285
295
 
286
296
  ### `session.onError(cb)`
287
297
 
@@ -361,7 +371,7 @@ extends `Uint8Array`.
361
371
 
362
372
  ### Stream Consumer APIs
363
373
 
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()`:
374
+ All consumer iterators (`bytes()`, `events()`, `readable()`, `inbound`) deliver messages in sequence order. The SDK's reorder buffer transparently corrects out-of-order PubNub delivery and drops duplicate messages. To customize or disable reordering, pass `reorderTimeoutMs` to `open()`:
365
375
 
366
376
  ```typescript
367
377
  // Default: reorder buffer with 750ms gap timeout
@@ -401,9 +411,10 @@ readable.pipe(createWriteStream('./output.bin'));
401
411
  ### Handling Stream Errors
402
412
 
403
413
  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`:
414
+ The callback fires whenever the stream's PubNub subscribe loop
415
+ surfaces an error-category status event: PAM revocation, network
416
+ issues, timeouts, or any other category the PubNub SDK marks as an
417
+ error. The payload is a typed `StreamError`:
407
418
 
408
419
  ```typescript
409
420
  import type { StreamError } from '@blocks-network/sdk/stream';
@@ -432,7 +443,7 @@ arrive:
432
443
 
433
444
  All other error categories (network transients, timeouts, etc.)
434
445
  fire `onError` with `fatal: false` and leave the stream running so
435
- the built-in retry machinery can recover.
446
+ PubNub's built-in retry machinery can recover.
436
447
 
437
448
  ### Opening Task Streams
438
449
 
@@ -494,8 +505,8 @@ The option is supported on both `sendMessage()` and `connect()`.
494
505
 
495
506
  ### Reconnecting to Terminal Tasks
496
507
 
497
- Stream data is **live-only** — the transport layer does not persist
498
- stream payloads. When `client.connect({ taskId })` returns a session for a
508
+ Stream data is **live-only** — PubNub does not persist stream
509
+ payloads. When `client.connect({ taskId })` returns a session for a
499
510
  task that has already finished, a stream that was **never opened
500
511
  while the task was active** throws a typed `StreamUnavailableError`
501
512
  from `StreamRef.open()` instead of subscribing to a dead channel.
@@ -576,8 +587,8 @@ are browser-safe — no Node.js `Buffer` polyfill needed:
576
587
  `Blob` field. `fetch` computes the multipart boundary
577
588
  automatically — no manual `Content-Type: multipart/form-data`
578
589
  header, no `Buffer.concat`.
579
- - `downloadArtifact()` handles multiple download shapes (raw
580
- `Uint8Array`, `Blob`, and legacy file objects with
590
+ - `downloadArtifact()` handles the three PubNub v10 download shapes
591
+ (raw `Uint8Array`, `Blob`, and legacy `PubNubFile` with
581
592
  `toArrayBuffer()`) with typeof-guarded branches — no
582
593
  `Buffer.isBuffer` OR-order short-circuits.
583
594
  - `decodeInlineArtifact()` uses `atob` + `Uint8Array` and
@@ -618,4 +629,4 @@ the SDK does not read from `process.env` in browser environments:
618
629
 
619
630
  ## License
620
631
 
621
- Blocks Network
632
+ PubNub
@@ -38,6 +38,8 @@ export interface RegistrationResult {
38
38
  refreshToken: string;
39
39
  expiresIn: number;
40
40
  pamToken?: string;
41
+ agentId?: string;
42
+ controlChannel?: string;
41
43
  [key: string]: unknown;
42
44
  }
43
45
  /**
@@ -219,6 +219,7 @@ export interface AgentInstanceHandle {
219
219
  stop: () => void;
220
220
  agentName: string;
221
221
  instanceId: string;
222
+ controlChannel: string | undefined;
222
223
  pubnub: PubNub;
223
224
  subscribeKey: string;
224
225
  taskClient: TaskClient;
@@ -1 +1 @@
1
- import{StreamClient as e}from"../stream/index.js";import{buildArtifactRef as t,shouldInlineArtifact as a,decodeInlineArtifact as r}from"./artifacts.js";import{createPubNubClient as s}from"./pubnub-client.js";import{createChannelManager as n}from"./channel-manager.js";import{toPayload as o}from"./pubnub-types.js";import{connectAgent as i,getAgent as d}from"./agent-registry.js";import{parseStreamSetupResponse as c,parseStreamSetupError as l}from"./stream-setup-helper.js";import{DEFAULTS as u}from"../defaults.js";import{getEnv as m}from"../env.js";import{uploadFile as f}from"./file-upload.js";import{StaticAuthProvider as p}from"./auth-provider.js";import{StreamRegistry as k}from"./stream-registry.js";import{CredentialCache as I}from"./credential-cache.js";import{createStreamObject as h,createExternalStreamObject as y,runOnActivate as g}from"./stream-context.js";import{TaskClient as w}from"./task-client.js";import{fetchCdmConfig as b}from"./cdm-config.js";import{AgentAuth as v,AgentAuthFatalError as T}from"./agent-auth.js";import{CURRENT_PROTOCOL_VERSION as S,SUPPORTED_PROTOCOL_VERSIONS as $,isProtocolVersionSupported as E}from"./protocol-version.js";const K=async(e,t,a,r,s,i=S)=>{const d=n(r);try{await e.publish({channel:d.taskChannel(t,a),message:o({...s,protocolVersion:i}),storeInHistory:!0,sendByPost:!0,meta:o({agentName:r,taskId:t,protocolVersion:i})})}catch{}};export const _LOG_LEVEL_ORDER={error:0,warn:1,info:2,debug:3};export function _resolveLogLevel(){const e=(m("LOG_LEVEL")??"info").toLowerCase();return _LOG_LEVEL_ORDER[e]??_LOG_LEVEL_ORDER.info}const x=(e,t,a)=>{const r=_resolveLogLevel();if((_LOG_LEVEL_ORDER[e]??_LOG_LEVEL_ORDER.info)>r)return;const s={level:e,message:t,ts:Date.now(),...a};"error"===e?console.error("[AgentInstance]",s):"warn"===e?console.warn("[AgentInstance]",s):console.log("[AgentInstance]",s)};export function resolveMaxRunningTimeSec(e,t){return void 0!==e&&void 0!==t&&e!==t&&console.info(`[AgentInstance] opts.maxRunningTimeSec (${e}) overrides card.runtime.maxRunningTimeSec (${t}).`),e??t}export function computeStreamDurationMinutes(e,t,a){return"number"==typeof e?e:t?60:Math.ceil((a??3600)/60)}const A=async(e,r,s,n,o,i,d,c,l)=>{const u={type:"artifact",taskId:r};if(c&&(u.outputId=c),a(o.length))await K(e,r,s,n,{...u,artifactRef:t({data:o,mimeType:i,fileName:d})});else{if(!l)throw new Error("Artifact size exceeds the inline threshold but no baseUrl is configured. Set baseUrl in AgentInstanceOptions to enable the pre-signed URL upload flow.");await f(l,{role:"provider-output",taskId:r,fileName:d??`${r}-artifact`,fileSize:o.length,mimeType:i,outputId:c},o)}};async function _(e,t){const a=`setup.${t.orgId}.${t.taskId}`,r={type:"stream_setup",taskId:t.taskId,orgId:t.orgId,agentName:t.agentName,streamId:t.streamId,channel:t.channel,direction:t.direction,format:t.format,taskKind:t.taskKind,durationMinutes:t.durationMinutes,affinity:t.affinity,declaredStream:t.declaredStream??"_default",protocolVersion:S};t.phase&&(r.phase=t.phase),t.metadata&&(r.metadata=t.metadata);try{throw await e.publish({channel:a,message:o(r),storeInHistory:!1,sendByPost:!0}),new Error("stream_setup publish should have been aborted by Function")}catch(e){const t=l(e);if(t)throw new Error(`Stream setup failed: [${t.code}] ${t.message}`);const a=c(e);if(a)return{token:a.token,tokenTtlMinutes:a.tokenTtlMinutes};throw e}}export const startAgentInstance=async t=>{const a=t.agentName;if(!a)throw new Error("agentName is required: provide opts.agentName");if(!/^[a-zA-Z0-9_]+$/.test(a))throw new Error("agentName must contain only alphanumeric characters and underscores (no hyphens)");const o=n(a),c=t.instanceId??`AG-${a}-${crypto.randomUUID()}`,l=t.token;let f,N=null,R="playground",C=R;if(t.pubnub){const e=t.pubnub._configuration?.keySet,a={publishKey:e?.publishKey??"",subscribeKey:e?.subscribeKey??""};f={playground:a,network:a}}else N=await b(t.cdmUrl),f={playground:N.playground,network:N.network},console.log("[AgentInstance] CDM config loaded — environment switching enabled (playground + network)");const P=m("BLOCKS_API_KEY");if(!P)throw new Error("BLOCKS_API_KEY is required. Run 'blocks publish' to set up credentials.");let M;const L=t.baseUrl??N?.api.baseUrl;let j,U;if(P&&L&&(M=new v(P,L),console.log("[AgentInstance] AgentAuth created — will authenticate via connect")),N){const e=t.baseUrl??N.api.baseUrl,r=await d(a,{baseUrl:e});r?(j=r.listing,R="playground"===j?"playground":"network",console.log(`[AgentInstance] Registry listing: ${j} — using ${R} environment`)):console.warn(`[AgentInstance] Agent "${a}" not found in registry — defaulting to playground environment`),C=R}let q=!1;if(t.pubnub)U=t.pubnub,q=!1,l&&U.setToken(l);else{const e=f[C];U=s({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20}),q=!0,l&&U.setToken(l)}let D=f[C].subscribeKey,O=f[C].publishKey;const V=new w({subscribeKey:D,authProvider:l?new p(l):void 0,baseUrl:t.baseUrl??N?.api.baseUrl,agentAuth:M,createPubNub:()=>{const e=s({publishKey:O,subscribeKey:D,userId:`${c}-taskclient`});return l&&e.setToken(l),e}}),B=t.concurrency??u.concurrency,F=t.expectedInstances??u.expectedInstances,G=t.maxPendingBacklog,z=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),H=Date.now();let Y=0;const W=new k,Z=new I,J=new Map,Q=new Map,X=new Map,ee=new Map,te=new Map,ae=new Set,re=new Set,se=new Map,ne=new Set,oe=new Map;let ie;t.logChannel;const de=o.controlChannel(),ce=async()=>{if("function"==typeof U.setState)try{await U.setState({channels:[de],state:{instanceId:c,activeTasks:Y,activeStreams:W.activeStreamCount,concurrency:B,startedAt:H,preferredProtocolVersion:S,protocolVersions:[...$]}})}catch{}},le=(e,t)=>{const a=J.get(e);a&&(a.delete(t),0===a.size&&J.delete(e))},ue=async e=>{(e=>{for(const[t,a]of J)a.has(e)&&(a.delete(e),0===a.size&&J.delete(t))})(e);const t=W.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},me=async(e,t)=>{le(e,t);const a=W.get(e);if(0===W.release(e,t)&&a?.streamClient){console.log("[AgentInstance]",{level:"info",message:"last-ref teardown",event:"stream_registry_last_ref_teardown",streamId:e,affinity:a.affinity,releasingTaskId:t,ts:Date.now()});try{await a.streamClient.end()}catch{}}ce().catch(()=>{})},fe=async(e,t)=>{const a=W.forceRemove(e);if((e=>{J.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=Z.get(e);if(a){try{const r=f[a.environment??R]??f[R],n=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c});n.setToken(a.writeToken),await K(n,e,a.orgId,a.agentName,{type:"terminal",taskId:e,state:"failed",error:t}),n.destroy()}catch{}Z.remove(e)}}await ce()}},pe=async(e,t)=>{const a=Z.get(e);if(!a)throw new Error(`No cached credentials for task ${e}`);const r=f[a.environment??R]??f[R],n=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c});n.setToken(a.writeToken);try{await K(n,e,a.orgId,a.agentName,{...t,type:"terminal",taskId:e})}finally{n.destroy()}await ue(e),Z.remove(e),await ce()},ke=async(s,n,i,d,c)=>{const u=a,m="pipe"===s.taskKind,f=m?"pipe":"request",k=computeStreamDurationMinutes(s.duration,m,z);if(m&&"number"==typeof s.durationExpiresAtMs){const e=Math.max(0,s.durationExpiresAtMs-Date.now());if(e>0){const t=setTimeout(()=>{ae.add(s.taskId),c.abort()},e);se.set(s.taskId,t)}}await K(n,s.taskId,d,u,{type:"progress",taskId:s.taskId,progress:0,state:"running"}),oe.set(s.taskId,0);let I=0,w=null,b=null;const v=()=>{if(null===w)return;const e=w;w=null,I=Date.now(),K(n,s.taskId,d,u,{type:"progress",taskId:s.taskId,message:e}).catch(()=>{})},T={taskId:s.taskId,requestParts:s.requestParts??[],reportStatus:e=>{ee.set(s.taskId,e);const t=Date.now();if(t-I>=1e3)w=null,I=t,K(n,s.taskId,d,u,{type:"progress",taskId:s.taskId,message:e}).catch(()=>{});else if(w=e,!b){b=setTimeout(()=>{b=null,v()},1e3-(t-I))}},get isCancelled(){return c.signal.aborted},get isExpired(){return ae.has(s.taskId)},hasStream:!!s.hasStream,cancelSignal:c.signal,taskClient:V,consumerPublicKey:s.consumerPublicKey,downloadInputArtifact:async e=>{const t=e.artifactRef;if(!t)throw new Error(`Request part has no artifactRef (partId: ${e.partId??"none"})`);if("inline"===t.kind)return Buffer.from(r(t));if(!t.channel||!t.fileId||!t.fileName)throw new Error("File artifactRef missing channel, fileId, or fileName");if(!n.downloadFile)throw new Error("PubNub client does not support downloadFile");const a=await n.downloadFile({channel:t.channel,id:t.fileId,name:t.fileName}),s=a?.data;if(Buffer.isBuffer(s))return s;if(s instanceof Uint8Array)return Buffer.from(s);if(s&&"function"==typeof s.arrayBuffer){const e=await s.arrayBuffer();return Buffer.from(e)}throw new Error("Unexpected downloadFile response format")},publishArtifact:async(e,t)=>{const a="string"==typeof e?Buffer.from(e,"utf-8"):e,r=t?.mimeType??"application/octet-stream",o=L?{baseUrl:L,authProvider:l?new p(l):void 0,agentAuth:M}:void 0;await A(n,s.taskId,d,u,a,r,t?.fileName,t?.outputId,o)},createStream:async a=>{if(void 0!==a&&("object"!=typeof a||null===a||Array.isArray(a)))throw new TypeError("createStream expects an options object. The positional streamId argument was removed; move any declared-stream key into CreateStreamOptions.declaredStream. See SDK_CONTRACT §8.2.1.");if(!s.hasStream)throw new Error("Streaming was not negotiated for this task. Ensure the agent card is registered with streaming capability.");const r=t.card.streams;if(!r||0===Object.keys(r).length)throw new Error("Agent card has no streams block. Streaming requires declared streams in the card.");const i=Object.keys(r);let c;if(a?.declaredStream)c=a.declaredStream;else{if(1!==i.length)throw new Error(`Card declares multiple streams (${i.join(", ")}). Specify declaredStream in CreateStreamOptions.`);c=i[0]}const l=r[c];if(!l)throw new Error(`Stream "${c}" is not declared in the agent card. Available streams: ${i.join(", ")}`);const p=a?.direction??l.direction,I=a?.format??l.format;if(a?.direction&&a.direction!==l.direction)throw new Error(`Direction "${a.direction}" conflicts with card declaration "${l.direction}" for stream "${c}".`);if(a?.format&&a.format!==l.format)throw new Error(`Format "${a.format}" conflicts with card declaration "${l.format}" for stream "${c}".`);const w=a?.external??!1,b=a?.metadata,v=a?.onActivate,T=l.affinity??"dedicated";if("shared"===T&&w)throw new Error(`Shared-affinity external streams are not supported. Declared stream '${c}' has affinity: 'shared' and createStream was called with external: true. Shared affinity requires a single SDK-managed writer with per-task ref-counting; external streams delegate the writer entirely. Use affinity: 'dedicated' with external: true, or affinity: 'shared' without external.`);if(!m){if("shared"===T)throw new Error(`Shared-affinity streams are not supported on request tasks. Declared stream '${c}' has affinity: 'shared'. Request tasks are single-shot; cross-task broadcast is inherently a pipe-task concept. Use affinity: 'dedicated' or remove 'request' from the agent card's taskKinds.`);if("outbound"!==p)throw new Error("Request tasks only support outbound streams");if(w)throw new Error("Request tasks cannot use external streams")}let S;if("shared"===T)S=c;else{const e=(oe.get(s.taskId)??0)+1;oe.set(s.taskId,e),S=`${s.taskId}-${e}`}const $=o.streamChannel(S);if("shared"===T){const e=J.get(S)?.get(s.taskId);if(e)return e}const{entry:E,isNew:K,isNewForTask:x}=W.acquire(S,s.taskId,{direction:p,format:I,external:w,affinity:T});Z.addStream(s.taskId,S);const A=e=>{if("shared"!==T)return;let t=J.get(S);t||(t=new Map,J.set(S,t)),t.set(s.taskId,e)};if(!x){E.setupPromise&&await E.setupPromise;const e=J.get(S)?.get(s.taskId);if(e)return e;if(E.streamClient){const e=h(S,E.streamClient,s.taskId,{releaseStream:me});return A(e),e}throw new Error(`Stream "${S}" same-task reacquire reached an impossible state: setup completed but no client installed. This indicates a logic error in the shared-stream handle cache.`)}if(!K)try{if(E.setupPromise&&await E.setupPromise,"shared"===T&&await(async()=>{await _(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:$,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:"activate",metadata:b,declaredStream:c})})(),E.streamClient){const e=h(S,E.streamClient,s.taskId,{releaseStream:me});return A(e),e}if(w)return y(S,$,"",async()=>{throw new Error("External stream already activated")});throw new Error(`Stream "${S}" exists but has no client`)}catch(e){le(S,s.taskId);try{await W.release(S,s.taskId)}catch{}throw e}let N,R;E.setupPromise=new Promise((e,t)=>{N=e,R=t}),E.setupPromise.catch(()=>{});const C=w?"token_request":"embedded";let P;try{P=await _(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:$,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:C,metadata:b,declaredStream:c})}catch(e){R(e),le(S,s.taskId);try{await W.release(S,s.taskId)}catch{}throw e}if(w){N(),E.setupPromise=null;const e=P.token??"";return y(S,$,e,async e=>{await _(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:$,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:"activate",metadata:e?.metadata??b,declaredStream:c})})}const M=P.token;if(!M){const e=new Error("No T7a token received from stream setup handshake");R(e),le(S,s.taskId);try{await W.release(S,s.taskId)}catch{}throw e}const L=new e({subscribeKey:D,publishKey:O,token:M,agentName:u,streamId:S,channel:$,format:I,direction:p,affinity:T,gating:m,bundleSizeBytes:a?.bundleSizeBytes,maxLatencyMs:a?.maxLatencyMs});E.streamClient=L;const j=h(S,L,s.taskId,{releaseStream:me});if(A(j),N(),E.setupPromise=null,"outbound"===p||"bidirectional"===p){const e=a?.subscribeGraceMs??1e3;e>0&&await new Promise(t=>setTimeout(t,e))}return v&&(E.activated=!0,E.activatePromise=g(S,j,v,fe)),j}};let S;try{S=t.handler?await t.handler(s,T):void 0}finally{oe.delete(s.taskId),b&&(clearTimeout(b),b=null),v()}const $=L?{baseUrl:L,authProvider:l?new p(l):void 0,agentAuth:M}:void 0;if(S?.artifacts&&S.artifacts.length>0)for(const e of S.artifacts){const t="string"==typeof e.data?Buffer.from(e.data,"utf-8"):e.data;await A(n,s.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,$)}const E=c.signal.aborted;if(m){const e=ae.has(s.taskId),t=re.has(s.taskId);if(E||e||t){await ue(s.taskId);const a=E&&!e?"canceled":"completed";await K(n,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),Z.remove(s.taskId),x("info",`Task ${s.taskId} ${a}`,{taskId:s.taskId,agentName:u,owner:i})}else x("info",`Task ${s.taskId} handler returned (pipe, no auto-terminal)`,{taskId:s.taskId,agentName:u,owner:i})}else{const e=re.has(s.taskId);await ue(s.taskId);const t=E?"canceled":"completed";await K(n,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:t,...e?{reason:"terminated"}:{}}),Z.remove(s.taskId),x("info",`Task ${s.taskId} ${t}`,{taskId:s.taskId,agentName:u,owner:i})}},Ie=(e,t)=>{if(x("info",`SwitchEnvironment requested: ${C} -> ${e}`,{pamToken:t?"present":"absent"}),"playground"===e&&"network"===C)return void x("error",`SwitchEnvironment rejected: cannot demote from ${C} to ${e}. Forbidden listing transition.`);if(!t)return void x("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=U,r=q;try{a.removeListener(ye),a.unsubscribe({channels:[de]})}catch{}const n=f[e];if(U=s({publishKey:n.publishKey,subscribeKey:n.subscribeKey,userId:c,presenceTimeout:20}),q=!0,ie=void 0,U.setToken(t),0!==F&&"function"==typeof U.setFilterExpression&&U.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),U.addListener(ye),U.subscribe({channels:[de]}),r)try{a.destroy()}catch{}C=e,D=n.subscribeKey,O=n.publishKey,V.updateKeys(n.subscribeKey,n.publishKey),x("info",`Switched to ${e} environment`),ce().catch(()=>{})},he=(()=>{let e=!1;return t=>{if("PNAccessDeniedCategory"===t&&!e){e=!0,console.error(`[AgentInstance] PAM token expired or revoked — agent ${c} is no longer receiving tasks. Re-register the agent to resume.`);try{U.destroy()}catch{}}}})(),ye={status:e=>{const t=e,a=String(t.category??""),r=String(t.operation??"");"PNConnectedCategory"===a?console.log(`[AgentInstance] PubNub connected to ${de}`):t.error&&console.error(`[AgentInstance] PubNub status: ${a} (op=${r}, code=${t.statusCode})`),he(a)},message:e=>{const r=e.message;if(!r)return;if("SwitchEnvironment"===r.type){const e=r,t=e.environment;return void(t&&t!==C&&t in f&&Ie(t,e.pamToken))}(async(e,r)=>{if("StartTask"===e.type){const n=C,o=((e,t)=>{if("string"==typeof e&&e.length>0)return e;const a=t?.sub;return"string"==typeof a&&a.length>0?a:"anonymous"})(e.ownerId,e.callerClaims),i=e.orgId??o;if(ne.has(e.taskId))return void x("warn",`Ignoring duplicate StartTask for in-flight task ${e.taskId}`,{taskId:e.taskId});const d=Boolean(r?.broadcast);if(e.protocolVersion&&!E(e.protocolVersion))return d?void x("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await K(U,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void x("warn",`Task ${e.taskId} rejected: unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId,instanceId:c}));if("pipe"===(e.taskKind||"request")){const t=e.duration,r=e.durationExpiresAtMs;if(null==t||!Number.isInteger(t)||t<1||t>43200||"number"!=typeof r||r<=0)return x("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${r})`,{taskId:e.taskId,instanceId:c}),void await K(U,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=Y+W.activeStreamCount;if(B>0&&l>=B)return d?void x("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${B})`,{taskId:e.taskId,instanceId:c}):(await K(U,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void x("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:Y}));Q.set(e.taskId,o),X.set(e.taskId,i),ne.add(e.taskId),Y++,e.controlToken&&(ie=e.controlToken);const u=new AbortController;te.set(e.taskId,u);let m=null;try{const r=f[n];if(!r.publishKey||!r.subscribeKey)throw new Error("Per-task PubNub client requires valid pub/sub keys. When using an injected PubNub client, ensure it was created with publishKey and subscribeKey.");m=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c}),e.writeToken&&m.setToken(e.writeToken),Z.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:n}),await ce(),t.onStartTask?await t.onStartTask(e,m):await ke(e,m,o,i,u)}catch(r){const s=r?.message??"Agent instance error",n=m??U;try{await ue(e.taskId)}catch{}await K(n,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:s}),t.onError&&await t.onError(e.taskId,r),x("error",s,{taskId:e.taskId}),Z.remove(e.taskId)}finally{const t=se.get(e.taskId);if(t&&(clearTimeout(t),se.delete(e.taskId)),ne.delete(e.taskId),Q.delete(e.taskId),X.delete(e.taskId),ee.delete(e.taskId),te.delete(e.taskId),ae.delete(e.taskId),re.delete(e.taskId),m)try{m.destroy()}catch{}Y--,0===Y&&ie&&U.setToken(ie),await ce()}}else if("CancelTask"===e.type){const t=te.get(e.taskId);t?(t.abort(),x("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):Z.get(e.taskId)&&await pe(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)te.has(e.taskId)?(ae.add(e.taskId),te.get(e.taskId)?.abort(),x("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):Z.get(e.taskId)&&await pe(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)te.has(e.taskId)?(re.add(e.taskId),te.get(e.taskId)?.abort()):Z.get(e.taskId)&&await pe(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if(ne.has(e.taskId)){const t=X.get(e.taskId)??"anonymous";await K(U,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if(ne.has(e.taskId)){const t=X.get(e.taskId)??"anonymous";await K(U,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&x("info","RetryTask received (no-op)",{taskId:e.taskId})})(r,e.userMetadata&&"object"==typeof e.userMetadata?e.userMetadata:void 0).catch(e=>{console.error("[AgentInstance] Unhandled error in message handler:",e)})}};U.addListener(ye),i(a,{instanceId:c,description:t.description,scaling:{expectedInstances:F,concurrency:B,maxPendingBacklog:G,maxRunningTimeSec:z},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:j??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??N?.api.baseUrl,agentAuth:M}).then(e=>{console.log(`[AgentInstance] Registered agent: ${a} (instance: ${c})`),e.pamToken&&U.setToken(e.pamToken),0!==F&&"function"==typeof U.setFilterExpression&&U.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),U.subscribe({channels:[de]}),ce().catch(()=>{})}).catch(e=>{const t=e instanceof Error?e.message:String(e);console.error(`[AgentInstance] Failed to register agent: ${a} — ${t}`),e instanceof T&&process.exit(1)}),x("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{V.destroy(),U.removeListener(ye),U.unsubscribe({channels:[de]}),q&&U.destroy();for(const e of W.streamIds()){const t=W.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}W.clear(),J.clear(),Z.clear()}catch{}},agentName:a,instanceId:c,get pubnub(){return U},get subscribeKey(){return f[C].subscribeKey},taskClient:V,publishTerminal:pe,failStream:fe,cdmConfig:N}};export const artifactUtils={buildArtifactRef:t,shouldInlineArtifact:a};
1
+ import{StreamClient as e}from"../stream/index.js";import{buildArtifactRef as t,shouldInlineArtifact as a,decodeInlineArtifact as r}from"./artifacts.js";import{createPubNubClient as s}from"./pubnub-client.js";import{createChannelManager as n}from"./channel-manager.js";import{toPayload as o}from"./pubnub-types.js";import{connectAgent as i,getAgent as d}from"./agent-registry.js";import{parseStreamSetupResponse as c,parseStreamSetupError as l}from"./stream-setup-helper.js";import{DEFAULTS as u}from"../defaults.js";import{getEnv as m}from"../env.js";import{uploadFile as f}from"./file-upload.js";import{StaticAuthProvider as p}from"./auth-provider.js";import{StreamRegistry as k}from"./stream-registry.js";import{CredentialCache as I}from"./credential-cache.js";import{createStreamObject as h,createExternalStreamObject as y,runOnActivate as g}from"./stream-context.js";import{TaskClient as w}from"./task-client.js";import{fetchCdmConfig as b}from"./cdm-config.js";import{AgentAuth as v,AgentAuthFatalError as T}from"./agent-auth.js";import{CURRENT_PROTOCOL_VERSION as S,SUPPORTED_PROTOCOL_VERSIONS as $,isProtocolVersionSupported as E}from"./protocol-version.js";const K=async(e,t,a,r,s,i=S)=>{const d=n(r);try{await e.publish({channel:d.taskChannel(t,a),message:o({...s,protocolVersion:i}),storeInHistory:!0,sendByPost:!0,meta:o({agentName:r,taskId:t,protocolVersion:i})})}catch{}};export const _LOG_LEVEL_ORDER={error:0,warn:1,info:2,debug:3};export function _resolveLogLevel(){const e=(m("LOG_LEVEL")??"info").toLowerCase();return _LOG_LEVEL_ORDER[e]??_LOG_LEVEL_ORDER.info}const x=(e,t,a)=>{const r=_resolveLogLevel();if((_LOG_LEVEL_ORDER[e]??_LOG_LEVEL_ORDER.info)>r)return;const s={level:e,message:t,ts:Date.now(),...a};"error"===e?console.error("[AgentInstance]",s):"warn"===e?console.warn("[AgentInstance]",s):console.log("[AgentInstance]",s)};export function resolveMaxRunningTimeSec(e,t){return void 0!==e&&void 0!==t&&e!==t&&console.info(`[AgentInstance] opts.maxRunningTimeSec (${e}) overrides card.runtime.maxRunningTimeSec (${t}).`),e??t}export function computeStreamDurationMinutes(e,t,a){return"number"==typeof e?e:t?60:Math.ceil((a??3600)/60)}const A=async(e,r,s,n,o,i,d,c,l)=>{const u={type:"artifact",taskId:r};if(c&&(u.outputId=c),a(o.length))await K(e,r,s,n,{...u,artifactRef:t({data:o,mimeType:i,fileName:d})});else{if(!l)throw new Error("Artifact size exceeds the inline threshold but no baseUrl is configured. Set baseUrl in AgentInstanceOptions to enable the pre-signed URL upload flow.");await f(l,{role:"provider-output",taskId:r,fileName:d??`${r}-artifact`,fileSize:o.length,mimeType:i,outputId:c},o)}};async function C(e,t){const a=`setup.${t.orgId}.${t.taskId}`,r={type:"stream_setup",taskId:t.taskId,orgId:t.orgId,agentName:t.agentName,streamId:t.streamId,channel:t.channel,direction:t.direction,format:t.format,taskKind:t.taskKind,durationMinutes:t.durationMinutes,affinity:t.affinity,declaredStream:t.declaredStream??"_default",protocolVersion:S};t.phase&&(r.phase=t.phase),t.metadata&&(r.metadata=t.metadata);try{throw await e.publish({channel:a,message:o(r),storeInHistory:!1,sendByPost:!0}),new Error("stream_setup publish should have been aborted by Function")}catch(e){const t=l(e);if(t)throw new Error(`Stream setup failed: [${t.code}] ${t.message}`);const a=c(e);if(a)return{token:a.token,tokenTtlMinutes:a.tokenTtlMinutes};throw e}}export const startAgentInstance=async t=>{const a=t.agentName;if(!a)throw new Error("agentName is required: provide opts.agentName");if(!/^[a-zA-Z0-9_]+$/.test(a))throw new Error("agentName must contain only alphanumeric characters and underscores (no hyphens)");const o=n(a),c=t.instanceId??`AG-${a}-${crypto.randomUUID()}`,l=t.token;let f,_=null,N="playground",R=N;if(t.pubnub){const e=t.pubnub._configuration?.keySet,a={publishKey:e?.publishKey??"",subscribeKey:e?.subscribeKey??""};f={playground:a,network:a}}else _=await b(t.cdmUrl),f={playground:_.playground,network:_.network},console.log("[AgentInstance] CDM config loaded — environment switching enabled (playground + network)");const P=m("BLOCKS_API_KEY");if(!P)throw new Error("BLOCKS_API_KEY is required. Run 'blocks publish' to set up credentials.");let M;const L=t.baseUrl??_?.api.baseUrl;let j,U;if(P&&L&&(M=new v(P,L),console.log("[AgentInstance] AgentAuth created — will authenticate via connect")),_){const e=t.baseUrl??_.api.baseUrl,r=await d(a,{baseUrl:e});r?(j=r.listing,N="playground"===j?"playground":"network",console.log(`[AgentInstance] Registry listing: ${j} — using ${N} environment`)):console.warn(`[AgentInstance] Agent "${a}" not found in registry — defaulting to playground environment`),R=N}let q=!1;if(t.pubnub)U=t.pubnub,q=!1,l&&U.setToken(l);else{const e=f[R];U=s({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20}),q=!0,l&&U.setToken(l)}let D=f[R].subscribeKey,O=f[R].publishKey;const V=new w({subscribeKey:D,authProvider:l?new p(l):void 0,baseUrl:t.baseUrl??_?.api.baseUrl,agentAuth:M,createPubNub:()=>{const e=s({publishKey:O,subscribeKey:D,userId:`${c}-taskclient`});return l&&e.setToken(l),e}}),B=t.concurrency??u.concurrency,F=t.expectedInstances??u.expectedInstances,G=t.maxPendingBacklog,z=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),H=Date.now();let Y=0;const W=new k,Z=new I,J=new Map,Q=new Map,X=new Map,ee=new Map,te=new Map,ae=new Set,re=new Set,se=new Map,ne=new Set,oe=new Map;let ie,de;t.logChannel;const ce=async()=>{if("function"==typeof U.setState&&de)try{await U.setState({channels:[de],state:{instanceId:c,activeTasks:Y,activeStreams:W.activeStreamCount,concurrency:B,startedAt:H,preferredProtocolVersion:S,protocolVersions:[...$]}})}catch{}},le=(e,t)=>{const a=J.get(e);a&&(a.delete(t),0===a.size&&J.delete(e))},ue=async e=>{(e=>{for(const[t,a]of J)a.has(e)&&(a.delete(e),0===a.size&&J.delete(t))})(e);const t=W.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},me=async(e,t)=>{le(e,t);const a=W.get(e);if(0===W.release(e,t)&&a?.streamClient){console.log("[AgentInstance]",{level:"info",message:"last-ref teardown",event:"stream_registry_last_ref_teardown",streamId:e,affinity:a.affinity,releasingTaskId:t,ts:Date.now()});try{await a.streamClient.end()}catch{}}ce().catch(()=>{})},fe=async(e,t)=>{const a=W.forceRemove(e);if((e=>{J.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=Z.get(e);if(a){try{const r=f[a.environment??N]??f[N],n=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c});n.setToken(a.writeToken),await K(n,e,a.orgId,a.agentName,{type:"terminal",taskId:e,state:"failed",error:t}),n.destroy()}catch{}Z.remove(e)}}await ce()}},pe=async(e,t)=>{const a=Z.get(e);if(!a)throw new Error(`No cached credentials for task ${e}`);const r=f[a.environment??N]??f[N],n=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c});n.setToken(a.writeToken);try{await K(n,e,a.orgId,a.agentName,{...t,type:"terminal",taskId:e})}finally{n.destroy()}await ue(e),Z.remove(e),await ce()},ke=async(s,n,i,d,c)=>{const u=a,m="pipe"===s.taskKind,f=m?"pipe":"request",k=computeStreamDurationMinutes(s.duration,m,z);if(m&&"number"==typeof s.durationExpiresAtMs){const e=Math.max(0,s.durationExpiresAtMs-Date.now());if(e>0){const t=setTimeout(()=>{ae.add(s.taskId),c.abort()},e);se.set(s.taskId,t)}}await K(n,s.taskId,d,u,{type:"progress",taskId:s.taskId,progress:0,state:"running"}),oe.set(s.taskId,0);let I=0,w=null,b=null;const v=()=>{if(null===w)return;const e=w;w=null,I=Date.now(),K(n,s.taskId,d,u,{type:"progress",taskId:s.taskId,message:e}).catch(()=>{})},T={taskId:s.taskId,requestParts:s.requestParts??[],reportStatus:e=>{ee.set(s.taskId,e);const t=Date.now();if(t-I>=1e3)w=null,I=t,K(n,s.taskId,d,u,{type:"progress",taskId:s.taskId,message:e}).catch(()=>{});else if(w=e,!b){b=setTimeout(()=>{b=null,v()},1e3-(t-I))}},get isCancelled(){return c.signal.aborted},get isExpired(){return ae.has(s.taskId)},hasStream:!!s.hasStream,cancelSignal:c.signal,taskClient:V,consumerPublicKey:s.consumerPublicKey,downloadInputArtifact:async e=>{const t=e.artifactRef;if(!t)throw new Error(`Request part has no artifactRef (partId: ${e.partId??"none"})`);if("inline"===t.kind)return Buffer.from(r(t));if(!t.channel||!t.fileId||!t.fileName)throw new Error("File artifactRef missing channel, fileId, or fileName");if(!n.downloadFile)throw new Error("PubNub client does not support downloadFile");const a=await n.downloadFile({channel:t.channel,id:t.fileId,name:t.fileName}),s=a?.data;if(Buffer.isBuffer(s))return s;if(s instanceof Uint8Array)return Buffer.from(s);if(s&&"function"==typeof s.arrayBuffer){const e=await s.arrayBuffer();return Buffer.from(e)}throw new Error("Unexpected downloadFile response format")},publishArtifact:async(e,t)=>{const a="string"==typeof e?Buffer.from(e,"utf-8"):e,r=t?.mimeType??"application/octet-stream",o=L?{baseUrl:L,authProvider:l?new p(l):void 0,agentAuth:M}:void 0;await A(n,s.taskId,d,u,a,r,t?.fileName,t?.outputId,o)},createStream:async a=>{if(void 0!==a&&("object"!=typeof a||null===a||Array.isArray(a)))throw new TypeError("createStream expects an options object. The positional streamId argument was removed; move any declared-stream key into CreateStreamOptions.declaredStream. See SDK_CONTRACT §8.2.1.");if(!s.hasStream)throw new Error("Streaming was not negotiated for this task. Ensure the agent card is registered with streaming capability.");const r=t.card.streams;if(!r||0===Object.keys(r).length)throw new Error("Agent card has no streams block. Streaming requires declared streams in the card.");const i=Object.keys(r);let c;if(a?.declaredStream)c=a.declaredStream;else{if(1!==i.length)throw new Error(`Card declares multiple streams (${i.join(", ")}). Specify declaredStream in CreateStreamOptions.`);c=i[0]}const l=r[c];if(!l)throw new Error(`Stream "${c}" is not declared in the agent card. Available streams: ${i.join(", ")}`);const p=a?.direction??l.direction,I=a?.format??l.format;if(a?.direction&&a.direction!==l.direction)throw new Error(`Direction "${a.direction}" conflicts with card declaration "${l.direction}" for stream "${c}".`);if(a?.format&&a.format!==l.format)throw new Error(`Format "${a.format}" conflicts with card declaration "${l.format}" for stream "${c}".`);const w=a?.external??!1,b=a?.metadata,v=a?.onActivate,T=l.affinity??"dedicated";if("shared"===T&&w)throw new Error(`Shared-affinity external streams are not supported. Declared stream '${c}' has affinity: 'shared' and createStream was called with external: true. Shared affinity requires a single SDK-managed writer with per-task ref-counting; external streams delegate the writer entirely. Use affinity: 'dedicated' with external: true, or affinity: 'shared' without external.`);if(!m){if("shared"===T)throw new Error(`Shared-affinity streams are not supported on request tasks. Declared stream '${c}' has affinity: 'shared'. Request tasks are single-shot; cross-task broadcast is inherently a pipe-task concept. Use affinity: 'dedicated' or remove 'request' from the agent card's taskKinds.`);if("outbound"!==p)throw new Error("Request tasks only support outbound streams");if(w)throw new Error("Request tasks cannot use external streams")}let S;if("shared"===T)S=c;else{const e=(oe.get(s.taskId)??0)+1;oe.set(s.taskId,e),S=`${s.taskId}-${e}`}const $=o.streamChannel(S);if("shared"===T){const e=J.get(S)?.get(s.taskId);if(e)return e}const{entry:E,isNew:K,isNewForTask:x}=W.acquire(S,s.taskId,{direction:p,format:I,external:w,affinity:T});Z.addStream(s.taskId,S);const A=e=>{if("shared"!==T)return;let t=J.get(S);t||(t=new Map,J.set(S,t)),t.set(s.taskId,e)};if(!x){E.setupPromise&&await E.setupPromise;const e=J.get(S)?.get(s.taskId);if(e)return e;if(E.streamClient){const e=h(S,E.streamClient,s.taskId,{releaseStream:me});return A(e),e}throw new Error(`Stream "${S}" same-task reacquire reached an impossible state: setup completed but no client installed. This indicates a logic error in the shared-stream handle cache.`)}if(!K)try{if(E.setupPromise&&await E.setupPromise,"shared"===T&&await(async()=>{await C(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:$,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:"activate",metadata:b,declaredStream:c})})(),E.streamClient){const e=h(S,E.streamClient,s.taskId,{releaseStream:me});return A(e),e}if(w)return y(S,$,"",async()=>{throw new Error("External stream already activated")});throw new Error(`Stream "${S}" exists but has no client`)}catch(e){le(S,s.taskId);try{await W.release(S,s.taskId)}catch{}throw e}let _,N;E.setupPromise=new Promise((e,t)=>{_=e,N=t}),E.setupPromise.catch(()=>{});const R=w?"token_request":"embedded";let P;try{P=await C(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:$,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:R,metadata:b,declaredStream:c})}catch(e){N(e),le(S,s.taskId);try{await W.release(S,s.taskId)}catch{}throw e}if(w){_(),E.setupPromise=null;const e=P.token??"";return y(S,$,e,async e=>{await C(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:$,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:"activate",metadata:e?.metadata??b,declaredStream:c})})}const M=P.token;if(!M){const e=new Error("No T7a token received from stream setup handshake");N(e),le(S,s.taskId);try{await W.release(S,s.taskId)}catch{}throw e}const L=new e({subscribeKey:D,publishKey:O,token:M,agentName:u,streamId:S,channel:$,format:I,direction:p,affinity:T,gating:m,bundleSizeBytes:a?.bundleSizeBytes,maxLatencyMs:a?.maxLatencyMs});E.streamClient=L;const j=h(S,L,s.taskId,{releaseStream:me});if(A(j),_(),E.setupPromise=null,"outbound"===p||"bidirectional"===p){const e=a?.subscribeGraceMs??1e3;e>0&&await new Promise(t=>setTimeout(t,e))}return v&&(E.activated=!0,E.activatePromise=g(S,j,v,fe)),j}};let S;try{S=t.handler?await t.handler(s,T):void 0}finally{oe.delete(s.taskId),b&&(clearTimeout(b),b=null),v()}const $=L?{baseUrl:L,authProvider:l?new p(l):void 0,agentAuth:M}:void 0;if(S?.artifacts&&S.artifacts.length>0)for(const e of S.artifacts){const t="string"==typeof e.data?Buffer.from(e.data,"utf-8"):e.data;await A(n,s.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,$)}const E=c.signal.aborted;if(m){const e=ae.has(s.taskId),t=re.has(s.taskId);if(E||e||t){await ue(s.taskId);const a=E&&!e?"canceled":"completed";await K(n,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),Z.remove(s.taskId),x("info",`Task ${s.taskId} ${a}`,{taskId:s.taskId,agentName:u,owner:i})}else x("info",`Task ${s.taskId} handler returned (pipe, no auto-terminal)`,{taskId:s.taskId,agentName:u,owner:i})}else{const e=re.has(s.taskId);await ue(s.taskId);const t=E?"canceled":"completed";await K(n,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:t,...e?{reason:"terminated"}:{}}),Z.remove(s.taskId),x("info",`Task ${s.taskId} ${t}`,{taskId:s.taskId,agentName:u,owner:i})}},Ie=(e,t)=>{if(x("info",`SwitchEnvironment requested: ${R} -> ${e}`,{pamToken:t?"present":"absent"}),"playground"===e&&"network"===R)return void x("error",`SwitchEnvironment rejected: cannot demote from ${R} to ${e}. Forbidden listing transition.`);if(!t)return void x("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=U,r=q;try{a.removeListener(ye),de&&a.unsubscribe({channels:[de]})}catch{}const n=f[e];if(U=s({publishKey:n.publishKey,subscribeKey:n.subscribeKey,userId:c,presenceTimeout:20}),q=!0,ie=void 0,U.setToken(t),0!==F&&"function"==typeof U.setFilterExpression&&U.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),U.addListener(ye),de&&U.subscribe({channels:[de]}),r)try{a.destroy()}catch{}R=e,D=n.subscribeKey,O=n.publishKey,V.updateKeys(n.subscribeKey,n.publishKey),x("info",`Switched to ${e} environment`),ce().catch(()=>{})},he=(()=>{let e=!1;return t=>{if("PNAccessDeniedCategory"===t&&!e){e=!0,console.error(`[AgentInstance] PAM token expired or revoked — agent ${c} is no longer receiving tasks. Re-register the agent to resume.`);try{U.destroy()}catch{}}}})(),ye={status:e=>{const t=e,a=String(t.category??""),r=String(t.operation??"");"PNConnectedCategory"===a?console.log(`[AgentInstance] PubNub connected to ${de}`):t.error&&console.error(`[AgentInstance] PubNub status: ${a} (op=${r}, code=${t.statusCode})`),he(a)},message:e=>{const r=e.message;if(!r)return;if("SwitchEnvironment"===r.type){const e=r,t=e.environment;return void(t&&t!==R&&t in f&&Ie(t,e.pamToken))}(async(e,r)=>{if("StartTask"===e.type){const n=R,o=((e,t)=>{if("string"==typeof e&&e.length>0)return e;const a=t?.sub;return"string"==typeof a&&a.length>0?a:"anonymous"})(e.ownerId,e.callerClaims),i=e.orgId??o;if(ne.has(e.taskId))return void x("warn",`Ignoring duplicate StartTask for in-flight task ${e.taskId}`,{taskId:e.taskId});const d=Boolean(r?.broadcast);if(e.protocolVersion&&!E(e.protocolVersion))return d?void x("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await K(U,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void x("warn",`Task ${e.taskId} rejected: unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId,instanceId:c}));if("pipe"===(e.taskKind||"request")){const t=e.duration,r=e.durationExpiresAtMs;if(null==t||!Number.isInteger(t)||t<1||t>43200||"number"!=typeof r||r<=0)return x("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${r})`,{taskId:e.taskId,instanceId:c}),void await K(U,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=Y+W.activeStreamCount;if(B>0&&l>=B)return d?void x("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${B})`,{taskId:e.taskId,instanceId:c}):(await K(U,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void x("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:Y}));Q.set(e.taskId,o),X.set(e.taskId,i),ne.add(e.taskId),Y++,e.controlToken&&(ie=e.controlToken);const u=new AbortController;te.set(e.taskId,u);let m=null;try{const r=f[n];if(!r.publishKey||!r.subscribeKey)throw new Error("Per-task PubNub client requires valid pub/sub keys. When using an injected PubNub client, ensure it was created with publishKey and subscribeKey.");m=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c}),e.writeToken&&m.setToken(e.writeToken),Z.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:n}),await ce(),t.onStartTask?await t.onStartTask(e,m):await ke(e,m,o,i,u)}catch(r){const s=r?.message??"Agent instance error",n=m??U;try{await ue(e.taskId)}catch{}await K(n,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:s}),t.onError&&await t.onError(e.taskId,r),x("error",s,{taskId:e.taskId}),Z.remove(e.taskId)}finally{const t=se.get(e.taskId);if(t&&(clearTimeout(t),se.delete(e.taskId)),ne.delete(e.taskId),Q.delete(e.taskId),X.delete(e.taskId),ee.delete(e.taskId),te.delete(e.taskId),ae.delete(e.taskId),re.delete(e.taskId),m)try{m.destroy()}catch{}Y--,0===Y&&ie&&U.setToken(ie),await ce()}}else if("CancelTask"===e.type){const t=te.get(e.taskId);t?(t.abort(),x("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):Z.get(e.taskId)&&await pe(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)te.has(e.taskId)?(ae.add(e.taskId),te.get(e.taskId)?.abort(),x("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):Z.get(e.taskId)&&await pe(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)te.has(e.taskId)?(re.add(e.taskId),te.get(e.taskId)?.abort()):Z.get(e.taskId)&&await pe(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if(ne.has(e.taskId)){const t=X.get(e.taskId)??"anonymous";await K(U,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if(ne.has(e.taskId)){const t=X.get(e.taskId)??"anonymous";await K(U,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&x("info","RetryTask received (no-op)",{taskId:e.taskId})})(r,e.userMetadata&&"object"==typeof e.userMetadata?e.userMetadata:void 0).catch(e=>{console.error("[AgentInstance] Unhandled error in message handler:",e)})}};U.addListener(ye),i(a,{instanceId:c,description:t.description,scaling:{expectedInstances:F,concurrency:B,maxPendingBacklog:G,maxRunningTimeSec:z},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:j??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??_?.api.baseUrl,agentAuth:M}).then(e=>{if(console.log(`[AgentInstance] Registered agent: ${a} (instance: ${c})`),!e.controlChannel)throw new Error("Connect response missing controlChannel — server may be outdated");de=e.controlChannel,e.pamToken&&U.setToken(e.pamToken),0!==F&&"function"==typeof U.setFilterExpression&&U.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),U.subscribe({channels:[de]}),ce().catch(()=>{})}).catch(e=>{const t=e instanceof Error?e.message:String(e);console.error(`[AgentInstance] Failed to register agent: ${a} — ${t}`),e instanceof T&&process.exit(1)}),x("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{V.destroy(),U.removeListener(ye),de&&U.unsubscribe({channels:[de]}),q&&U.destroy();for(const e of W.streamIds()){const t=W.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}W.clear(),J.clear(),Z.clear()}catch{}},agentName:a,instanceId:c,get controlChannel(){return de},get pubnub(){return U},get subscribeKey(){return f[R].subscribeKey},taskClient:V,publishTerminal:pe,failStream:fe,cdmConfig:_}};export const artifactUtils={buildArtifactRef:t,shouldInlineArtifact:a};
@@ -229,6 +229,8 @@ declare function detectDeviceOs(): string;
229
229
  */
230
230
  export declare function connectAgent(agentName: string, options: ConnectAgentOptions): Promise<{
231
231
  pamToken?: string;
232
+ agentId?: string;
233
+ controlChannel?: string;
232
234
  }>;
233
235
  /**
234
236
  * Get a single agent by name.
@@ -1 +1 @@
1
- import t from"node:os";import{registryAllChannel as n,registrySkillChannel as e,registryVisibilityChannel as a,registryLogChannel as r,normalizeSkillSlug as s}from"./channel-manager.js";import{getEnv as o}from"../env.js";import{CURRENT_PROTOCOL_VERSION as i,SUPPORTED_PROTOCOL_VERSIONS as c,SDK_VERSION as u,PROTOCOL_VERSION_HEADER as l}from"./protocol-version.js";import{captureAffinity as g,injectAffinity as d}from"./write-affinity.js";function m(){try{return t.platform()}catch{return"unknown"}}async function f(t,n=3,e=500){let a;for(let r=0;r<n;r++)try{return await t()}catch(t){a=t;const s=String(t);if(!(s.includes("ENOTFOUND")||s.includes("ETIMEDOUT")||s.includes("ECONNRESET")||s.includes("NetworkIssues"))||r===n-1)throw t;const o=e*Math.pow(2,r)+100*Math.random();await new Promise(t=>setTimeout(t,o))}throw a}function h(t,n){let e=`${function(t){if(t)return t.replace(/\/+$/,"");throw new Error("[AgentRegistry] baseUrl is required: provide it via options or CDM config")}(n)}/api/v1/registry/agents`;if(t){const n=new URLSearchParams;for(const[e,a]of Object.entries(t))void 0!==a&&""!==a&&n.set(e,a);const a=n.toString();a&&(e+="?"+a)}return e}async function p(t,n){const e=new Headers(n?.headers);e.has(l)||e.set(l,i);const a={};d(a);for(const[t,n]of Object.entries(a))e.set(t,n);const r=await fetch(t,{...n,headers:e});if(g(r.headers),404===r.status)return null;if(!r.ok){const t=(await r.json().catch(()=>({}))).message??`HTTP ${r.status}`;throw new Error(`[AgentRegistry] Request failed: ${t}`)}return{data:await r.json(),status:r.status}}function y(t){return{agentName:t.agentName,displayName:t.name??t.agentName,description:t.description,skills:t.skills,scaling:t.scaling,card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:t.listing??"playground",createdAt:t.registeredAt}}export async function connectAgent(t,n){if(!/^[a-zA-Z0-9_]+$/.test(t))throw new Error("agentName must contain only alphanumeric characters and underscores (no hyphens)");h(void 0,n.baseUrl);const e=o("BLOCKS_CLI_VERSION"),a={agentName:t,instanceId:n.instanceId,listing:n.listing,expectedInstances:n.scaling?.expectedInstances,concurrency:n.scaling?.concurrency,maxPendingBacklog:n.scaling?.maxPendingBacklog,maxRunningTimeSec:n.scaling?.maxRunningTimeSec,deviceOs:m(),sdkLanguage:"Node",sdkVersion:u,protocolVersions:[...c],preferredProtocolVersion:i};let r;if(e&&(a.cliVersion=e),!n.agentAuth)throw new Error("[AgentRegistry] agentAuth is required. Set BLOCKS_API_KEY and pass agentAuth to connectAgent.");return r=(await f(async()=>n.agentAuth.init(a))).pamToken??void 0,{pamToken:r}}export async function getAgent(t,n){const e=h({agentName:t},n?.baseUrl),a=await f(async()=>p(e));return a?y(a.data.agent):null}export async function fetchAgentRegistry(t){const n={include:"full"};t?.limit&&(n.limit=String(t.limit)),t?.cursor&&(n.cursor=t.cursor);const e=h(n,t?.baseUrl),a=await f(async()=>p(e));return a?{agents:a.data.agents.map(y),next:a.data.next??void 0,totalCount:a.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsBySkill(t,n){const e={include:"full",skill:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsByListing(t,n){const e={include:"full",listing:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function removeAgent(t,n){const e=h({agentName:t},n?.baseUrl);if(n?.agentAuth){return 404!==(await f(()=>n.agentAuth.authenticatedFetch(e,{method:"DELETE"}))).status}const a=o("BLOCKS_API_KEY");return null!==await f(async()=>p(e,{method:"DELETE",headers:{...a?{Authorization:`Bearer ${a}`}:{}}}))}export{m as detectDeviceOs,n as registryAllChannel,e as registrySkillChannel,a as registryVisibilityChannel,r as registryLogChannel,s as normalizeSkillSlug};
1
+ import t from"node:os";import{registryAllChannel as n,registrySkillChannel as e,registryVisibilityChannel as a,registryLogChannel as r,normalizeSkillSlug as o}from"./channel-manager.js";import{getEnv as s}from"../env.js";import{CURRENT_PROTOCOL_VERSION as i,SUPPORTED_PROTOCOL_VERSIONS as c,SDK_VERSION as u,PROTOCOL_VERSION_HEADER as l}from"./protocol-version.js";import{captureAffinity as g,injectAffinity as d}from"./write-affinity.js";function m(){try{return t.platform()}catch{return"unknown"}}async function f(t,n=3,e=500){let a;for(let r=0;r<n;r++)try{return await t()}catch(t){a=t;const o=String(t);if(!(o.includes("ENOTFOUND")||o.includes("ETIMEDOUT")||o.includes("ECONNRESET")||o.includes("NetworkIssues"))||r===n-1)throw t;const s=e*Math.pow(2,r)+100*Math.random();await new Promise(t=>setTimeout(t,s))}throw a}function h(t,n){let e=`${function(t){if(t)return t.replace(/\/+$/,"");throw new Error("[AgentRegistry] baseUrl is required: provide it via options or CDM config")}(n)}/api/v1/registry/agents`;if(t){const n=new URLSearchParams;for(const[e,a]of Object.entries(t))void 0!==a&&""!==a&&n.set(e,a);const a=n.toString();a&&(e+="?"+a)}return e}async function p(t,n){const e=new Headers(n?.headers);e.has(l)||e.set(l,i);const a={};d(a);for(const[t,n]of Object.entries(a))e.set(t,n);const r=await fetch(t,{...n,headers:e});if(g(r.headers),404===r.status)return null;if(!r.ok){const t=(await r.json().catch(()=>({}))).message??`HTTP ${r.status}`;throw new Error(`[AgentRegistry] Request failed: ${t}`)}return{data:await r.json(),status:r.status}}function y(t){return{agentName:t.agentName,displayName:t.name??t.agentName,description:t.description,skills:t.skills,scaling:t.scaling,card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:t.listing??"playground",createdAt:t.registeredAt}}export async function connectAgent(t,n){if(!/^[a-zA-Z0-9_]+$/.test(t))throw new Error("agentName must contain only alphanumeric characters and underscores (no hyphens)");h(void 0,n.baseUrl);const e=s("BLOCKS_CLI_VERSION"),a={agentName:t,instanceId:n.instanceId,listing:n.listing,expectedInstances:n.scaling?.expectedInstances,concurrency:n.scaling?.concurrency,maxPendingBacklog:n.scaling?.maxPendingBacklog,maxRunningTimeSec:n.scaling?.maxRunningTimeSec,deviceOs:m(),sdkLanguage:"Node",sdkVersion:u,protocolVersions:[...c],preferredProtocolVersion:i};let r;if(e&&(a.cliVersion=e),!n.agentAuth)throw new Error("[AgentRegistry] agentAuth is required. Set BLOCKS_API_KEY and pass agentAuth to connectAgent.");const o=await f(async()=>n.agentAuth.init(a));return r=o.pamToken??void 0,{pamToken:r,agentId:o.agentId,controlChannel:o.controlChannel}}export async function getAgent(t,n){const e=h({agentName:t},n?.baseUrl),a=await f(async()=>p(e));return a?y(a.data.agent):null}export async function fetchAgentRegistry(t){const n={include:"full"};t?.limit&&(n.limit=String(t.limit)),t?.cursor&&(n.cursor=t.cursor);const e=h(n,t?.baseUrl),a=await f(async()=>p(e));return a?{agents:a.data.agents.map(y),next:a.data.next??void 0,totalCount:a.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsBySkill(t,n){const e={include:"full",skill:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsByListing(t,n){const e={include:"full",listing:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function removeAgent(t,n){const e=h({agentName:t},n?.baseUrl);if(n?.agentAuth){return 404!==(await f(()=>n.agentAuth.authenticatedFetch(e,{method:"DELETE"}))).status}const a=s("BLOCKS_API_KEY");return null!==await f(async()=>p(e,{method:"DELETE",headers:{...a?{Authorization:`Bearer ${a}`}:{}}}))}export{m as detectDeviceOs,n as registryAllChannel,e as registrySkillChannel,a as registryVisibilityChannel,r as registryLogChannel,o as normalizeSkillSlug};
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Channel topology (org-scoped with 3-level constraint):
5
5
  * - u.{orgId}.{taskId} - Org-scoped task events (progress, artifacts, terminal, logs)
6
- * - agent.{agentName}.control - Control plane per agent name
6
+ * - agent.{agentId}.control - Control plane per agent ID
7
7
  * - obs.{agentName}.log - Agent-level observability
8
8
  *
9
9
  * Registry channels (App Context membership indexes):
@@ -72,9 +72,9 @@ export declare class ChannelManager {
72
72
  taskChannel(taskId: string, orgId: string): string;
73
73
  /**
74
74
  * Control channel for agent.
75
- * Format: agent.{agentName}.control (3 levels)
75
+ * Format: agent.{agentId}.control (3 levels)
76
76
  */
77
- controlChannel(agentName?: string): string;
77
+ controlChannel(agentId: string): string;
78
78
  /**
79
79
  * Agent-level observability channel.
80
80
  * Format: obs.{agentName}.log (3 levels)
@@ -1 +1 @@
1
- export const registryAllChannel=()=>"registry.all";export const registrySkillChannel=r=>`registry.skill.${normalizeSkillSlug(r)}`;export const registryVisibilityChannel=r=>r?"registry.public":"registry.private";export const registryLogChannel=()=>"registry.log";export const normalizeSkillSlug=r=>r.toLowerCase().replace(/[^a-z0-9._]/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"");const r=["agent","obs","sys","u","anonymous","system","stream"];export function validateOwnerId(e){return!(!e||"string"!=typeof e)&&!r.includes(e.toLowerCase())}export class ChannelManager{constructor(r={}){if(!r.agentName)throw new Error("agentName is required for ChannelManager");this.agentName=r.agentName}getAgentName(){return this.agentName}taskChannel(r,e){if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`}controlChannel(r){return`agent.${r??this.agentName}.control`}obsChannel(r){return`obs.${r??this.agentName}.log`}userTaskPattern(r){if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`}agentWildcard(r){return`agent.${r??this.agentName}.*`}streamChannel(r,e){if(!r)throw new Error("streamId required for stream channel");return`stream.${e??this.agentName}.${r}`}streamWildcard(r){return`stream.${r??this.agentName}.*`}}export const createChannelManager=r=>new ChannelManager({agentName:r});export const taskChannel=(r,e)=>{if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`};export const userTaskPattern=r=>{if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`};export const streamChannel=(r,e)=>{if(!r)throw new Error("agentName required for stream channel");if(!e)throw new Error("streamId required for stream channel");return`stream.${r}.${e}`};
1
+ export const registryAllChannel=()=>"registry.all";export const registrySkillChannel=r=>`registry.skill.${normalizeSkillSlug(r)}`;export const registryVisibilityChannel=r=>r?"registry.public":"registry.private";export const registryLogChannel=()=>"registry.log";export const normalizeSkillSlug=r=>r.toLowerCase().replace(/[^a-z0-9._]/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"");const r=["agent","obs","sys","u","anonymous","system","stream"];export function validateOwnerId(e){return!(!e||"string"!=typeof e)&&!r.includes(e.toLowerCase())}export class ChannelManager{constructor(r={}){if(!r.agentName)throw new Error("agentName is required for ChannelManager");this.agentName=r.agentName}getAgentName(){return this.agentName}taskChannel(r,e){if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`}controlChannel(r){return`agent.${r}.control`}obsChannel(r){return`obs.${r??this.agentName}.log`}userTaskPattern(r){if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`}agentWildcard(r){return`agent.${r??this.agentName}.*`}streamChannel(r,e){if(!r)throw new Error("streamId required for stream channel");return`stream.${e??this.agentName}.${r}`}streamWildcard(r){return`stream.${r??this.agentName}.*`}}export const createChannelManager=r=>new ChannelManager({agentName:r});export const taskChannel=(r,e)=>{if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`};export const userTaskPattern=r=>{if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`};export const streamChannel=(r,e)=>{if(!r)throw new Error("agentName required for stream channel");if(!e)throw new Error("streamId required for stream channel");return`stream.${r}.${e}`};
@@ -1 +1 @@
1
- import e from"pubnub";import{callRpc as t}from"./rpc-client.js";import{taskChannel as s}from"./channel-manager.js";import{TaskSession as n}from"./task-session.js";import{ConsumerAuth as i}from"./consumer-auth.js";import{shouldInlineArtifact as r,buildArtifactRef as o}from"./artifacts.js";import{uploadFile as a}from"./file-upload.js";import{normalizeFileInput as c}from"./file-input.js";import{fetchCdmConfig as u}from"./cdm-config.js";import{captureAffinity as b,injectAffinity as d}from"./write-affinity.js";import{getEnv as l}from"../env.js";import{StreamRef as h}from"./stream-ref.js";import{invertDirection as f}from"../stream/index.js";import{asPubNubFetcher as p}from"./pubnub-types.js";import{CURRENT_PROTOCOL_VERSION as m,PROTOCOL_VERSION_HEADER as y}from"./protocol-version.js";import{getAgent as k}from"./agent-registry.js";const g=new Set(["completed","failed","canceled"]);export class TaskClient{constructor(e){this._ownsPubNub=!1,this.config={subscribeKey:e.subscribeKey,authProvider:e.authProvider,baseUrl:e.baseUrl,agentAuth:e.agentAuth},this._pubnub=e.pubnub,this._createPubNub=e.createPubNub,this._createSessionPubNub=e.createSessionPubNub,this.defaultOwnerId=e.defaultOwnerId,this._subscribeKey=e.subscribeKey,this._publishKey=e.publishKey??"",this._ownsPubNub=!e.pubnub&&!!e.createPubNub}static async create(t){const s=t??{},n=s.listing;if(!n)throw new Error("TaskClient.create() requires a listing option ('playground', 'private', or 'public') to select the CDM keyset.");const r=!!(s.apiKey||s.tokenEndpoint||s.tokenProvider);if([s.apiKey,s.tokenEndpoint,s.tokenProvider].filter(Boolean).length>1)throw new Error("Only one token provider mode may be specified");const o=s.cdmUrl??l("BLOCKS_CDM_URL"),a=await u(o),c="playground"===n?a.playground:a.network,b=s.subscribeKey??l("BLOCKS_SUBSCRIBE_KEY")??c.subscribeKey,d=s.publishKey??l("BLOCKS_PUBLISH_KEY")??c.publishKey,h=s.baseUrl??l("BLOCKS_BACKEND_URL")??a.api.baseUrl;if(!b)throw new Error("TaskClient.create() could not resolve subscribeKey from options, env, or CDM");if(!h)throw new Error("TaskClient.create() could not resolve baseUrl. Set baseUrl option, BLOCKS_BACKEND_URL env var, or ensure CDM config has api.baseUrl.");const f=()=>{const t=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;return new e({subscribeKey:b,publishKey:d||void 0,userId:t,enableEventEngine:!0})};if(r){const e=new i({apiKey:s.apiKey,tokenEndpoint:s.tokenEndpoint,tokenProvider:s.tokenProvider,baseUrl:h,onAuthError:s.onAuthError});await e.init();const t=new TaskClient({subscribeKey:b,publishKey:d,baseUrl:h,createSessionPubNub:f,defaultOwnerId:e.getUserId()??void 0});return t.config.authProvider=e,t._consumerAuth=e,t}return new TaskClient({subscribeKey:b,publishKey:d,baseUrl:h,createSessionPubNub:f})}getUserId(){return this._consumerAuth?.getUserId()??null}async getAgentCard(e){const t=await k(e,{baseUrl:this.config.baseUrl});return t?.card??null}updateKeys(e,t){this.config.subscribeKey=e,this._subscribeKey=e,this._publishKey=t??""}getPubNub(){if(!this._pubnub&&this._createPubNub&&(this._pubnub=this._createPubNub()),!this._pubnub)throw new Error("TaskClient requires a pubnub instance for subscribe. Pass pubnub or createPubNub in TaskClientOptions.");return this._pubnub}destroy(){this._pubnub&&this._ownsPubNub&&(this._pubnub.destroy(),this._pubnub=void 0),this._consumerAuth&&this._consumerAuth.destroy()}[Symbol.dispose](){this.destroy()}createPerSessionPubNub(t){let s;if(this._createSessionPubNub)s=this._createSessionPubNub();else{const t=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;s=new e({subscribeKey:this._subscribeKey,publishKey:this._publishKey||void 0,userId:t,enableEventEngine:!0})}return t&&s.setToken(t),s}async fetchConsumerReadToken(e){if(!this.config.baseUrl)throw new Error("connect() requires a backend baseUrl. Set baseUrl in TaskClientOptions.");const t=`${this.config.baseUrl.replace(/\/+$/,"")}/api/v1/auth/task-read-token`,s=async()=>{const s={"Content-Type":"application/json",[y]:m},n=this.config.authProvider?.getAuthHeader();return n&&(s.Authorization=n),d(s),fetch(t,{method:"POST",headers:s,body:JSON.stringify({taskId:e,role:"consumer"})})};let n=await s();if(b(n.headers),401===n.status&&this.config.authProvider){await this.config.authProvider.onAuthFailure()&&(n=await s(),b(n.headers))}if(!n.ok){const e=await n.text().catch(()=>"");throw new Error(`task-read-token failed: HTTP ${n.status}${e?` ${e}`:""}`)}return n.json()}async sendMessage(e){const s=e.ownerId||this.defaultOwnerId||"",i=e.taskKind,u=e.duration;if("pipe"===i){if(null==u||!Number.isInteger(u)||u<1||u>43200)throw new Error("Pipe tasks require a duration between 1 and 43200 minutes")}else if(null!=u)throw new Error("Request tasks must not include a duration. Duration is only valid for pipe tasks.");let b;const d=[];for(const t of e.requestParts)if(t.file){if(!t.partId)throw new Error("partId is required for file-bearing request parts");const s=c(t.file),n=t.fileName??"unnamed",i=t.contentType??"application/octet-stream";if(r(s.size)){const e=await s.getBytes(),r=o({data:e,mimeType:i,fileName:n}),{file:a,fileName:c,text:u,...b}=t;d.push({...b,artifactRef:r})}else{if(!this.config.baseUrl)throw new Error("File upload requires a backend baseUrl. Set baseUrl in TaskClientOptions.");const r={baseUrl:this.config.baseUrl,authProvider:this.config.authProvider,agentAuth:this.config.agentAuth},o={role:"consumer-input",agentName:e.agentName,fileName:n,fileSize:s.size,mimeType:i,partId:t.partId,uploadSessionId:b},c=await a(r,o,s.uploadBody);c.uploadSessionId&&(b=c.uploadSessionId);const{file:u,fileName:l,...h}=t;d.push({partId:h.partId})}}else{const{file:e,fileName:s,...n}=t;d.push(n)}const l={agentName:e.agentName,requestParts:d};b&&(l.uploadSessionId=b),e.idempotencyKey&&(l.idempotencyKey=e.idempotencyKey),l.ownerId=s;const h={};i&&(h.taskKind=i),null!=u&&(h.duration=u),e.consumerPublicKey&&(h.consumerPublicKey=e.consumerPublicKey),Object.keys(h).length>0&&(l.extensions={blocks:h}),e.pushNotificationConfig&&(l.pushNotificationConfig=e.pushNotificationConfig),e.retryPolicy&&(l.retryPolicy=e.retryPolicy);const f=await t(this.config,"SendMessage",l),p=f.extensions?.blocks,m=p?.readToken??null,y=p?.streamChannels?.status??void 0;if(!0===f.idempotent&&!!f.state&&g.has(f.state))return new n({taskId:f.taskId,ownerId:s,orgId:f.orgId??s,readToken:m,statusChannel:y,agentName:e.agentName,pubnub:null,ownsSubscribeClient:!1,sdkOptions:{subscribeKey:this._subscribeKey,publishKey:this._publishKey},rpcConfig:this.config,idempotent:f.idempotent,queued:f.queued,pushConfigId:f.pushConfigId,preClosed:!0,state:f.state});const k=this.createPerSessionPubNub(m);return new n({taskId:f.taskId,ownerId:s,orgId:f.orgId??s,readToken:m,statusChannel:y,agentName:e.agentName,pubnub:k,ownsSubscribeClient:!0,sdkOptions:{subscribeKey:this._subscribeKey,publishKey:this._publishKey},rpcConfig:this.config,idempotent:f.idempotent,queued:f.queued,pushConfigId:f.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}async connect(e){const{taskId:t}=e;if(!this.config.authProvider?.getAuthHeader())throw new Error("connect() requires an authenticated TaskClient. Use apiKey, tokenEndpoint, or tokenProvider. AgentAuth is not supported for consumer task connections.");const s=await this.getTask(t);if(!s)throw new Error(`Task not found: ${t}`);const i=s.agentName??"",r=s.state??"",o=await this.fetchConsumerReadToken(t),{pamToken:a,channel:c}=o,u={subscribeKey:this._subscribeKey,publishKey:this._publishKey},b=this.createPerSessionPubNub(a);try{const o=await b.time(),d=String(o.timetoken),l=p(b);let m,y=new Map,k=[],w="0";if(l?.fetchMessages){const e=await async function(e,t){const s=[];let n;for(;;){const i={channels:[t],count:100};n&&(i.start=n);const r=await e.fetchMessages(i),o=r.channels?.[t]??[];if(0===o.length)break;if(s.push(...o),o.length<100)break;n=o[0].timetoken}return s.sort((e,t)=>e.timetoken<t.timetoken?-1:e.timetoken>t.timetoken?1:0),s}(l,c),s=function(e,t,s,n){const i=new Map,r=[];let o,a="0",c="0";for(const u of e){u.timetoken&&u.timetoken>a&&(a=u.timetoken);const e=u.message;if(e&&"object"==typeof e&&e.type){if("terminal"===e.type){const t=String(u.timetoken??"0");if(t>=c){const s=e.state;"completed"!==s&&"failed"!==s&&"canceled"!==s||(o=s,c=t)}}if("progress"===e.type&&"stream_started"===e.streamEvent&&e.streams){const r=e.declaredStream,o=e.streams;for(const[e,a]of Object.entries(o)){if(!a||"object"!=typeof a||i.has(e))continue;const o=a.direction,c=f(o),u=a.format;if(!u||"bytes"!==u&&"events"!==u)continue;const b=a.affinity;if("dedicated"!==b&&"shared"!==b){console.warn(`[TaskClient] history-preload: dropping stream "${e}" for task "${t}" — invalid or missing affinity (got ${JSON.stringify(a.affinity)})`);continue}const d={taskId:t,streamId:e,agentName:s,channel:a.channel,token:a.token,agentDirection:o,localDirection:c,format:u,affinity:b,metadata:a.metadata,declaredStream:r};i.set(e,new h(d,n))}}if("artifact"===e.type&&e.artifactRef){const t=e.artifactRef;t&&"object"==typeof t&&t.kind&&r.push(t)}}}return{streams:i,artifacts:r,highWaterMark:a,terminalState:o}}(e,t,i,u);y=s.streams,k=s.artifacts,w=s.highWaterMark,m=s.terminalState}const K=m&&g.has(m)?m:r;if(g.has(K))return new n({taskId:t,ownerId:s.owner??"",readToken:a,statusChannel:c,agentName:i,pubnub:b,ownsSubscribeClient:!0,sdkOptions:u,rpcConfig:this.config,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,state:K,skipSubscription:!0,preloadedStreams:y,preloadedArtifacts:k});const P="0"!==w?w:d,T=[];let S,I=!1;const C={message:e=>{if(e.channel!==c)return;const t=e.message;if(!t||"object"!=typeof t||!t.type)return;const s=String(e.timetoken??"0");I&&S?S(t,s):T.push({message:t,timetoken:s})}};b.addListener(C),b.subscribe({channels:[c],timetoken:P});const N=new n({taskId:t,ownerId:s.owner??"",readToken:a,statusChannel:c,agentName:i,pubnub:b,ownsSubscribeClient:!0,sdkOptions:u,rpcConfig:this.config,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,state:r,preloadedStreams:y,preloadedArtifacts:k,externalSubscription:{listener:C,channel:c,onReady:e=>{S=e}}});if(S){const e=S;for(const t of T)t.timetoken>P&&e(t.message,t.timetoken)}return T.length=0,I=!0,N}catch(e){throw b.destroy(),e}}async getTask(e){return(await t(this.config,"GetTask",{taskId:e})).task}async listTasks(e){return t(this.config,"ListTasks",{...e})}async cancelTask(e){await t(this.config,"CancelTask",{taskId:e})}async pauseTask(e){await t(this.config,"PauseTask",{taskId:e})}async resumeTask(e){await t(this.config,"ResumeTask",{taskId:e})}async retryTask(e){await t(this.config,"RetryTask",{taskId:e})}async terminateTask(e){await t(this.config,"TerminateTask",{taskId:e})}subscribeToTask(e,t,n){return function(e,t,n,i){const r=s(t,n),o={message:e=>{if(e.channel!==r)return;const t=e.message;if(t&&"object"==typeof t&&t.type){if(i.onEvent)try{i.onEvent(t)}catch(e){w(e,"onEvent",t,i.onError)}switch(t.type){case"progress":if(i.onProgress)try{i.onProgress(t)}catch(e){w(e,"onProgress",t,i.onError)}break;case"artifact":if(i.onArtifact)try{i.onArtifact(t)}catch(e){w(e,"onArtifact",t,i.onError)}break;case"terminal":if(i.onTerminal)try{i.onTerminal(t)}catch(e){w(e,"onTerminal",t,i.onError)}break;case"system":if(i.onSystem)try{i.onSystem(t)}catch(e){w(e,"onSystem",t,i.onError)}}}}};return e.addListener(o),e.subscribe({channels:[r],timetoken:1e3}),{unsubscribe(){e.removeListener(o),e.unsubscribe({channels:[r]})}}}(this.getPubNub(),e,t,n)}}function w(e,t,s,n){const i=e instanceof Error?e:new Error(String(e));if(n)try{n(i,{entryPoint:"subscribeToTask",callbackType:t,event:s})}catch{}else console.warn(`[subscribeToTask] callback error in ${t}:`,i.message)}
1
+ import e from"pubnub";import{callRpc as t}from"./rpc-client.js";import{taskChannel as s}from"./channel-manager.js";import{TaskSession as n}from"./task-session.js";import{ConsumerAuth as i}from"./consumer-auth.js";import{shouldInlineArtifact as r,buildArtifactRef as o}from"./artifacts.js";import{uploadFile as a}from"./file-upload.js";import{normalizeFileInput as c}from"./file-input.js";import{fetchCdmConfig as u}from"./cdm-config.js";import{captureAffinity as b,injectAffinity as d}from"./write-affinity.js";import{getEnv as l}from"../env.js";import{StreamRef as h}from"./stream-ref.js";import{invertDirection as f}from"../stream/index.js";import{asPubNubFetcher as p}from"./pubnub-types.js";import{CURRENT_PROTOCOL_VERSION as m,PROTOCOL_VERSION_HEADER as y}from"./protocol-version.js";import{getAgent as k}from"./agent-registry.js";const g=new Set(["completed","failed","canceled"]);export class TaskClient{constructor(e){this._ownsPubNub=!1,this.config={subscribeKey:e.subscribeKey,authProvider:e.authProvider,baseUrl:e.baseUrl,agentAuth:e.agentAuth},this._pubnub=e.pubnub,this._createPubNub=e.createPubNub,this._createSessionPubNub=e.createSessionPubNub,this.defaultOwnerId=e.defaultOwnerId,this._subscribeKey=e.subscribeKey,this._publishKey=e.publishKey??"",this._ownsPubNub=!e.pubnub&&!!e.createPubNub}static async create(t){const s=t??{},n=s.listing;if(!n)throw new Error("TaskClient.create() requires a listing option ('playground', 'private', or 'public') to select the CDM keyset.");const r=!!(s.apiKey||s.tokenEndpoint||s.tokenProvider);if([s.apiKey,s.tokenEndpoint,s.tokenProvider].filter(Boolean).length>1)throw new Error("Only one token provider mode may be specified");const o=s.cdmUrl??l("BLOCKS_CDM_URL"),a=await u(o),c="playground"===n?a.playground:a.network,b=s.subscribeKey??l("BLOCKS_SUBSCRIBE_KEY")??c.subscribeKey,d=s.publishKey??l("BLOCKS_PUBLISH_KEY")??c.publishKey,h=s.baseUrl??l("BLOCKS_BACKEND_URL")??a.api.baseUrl;if(!b)throw new Error("TaskClient.create() could not resolve subscribeKey from options, env, or CDM");if(!h)throw new Error("TaskClient.create() could not resolve baseUrl. Set baseUrl option, BLOCKS_BACKEND_URL env var, or ensure CDM config has api.baseUrl.");const f=()=>{const t=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;return new e({subscribeKey:b,publishKey:d||void 0,userId:t,enableEventEngine:!0})};if(r){const e=new i({apiKey:s.apiKey,tokenEndpoint:s.tokenEndpoint,tokenProvider:s.tokenProvider,baseUrl:h,onAuthError:s.onAuthError});await e.init();const t=new TaskClient({subscribeKey:b,publishKey:d,baseUrl:h,createSessionPubNub:f,defaultOwnerId:e.getUserId()??void 0});return t.config.authProvider=e,t._consumerAuth=e,t}return new TaskClient({subscribeKey:b,publishKey:d,baseUrl:h,createSessionPubNub:f})}getUserId(){return this._consumerAuth?.getUserId()??null}async getAgentCard(e){const t=await k(e,{baseUrl:this.config.baseUrl});return t?.card??null}updateKeys(e,t){this.config.subscribeKey=e,this._subscribeKey=e,this._publishKey=t??""}getPubNub(){if(!this._pubnub&&this._createPubNub&&(this._pubnub=this._createPubNub()),!this._pubnub)throw new Error("TaskClient requires a pubnub instance for subscribe. Pass pubnub or createPubNub in TaskClientOptions.");return this._pubnub}destroy(){this._pubnub&&this._ownsPubNub&&(this._pubnub.destroy(),this._pubnub=void 0),this._consumerAuth&&this._consumerAuth.destroy()}[Symbol.dispose](){this.destroy()}createPerSessionPubNub(t){let s;if(this._createSessionPubNub)s=this._createSessionPubNub();else{const t=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;s=new e({subscribeKey:this._subscribeKey,publishKey:this._publishKey||void 0,userId:t,enableEventEngine:!0})}return t&&s.setToken(t),s}async fetchConsumerReadToken(e){if(!this.config.baseUrl)throw new Error("connect() requires a backend baseUrl. Set baseUrl in TaskClientOptions.");const t=`${this.config.baseUrl.replace(/\/+$/,"")}/api/v1/auth/task-read-token`,s=async()=>{const s={"Content-Type":"application/json",[y]:m},n=this.config.authProvider?.getAuthHeader();return n&&(s.Authorization=n),d(s),fetch(t,{method:"POST",headers:s,body:JSON.stringify({taskId:e,role:"consumer"})})};let n=await s();if(b(n.headers),401===n.status&&this.config.authProvider){await this.config.authProvider.onAuthFailure()&&(n=await s(),b(n.headers))}if(!n.ok){const e=await n.text().catch(()=>"");throw new Error(`task-read-token failed: HTTP ${n.status}${e?` ${e}`:""}`)}return n.json()}async sendMessage(e){const s=e.ownerId||this.defaultOwnerId||"",i=e.taskKind,u=e.duration;if("pipe"===i){if(null==u||!Number.isInteger(u)||u<1||u>43200)throw new Error("Pipe tasks require a duration between 1 and 43200 minutes")}else if(null!=u)throw new Error("Request tasks must not include a duration. Duration is only valid for pipe tasks.");let b;const d=[];for(const t of e.requestParts)if(t.file){if(!t.partId)throw new Error("partId is required for file-bearing request parts");const s=c(t.file),n=t.fileName??"unnamed",i=t.contentType??"application/octet-stream";if(r(s.size)){const e=await s.getBytes(),r=o({data:e,mimeType:i,fileName:n}),{file:a,fileName:c,text:u,...b}=t;d.push({...b,artifactRef:r})}else{if(!this.config.baseUrl)throw new Error("File upload requires a backend baseUrl. Set baseUrl in TaskClientOptions.");const r={baseUrl:this.config.baseUrl,authProvider:this.config.authProvider,agentAuth:this.config.agentAuth},o={role:"consumer-input",agentName:e.agentName,fileName:n,fileSize:s.size,mimeType:i,partId:t.partId,uploadSessionId:b},c=await a(r,o,s.uploadBody);c.uploadSessionId&&(b=c.uploadSessionId);const{file:u,fileName:l,...h}=t;d.push({partId:h.partId,...h.contentType?{contentType:h.contentType}:{}})}}else{const{file:e,fileName:s,...n}=t;d.push(n)}const l={agentName:e.agentName,requestParts:d};b&&(l.uploadSessionId=b),e.idempotencyKey&&(l.idempotencyKey=e.idempotencyKey),l.ownerId=s;const h={};i&&(h.taskKind=i),null!=u&&(h.duration=u),e.consumerPublicKey&&(h.consumerPublicKey=e.consumerPublicKey),Object.keys(h).length>0&&(l.extensions={blocks:h}),e.pushNotificationConfig&&(l.pushNotificationConfig=e.pushNotificationConfig),e.retryPolicy&&(l.retryPolicy=e.retryPolicy);const f=await t(this.config,"SendMessage",l),p=f.extensions?.blocks,m=p?.readToken??null,y=p?.streamChannels?.status??void 0;if(!0===f.idempotent&&!!f.state&&g.has(f.state))return new n({taskId:f.taskId,ownerId:s,orgId:f.orgId??s,readToken:m,statusChannel:y,agentName:e.agentName,pubnub:null,ownsSubscribeClient:!1,sdkOptions:{subscribeKey:this._subscribeKey,publishKey:this._publishKey},rpcConfig:this.config,idempotent:f.idempotent,queued:f.queued,pushConfigId:f.pushConfigId,preClosed:!0,state:f.state});const k=this.createPerSessionPubNub(m);return new n({taskId:f.taskId,ownerId:s,orgId:f.orgId??s,readToken:m,statusChannel:y,agentName:e.agentName,pubnub:k,ownsSubscribeClient:!0,sdkOptions:{subscribeKey:this._subscribeKey,publishKey:this._publishKey},rpcConfig:this.config,idempotent:f.idempotent,queued:f.queued,pushConfigId:f.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}async connect(e){const{taskId:t}=e;if(!this.config.authProvider?.getAuthHeader())throw new Error("connect() requires an authenticated TaskClient. Use apiKey, tokenEndpoint, or tokenProvider. AgentAuth is not supported for consumer task connections.");const s=await this.getTask(t);if(!s)throw new Error(`Task not found: ${t}`);const i=s.agentName??"",r=s.state??"",o=await this.fetchConsumerReadToken(t),{pamToken:a,channel:c}=o,u={subscribeKey:this._subscribeKey,publishKey:this._publishKey},b=this.createPerSessionPubNub(a);try{const o=await b.time(),d=String(o.timetoken),l=p(b);let m,y=new Map,k=[],w="0";if(l?.fetchMessages){const e=await async function(e,t){const s=[];let n;for(;;){const i={channels:[t],count:100};n&&(i.start=n);const r=await e.fetchMessages(i),o=r.channels?.[t]??[];if(0===o.length)break;if(s.push(...o),o.length<100)break;n=o[0].timetoken}return s.sort((e,t)=>e.timetoken<t.timetoken?-1:e.timetoken>t.timetoken?1:0),s}(l,c),s=function(e,t,s,n){const i=new Map,r=[];let o,a="0",c="0";for(const u of e){u.timetoken&&u.timetoken>a&&(a=u.timetoken);const e=u.message;if(e&&"object"==typeof e&&e.type){if("terminal"===e.type){const t=String(u.timetoken??"0");if(t>=c){const s=e.state;"completed"!==s&&"failed"!==s&&"canceled"!==s||(o=s,c=t)}}if("progress"===e.type&&"stream_started"===e.streamEvent&&e.streams){const r=e.declaredStream,o=e.streams;for(const[e,a]of Object.entries(o)){if(!a||"object"!=typeof a||i.has(e))continue;const o=a.direction,c=f(o),u=a.format;if(!u||"bytes"!==u&&"events"!==u)continue;const b=a.affinity;if("dedicated"!==b&&"shared"!==b){console.warn(`[TaskClient] history-preload: dropping stream "${e}" for task "${t}" — invalid or missing affinity (got ${JSON.stringify(a.affinity)})`);continue}const d={taskId:t,streamId:e,agentName:s,channel:a.channel,token:a.token,agentDirection:o,localDirection:c,format:u,affinity:b,metadata:a.metadata,declaredStream:r};i.set(e,new h(d,n))}}if("artifact"===e.type&&e.artifactRef){const t=e.artifactRef;t&&"object"==typeof t&&t.kind&&r.push(t)}}}return{streams:i,artifacts:r,highWaterMark:a,terminalState:o}}(e,t,i,u);y=s.streams,k=s.artifacts,w=s.highWaterMark,m=s.terminalState}const K=m&&g.has(m)?m:r;if(g.has(K))return new n({taskId:t,ownerId:s.owner??"",readToken:a,statusChannel:c,agentName:i,pubnub:b,ownsSubscribeClient:!0,sdkOptions:u,rpcConfig:this.config,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,state:K,skipSubscription:!0,preloadedStreams:y,preloadedArtifacts:k});const T="0"!==w?w:d,P=[];let S,I=!1;const C={message:e=>{if(e.channel!==c)return;const t=e.message;if(!t||"object"!=typeof t||!t.type)return;const s=String(e.timetoken??"0");I&&S?S(t,s):P.push({message:t,timetoken:s})}};b.addListener(C),b.subscribe({channels:[c],timetoken:T});const N=new n({taskId:t,ownerId:s.owner??"",readToken:a,statusChannel:c,agentName:i,pubnub:b,ownsSubscribeClient:!0,sdkOptions:u,rpcConfig:this.config,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,state:r,preloadedStreams:y,preloadedArtifacts:k,externalSubscription:{listener:C,channel:c,onReady:e=>{S=e}}});if(S){const e=S;for(const t of P)t.timetoken>T&&e(t.message,t.timetoken)}return P.length=0,I=!0,N}catch(e){throw b.destroy(),e}}async getTask(e){return(await t(this.config,"GetTask",{taskId:e})).task}async listTasks(e){return t(this.config,"ListTasks",{...e})}async cancelTask(e){await t(this.config,"CancelTask",{taskId:e})}async pauseTask(e){await t(this.config,"PauseTask",{taskId:e})}async resumeTask(e){await t(this.config,"ResumeTask",{taskId:e})}async retryTask(e){await t(this.config,"RetryTask",{taskId:e})}async terminateTask(e){await t(this.config,"TerminateTask",{taskId:e})}subscribeToTask(e,t,n){return function(e,t,n,i){const r=s(t,n),o={message:e=>{if(e.channel!==r)return;const t=e.message;if(t&&"object"==typeof t&&t.type){if(i.onEvent)try{i.onEvent(t)}catch(e){w(e,"onEvent",t,i.onError)}switch(t.type){case"progress":if(i.onProgress)try{i.onProgress(t)}catch(e){w(e,"onProgress",t,i.onError)}break;case"artifact":if(i.onArtifact)try{i.onArtifact(t)}catch(e){w(e,"onArtifact",t,i.onError)}break;case"terminal":if(i.onTerminal)try{i.onTerminal(t)}catch(e){w(e,"onTerminal",t,i.onError)}break;case"system":if(i.onSystem)try{i.onSystem(t)}catch(e){w(e,"onSystem",t,i.onError)}}}}};return e.addListener(o),e.subscribe({channels:[r],timetoken:1e3}),{unsubscribe(){e.removeListener(o),e.unsubscribe({channels:[r]})}}}(this.getPubNub(),e,t,n)}}function w(e,t,s,n){const i=e instanceof Error?e:new Error(String(e));if(n)try{n(i,{entryPoint:"subscribeToTask",callbackType:t,event:s})}catch{}else console.warn(`[subscribeToTask] callback error in ${t}:`,i.message)}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@blocks-network/sdk",
3
- "version": "0.1.47",
3
+ "version": "0.1.49",
4
4
  "type": "module",
5
- "description": "Blocks Network SDK — build and run A2A agents on Blocks Network",
5
+ "description": "Blocks Network SDK — build and run A2A agents on PubNub",
6
6
  "exports": {
7
7
  ".": {
8
8
  "browser": {
@@ -58,14 +58,14 @@
58
58
  },
59
59
  "repository": {
60
60
  "type": "git",
61
- "url": "git+https://github.com/blocksnetwork/blocks-sdk.git",
61
+ "url": "git+https://github.com/pubnub/blocksnetwork.git",
62
62
  "directory": "blocks-sdk/sdks/node"
63
63
  },
64
- "homepage": "https://github.com/blocksnetwork/blocks-sdk/tree/master/sdks/node#readme",
64
+ "homepage": "https://github.com/pubnub/blocksnetwork/tree/main/blocks-sdk/sdks/node#readme",
65
65
  "bugs": {
66
- "url": "https://github.com/blocksnetwork/blocks-sdk/issues"
66
+ "url": "https://github.com/pubnub/blocksnetwork/issues"
67
67
  },
68
- "license": "Blocks Network",
68
+ "license": "PubNub",
69
69
  "overrides": {
70
70
  "pubnub": {
71
71
  "react-native-url-polyfill": "npm:empty-npm-package@1.0.0"