@blocks-network/sdk 0.1.49 → 0.1.50

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
@@ -17,7 +17,7 @@ import type { TaskContext } from '@blocks-network/sdk';
17
17
 
18
18
  export default async function handler(task: any, ctx: TaskContext) {
19
19
  const input = task.requestParts?.[0]?.text ?? '';
20
- ctx.reportStatus('working', `Processing: ${input}`);
20
+ ctx.reportStatus(`Processing: ${input}`);
21
21
  return { artifacts: [{ data: `Echo: ${input}`, mimeType: 'text/plain' }] };
22
22
  }
23
23
  ```
@@ -26,13 +26,14 @@ Create an `agent-card.json`:
26
26
 
27
27
  ```json
28
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 },
29
+ "identity": {
30
+ "agentName": "my_agent",
31
+ "displayName": "My Agent",
32
+ "description": "An example agent",
33
+ "version": "1.0.0",
34
+ "provider": { "organization": "Your Org" }
35
+ },
36
+ "capabilities": { "taskKinds": ["request"] },
36
37
  "skills": [{ "id": "main", "name": "Main Skill" }],
37
38
  "runtime": {
38
39
  "handler": "./handler.ts"
@@ -68,13 +69,14 @@ The agent card follows the A2A specification with a `runtime` extension:
68
69
 
69
70
  ```json
70
71
  {
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 },
72
+ "identity": {
73
+ "agentName": "my_agent",
74
+ "displayName": "My Agent",
75
+ "description": "An example agent",
76
+ "version": "1.0.0",
77
+ "provider": { "organization": "Your Org" }
78
+ },
79
+ "capabilities": { "taskKinds": ["request"] },
78
80
  "skills": [{ "id": "main", "name": "Main Skill" }],
79
81
  "runtime": {
80
82
  "handler": "./handler.ts",
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_CDM_URL = "https://config.blocksnetwork.functions.pdx1.aws.int.ps.pn/config.json";
1
+ export declare const DEFAULT_CDM_URL = "https://config.blocks.ai/config.json";
2
2
  export interface CdmKeyset {
3
3
  publishKey: string;
4
4
  subscribeKey: string;
@@ -1 +1 @@
1
- import{getEnv as n}from"../env.js";export const DEFAULT_CDM_URL="https://config.blocksnetwork.functions.pdx1.aws.int.ps.pn/config.json";export async function fetchCdmConfig(o){const t=o??n("BLOCKS_CDM_URL")??DEFAULT_CDM_URL,s=await fetch(t);if(!s.ok)throw new Error(`CDM config fetch failed: ${s.status} ${s.statusText}`);const r=await s.json();if(!r.playground?.publishKey||!r.playground?.subscribeKey)throw new Error("CDM config missing playground keys");if(!r.network?.publishKey||!r.network?.subscribeKey)throw new Error("CDM config missing network keys");return r}
1
+ import{getEnv as o}from"../env.js";export const DEFAULT_CDM_URL="https://config.blocks.ai/config.json";export async function fetchCdmConfig(n){const r=n??o("BLOCKS_CDM_URL")??DEFAULT_CDM_URL,t=await fetch(r);if(!t.ok)throw new Error(`CDM config fetch failed: ${t.status} ${t.statusText}`);const s=await t.json();if(!s.playground?.publishKey||!s.playground?.subscribeKey)throw new Error("CDM config missing playground keys");if(!s.network?.publishKey||!s.network?.subscribeKey)throw new Error("CDM config missing network keys");return s}
@@ -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,...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)}
1
+ import e from"pubnub";import{callRpc as t}from"./rpc-client.js";import{taskChannel as n}from"./channel-manager.js";import{TaskSession as s}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 d,injectAffinity as b}from"./write-affinity.js";import{getEnv as l}from"../env.js";import{StreamRef as f}from"./stream-ref.js";import{invertDirection as h}from"../stream/index.js";import{asPubNubFetcher as p}from"./pubnub-types.js";import{CURRENT_PROTOCOL_VERSION as m,PROTOCOL_VERSION_HEADER as g}from"./protocol-version.js";import{getAgent as k}from"./agent-registry.js";const y=new Set(["completed","failed","canceled"]);function w(e,t,n,s){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 n=e.state;"completed"!==n&&"failed"!==n&&"canceled"!==n||(o=n,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=h(o),u=a.format;if(!u||"bytes"!==u&&"events"!==u)continue;const d=a.affinity;if("dedicated"!==d&&"shared"!==d){console.warn(`[TaskClient] history-preload: dropping stream "${e}" for task "${t}" — invalid or missing affinity (got ${JSON.stringify(a.affinity)})`);continue}const b={taskId:t,streamId:e,agentName:n,channel:a.channel,token:a.token,agentDirection:o,localDirection:c,format:u,affinity:d,metadata:a.metadata,declaredStream:r};i.set(e,new f(b,s))}}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}}async function I(e,t){const n=[];let s;for(;;){const i={channels:[t],count:100};s&&(i.start=s);const r=await e.fetchMessages(i),o=r.channels?.[t]??[];if(0===o.length)break;if(n.push(...o),o.length<100)break;s=o[0].timetoken}return n.sort((e,t)=>e.timetoken<t.timetoken?-1:e.timetoken>t.timetoken?1:0),n}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 n=t??{},s=n.listing;if(!s)throw new Error("TaskClient.create() requires a listing option ('playground', 'private', or 'public') to select the CDM keyset.");const r=!!(n.apiKey||n.tokenEndpoint||n.tokenProvider);if([n.apiKey,n.tokenEndpoint,n.tokenProvider].filter(Boolean).length>1)throw new Error("Only one token provider mode may be specified");const o=n.cdmUrl??l("BLOCKS_CDM_URL"),a=await u(o),c="playground"===s?a.playground:a.network,d=n.subscribeKey??l("BLOCKS_SUBSCRIBE_KEY")??c.subscribeKey,b=n.publishKey??l("BLOCKS_PUBLISH_KEY")??c.publishKey,f=n.baseUrl??l("BLOCKS_BACKEND_URL")??a.api.baseUrl;if(!d)throw new Error("TaskClient.create() could not resolve subscribeKey from options, env, or CDM");if(!f)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 h=()=>{const t=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;return new e({subscribeKey:d,publishKey:b||void 0,userId:t,enableEventEngine:!0})};if(r){const e=new i({apiKey:n.apiKey,tokenEndpoint:n.tokenEndpoint,tokenProvider:n.tokenProvider,baseUrl:f,onAuthError:n.onAuthError});await e.init();const t=new TaskClient({subscribeKey:d,publishKey:b,baseUrl:f,createSessionPubNub:h,defaultOwnerId:e.getUserId()??void 0});return t.config.authProvider=e,t._consumerAuth=e,t}return new TaskClient({subscribeKey:d,publishKey:b,baseUrl:f,createSessionPubNub:h})}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 n;if(this._createSessionPubNub)n=this._createSessionPubNub();else{const t=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;n=new e({subscribeKey:this._subscribeKey,publishKey:this._publishKey||void 0,userId:t,enableEventEngine:!0})}return t&&n.setToken(t),n}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`,n=async()=>{const n={"Content-Type":"application/json",[g]:m},s=this.config.authProvider?.getAuthHeader();return s&&(n.Authorization=s),b(n),fetch(t,{method:"POST",headers:n,body:JSON.stringify({taskId:e,role:"consumer"})})};let s=await n();if(d(s.headers),401===s.status&&this.config.authProvider){await this.config.authProvider.onAuthFailure()&&(s=await n(),d(s.headers))}if(!s.ok){const e=await s.text().catch(()=>"");throw new Error(`task-read-token failed: HTTP ${s.status}${e?` ${e}`:""}`)}return s.json()}async sendMessage(e){const i=e.ownerId||this.defaultOwnerId||"",u=e.taskKind,d=e.duration;if("pipe"===u){if(null==d||!Number.isInteger(d)||d<1||d>43200)throw new Error("Pipe tasks require a duration between 1 and 43200 minutes")}else if(null!=d)throw new Error("Request tasks must not include a duration. Duration is only valid for pipe tasks.");let b;const l=[];for(const t of e.requestParts)if(t.file){if(!t.partId)throw new Error("partId is required for file-bearing request parts");const n=c(t.file),s=t.fileName??"unnamed",i=t.contentType??"application/octet-stream";if(r(n.size)){const e=await n.getBytes(),r=o({data:e,mimeType:i,fileName:s}),{file:a,fileName:c,text:u,...d}=t;l.push({...d,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:s,fileSize:n.size,mimeType:i,partId:t.partId,uploadSessionId:b},c=await a(r,o,n.uploadBody);c.uploadSessionId&&(b=c.uploadSessionId);const{file:u,fileName:d,...f}=t;l.push({partId:f.partId,...f.contentType?{contentType:f.contentType}:{}})}}else{const{file:e,fileName:n,...s}=t;l.push(s)}const f={agentName:e.agentName,requestParts:l};b&&(f.uploadSessionId=b),e.idempotencyKey&&(f.idempotencyKey=e.idempotencyKey),f.ownerId=i;const h={};u&&(h.taskKind=u),null!=d&&(h.duration=d),e.consumerPublicKey&&(h.consumerPublicKey=e.consumerPublicKey),Object.keys(h).length>0&&(f.extensions={blocks:h}),e.pushNotificationConfig&&(f.pushNotificationConfig=e.pushNotificationConfig),e.retryPolicy&&(f.retryPolicy=e.retryPolicy);const m=await t(this.config,"SendMessage",f),g=m.extensions?.blocks,k=g?.readToken??null,S=g?.streamChannels?.status??void 0;if(!0===m.idempotent&&!!m.state&&y.has(m.state))return new s({taskId:m.taskId,ownerId:i,orgId:m.orgId??i,readToken:k,statusChannel:S,agentName:e.agentName,pubnub:null,ownsSubscribeClient:!1,sdkOptions:{subscribeKey:this._subscribeKey,publishKey:this._publishKey},rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,preClosed:!0,state:m.state});const C=this.createPerSessionPubNub(k),T={subscribeKey:this._subscribeKey,publishKey:this._publishKey},K=S??n(m.taskId,m.orgId??i);try{const t=await C.time(),n=String(t.timetoken),r=p(C);let o,a=new Map,c=[],u="0";if(r?.fetchMessages){const t=w(await I(r,K),m.taskId,e.agentName,T);a=t.streams,c=t.artifacts,u=t.highWaterMark,o=t.terminalState}if(o&&y.has(o))return new s({taskId:m.taskId,ownerId:i,orgId:m.orgId??i,readToken:k,statusChannel:K,agentName:e.agentName,pubnub:C,ownsSubscribeClient:!0,sdkOptions:T,rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,state:o,skipSubscription:!0,preloadedStreams:a,preloadedArtifacts:c});const d="0"!==u?u:n,b=[];let l,f=!1;const h={message:e=>{if(e.channel!==K)return;const t=e.message;if(!t||"object"!=typeof t||!t.type)return;const n=String(e.timetoken??"0");f&&l?l(t,n):b.push({message:t,timetoken:n})}};C.addListener(h),C.subscribe({channels:[K],timetoken:d});const g=new s({taskId:m.taskId,ownerId:i,orgId:m.orgId??i,readToken:k,statusChannel:K,agentName:e.agentName,pubnub:C,ownsSubscribeClient:!0,sdkOptions:T,rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,preloadedStreams:a,preloadedArtifacts:c,externalSubscription:{listener:h,channel:K,onReady:e=>{l=e}}});if(l){const e=l;for(const t of b)t.timetoken>d&&e(t.message,t.timetoken)}return b.length=0,f=!0,g}catch{return new s({taskId:m.taskId,ownerId:i,orgId:m.orgId??i,readToken:k,statusChannel:K,agentName:e.agentName,pubnub:C,ownsSubscribeClient:!0,sdkOptions:T,rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.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 n=await this.getTask(t);if(!n)throw new Error(`Task not found: ${t}`);const i=n.agentName??"",r=n.state??"",o=await this.fetchConsumerReadToken(t),{pamToken:a,channel:c}=o,u={subscribeKey:this._subscribeKey,publishKey:this._publishKey},d=this.createPerSessionPubNub(a);try{const o=await d.time(),b=String(o.timetoken),l=p(d);let f,h=new Map,m=[],g="0";if(l?.fetchMessages){const e=w(await I(l,c),t,i,u);h=e.streams,m=e.artifacts,g=e.highWaterMark,f=e.terminalState}const k=f&&y.has(f)?f:r;if(y.has(k))return new s({taskId:t,ownerId:n.owner??"",readToken:a,statusChannel:c,agentName:i,pubnub:d,ownsSubscribeClient:!0,sdkOptions:u,rpcConfig:this.config,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,state:k,skipSubscription:!0,preloadedStreams:h,preloadedArtifacts:m});const S="0"!==g?g:b,C=[];let T,K=!1;const P={message:e=>{if(e.channel!==c)return;const t=e.message;if(!t||"object"!=typeof t||!t.type)return;const n=String(e.timetoken??"0");K&&T?T(t,n):C.push({message:t,timetoken:n})}};d.addListener(P),d.subscribe({channels:[c],timetoken:S});const N=new s({taskId:t,ownerId:n.owner??"",readToken:a,statusChannel:c,agentName:i,pubnub:d,ownsSubscribeClient:!0,sdkOptions:u,rpcConfig:this.config,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,state:r,preloadedStreams:h,preloadedArtifacts:m,externalSubscription:{listener:P,channel:c,onReady:e=>{T=e}}});if(T){const e=T;for(const t of C)t.timetoken>S&&e(t.message,t.timetoken)}return C.length=0,K=!0,N}catch(e){throw d.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,s){return function(e,t,s,i){const r=n(t,s),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){S(e,"onEvent",t,i.onError)}switch(t.type){case"progress":if(i.onProgress)try{i.onProgress(t)}catch(e){S(e,"onProgress",t,i.onError)}break;case"artifact":if(i.onArtifact)try{i.onArtifact(t)}catch(e){S(e,"onArtifact",t,i.onError)}break;case"terminal":if(i.onTerminal)try{i.onTerminal(t)}catch(e){S(e,"onTerminal",t,i.onError)}break;case"system":if(i.onSystem)try{i.onSystem(t)}catch(e){S(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,s)}}function S(e,t,n,s){const i=e instanceof Error?e:new Error(String(e));if(s)try{s(i,{entryPoint:"subscribeToTask",callbackType:t,event:n})}catch{}else console.warn(`[subscribeToTask] callback error in ${t}:`,i.message)}
@@ -1 +1 @@
1
- import t from"pubnub";import{invertDirection as e}from"../stream/index.js";import{StreamRef as s}from"./stream-ref.js";import{taskChannel as r}from"./channel-manager.js";import{callRpc as i}from"./rpc-client.js";import{downloadArtifact as a}from"./artifacts.js";export const TERMINAL_STATES=new Set(["completed","failed","canceled"]);export class TaskSession{constructor(t){if(this.progressCallbacks=[],this.artifactCallbacks=[],this.terminalCallbacks=[],this.eventCallbacks=[],this.streamCallbacks=[],this.errorCallbacks=[],this.streams=new Map,this.artifacts=[],this.streamWaiters=[],this.terminalWaiters=[],this.closed=!1,this.listener=null,this.terminalReceived=!1,this.drainTimer=null,this.openStreamClients=new Set,this.seenTimetokens=new Set,this.taskId=t.taskId,this.ownerId=t.ownerId,this.orgId=t.orgId??t.ownerId,this.readToken=t.readToken,this.agentName=t.agentName,this.pubnub=t.pubnub,this.ownsSubscribeClient=t.ownsSubscribeClient??!1,this.sdkOptions=t.sdkOptions,this.rpcConfig=t.rpcConfig??null,this.statusChannel=t.statusChannel??r(t.taskId,this.orgId),this.idempotent=t.idempotent,this.queued=t.queued,this.pushConfigId=t.pushConfigId,this.autoDrain=t.autoDrain??!0,this.drainWindowMs=t.drainWindowMs??3e4,this.state=t.state,this.skipSubscriptionMode=t.skipSubscription??!1,this.subscribeKey=t.sdkOptions.subscribeKey,this.publishKey=t.sdkOptions.publishKey??"",t.preloadedStreams)for(const[e,r]of t.preloadedStreams){const t=new s(r.descriptor,this.sdkOptions,{onOpen:t=>this.trackStreamClient(t),sessionState:()=>this.state});this.streams.set(e,t)}t.preloadedArtifacts&&this.artifacts.push(...t.preloadedArtifacts),t.preClosed?this.closed=!0:t.skipSubscription||(t.externalSubscription?(this.listener=t.externalSubscription.listener,t.externalSubscription.onReady(this.handleEvent.bind(this))):this.setupSubscription())}setupSubscription(){const t=this.pubnub,e=this.statusChannel;this.listener={message:t=>{if(t.channel!==e)return;const s=t.message;if(!s||"object"!=typeof s||!s.type)return;const r=null!=t.timetoken?String(t.timetoken):void 0;this.handleEvent(s,r)}},t.addListener(this.listener),t.subscribe({channels:[e],timetoken:1e3})}routeCallbackError(t,e,s){const r=t instanceof Error?t:new Error(String(t));if(this.errorCallbacks.length>0)for(const t of this.errorCallbacks)try{t(r,{entryPoint:"taskSession",callbackType:e,event:s})}catch{}else console.warn(`[TaskSession] callback error in ${e}:`,r.message)}handleEvent(t,e){if(!this.closed){if(e){if(this.seenTimetokens.has(e))return;if(this.seenTimetokens.add(e),this.seenTimetokens.size>TaskSession.SEEN_TIMETOKENS_MAX){const t=this.seenTimetokens.values().next().value;void 0!==t&&this.seenTimetokens.delete(t)}}for(const e of this.eventCallbacks)try{e(t)}catch(e){this.routeCallbackError(e,"onEvent",t)}switch(t.type){case"progress":for(const e of this.progressCallbacks)try{e(t)}catch(e){this.routeCallbackError(e,"onProgress",t)}"stream_started"===t.streamEvent&&t.streams&&this.handleStreamStarted(t);break;case"artifact":this.accumulateArtifact(t);for(const e of this.artifactCallbacks)try{e(t)}catch(e){this.routeCallbackError(e,"onArtifact",t)}break;case"terminal":this.state=t.state;for(const e of this.terminalCallbacks)try{e(t)}catch(e){this.routeCallbackError(e,"onTerminal",t)}for(const e of this.terminalWaiters)e.resolve(t);this.terminalWaiters=[],this.autoDrain?this.startAutoDrain():this.close()}}}trackStreamClient(t){this.openStreamClients.add(t),t.onInboundDone(()=>{this.openStreamClients.delete(t),t.isActive&&t.end().catch(()=>{}),this.terminalReceived&&0===this.openStreamClients.size&&(this.drainTimer&&(clearTimeout(this.drainTimer),this.drainTimer=null),this.close())})}accumulateArtifact(t){const e=t.artifactRef;e&&"object"==typeof e&&e.kind&&this.artifacts.push(e)}handleStreamStarted(t){const r=t.streams;if(!r||"object"!=typeof r)return;const i=t.declaredStream;for(const[t,a]of Object.entries(r)){if(!a||"object"!=typeof a)continue;if(this.streams.has(t))continue;const r=a.direction,n=e(r),o=a.format;if(!o||"bytes"!==o&&"events"!==o)continue;const l=a.affinity;if("dedicated"!==l&&"shared"!==l){console.warn(`[TaskSession] live stream_started: dropping stream "${t}" for task "${this.taskId}" — invalid or missing affinity (got ${JSON.stringify(a.affinity)})`);continue}const c={taskId:this.taskId,streamId:t,agentName:this.agentName,channel:a.channel,token:a.token,agentDirection:r,localDirection:n,format:o,affinity:l,metadata:a.metadata,declaredStream:i},h=new s(c,this.sdkOptions,{onOpen:t=>this.trackStreamClient(t),sessionState:()=>this.state});this.streams.set(t,h);for(const t of this.streamCallbacks)try{t(h)}catch(t){this.routeCallbackError(t,"onStream",h)}this.resolveWaiters(h)}}startAutoDrain(){this.closed||(this.terminalReceived=!0,0!==this.openStreamClients.size?this.drainTimer=setTimeout(()=>{this.drainTimer=null;for(const t of this.openStreamClients)t.isActive&&t.end().catch(()=>{});this.close()},this.drainWindowMs):this.close())}findStreamByIdOrDeclared(t){const e=this.streams.get(t);if(e)return e;for(const e of this.streams.values())if(e.descriptor.declaredStream===t)return e}resolveWaiters(t){const e=[];for(const s of this.streamWaiters){let r=!1;if(void 0!==s.streamId)r=t.descriptor.declaredStream===s.streamId||t.descriptor.streamId===s.streamId;else if(s.predicate)try{r=s.predicate(t)}catch(e){this.routeCallbackError(e,"streamPredicate",t)}else r=!0;r?s.resolve(t):e.push(s)}this.streamWaiters=e}onProgress(t){return this.progressCallbacks.push(t),()=>{this.progressCallbacks=this.progressCallbacks.filter(e=>e!==t)}}onArtifact(t){return this.artifactCallbacks.push(t),()=>{this.artifactCallbacks=this.artifactCallbacks.filter(e=>e!==t)}}onTerminal(t){return this.terminalCallbacks.push(t),()=>{this.terminalCallbacks=this.terminalCallbacks.filter(e=>e!==t)}}onEvent(t){return this.eventCallbacks.push(t),()=>{this.eventCallbacks=this.eventCallbacks.filter(e=>e!==t)}}onError(t){return this.errorCallbacks.push(t),()=>{this.errorCallbacks=this.errorCallbacks.filter(e=>e!==t)}}listArtifacts(){return[...this.artifacts]}async downloadArtifact(e){if(this.pubnub)return a(e,this.pubnub);const s=`blocks-dl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`,r=new t({subscribeKey:this.subscribeKey,publishKey:this.publishKey||void 0,userId:s});this.readToken&&r.setToken(this.readToken);try{return await a(e,r)}finally{r.destroy()}}onStream(t){this.streamCallbacks.push(t);for(const e of this.streams.values())try{t(e)}catch(t){this.routeCallbackError(t,"onStream",e)}return()=>{this.streamCallbacks=this.streamCallbacks.filter(e=>e!==t)}}listStreams(){return[...this.streams.values()]}openAllStreams(t){const e=[];for(const s of this.streams.values()){const r=s.descriptor.localDirection;if("inbound"===r||"bidirectional"===r)try{e.push(s.open(t))}catch{}}return e}waitForStream(t){if(this.closed)return Promise.reject(new Error("TaskSession is closed"));if(this.skipSubscriptionMode){if(void 0!==t){const e=this.findStreamByIdOrDeclared(t);if(e)return Promise.resolve(e)}else{if(1===this.streams.size)return Promise.resolve(this.streams.values().next().value);if(this.streams.size>1)return Promise.reject(new Error(`Multiple streams exist (${this.streams.size}). Use waitForStream(streamId) or waitForStreamWhere(predicate) to select one.`))}return Promise.reject(new Error("No matching stream found. This is a terminal task session with no live subscription -- no future stream announcements will arrive."))}if(void 0!==t){const e=this.findStreamByIdOrDeclared(t);if(e)return Promise.resolve(e)}else{if(1===this.streams.size)return Promise.resolve(this.streams.values().next().value);if(this.streams.size>1)return Promise.reject(new Error(`Multiple streams exist (${this.streams.size}). Use waitForStream(streamId) or waitForStreamWhere(predicate) to select one.`))}return new Promise((e,s)=>{this.streamWaiters.push({resolve:e,reject:s,streamId:t})})}waitForStreamWhere(t){if(this.closed)return Promise.reject(new Error("TaskSession is closed"));for(const e of this.streams.values())try{if(t(e))return Promise.resolve(e)}catch(t){this.routeCallbackError(t,"streamPredicate",e)}return this.skipSubscriptionMode?Promise.reject(new Error("No matching stream found. This is a terminal task session with no live subscription -- no future stream announcements will arrive.")):new Promise((e,s)=>{this.streamWaiters.push({resolve:e,reject:s,predicate:t})})}async waitForTerminal(t){if(this.state&&TERMINAL_STATES.has(this.state))return{type:"terminal",taskId:this.taskId,state:this.state};if(this.closed)throw new Error("TaskSession closed");return new Promise((e,s)=>{const r={resolve:e,reject:s};if(this.terminalWaiters.push(r),void 0!==t){const e=setTimeout(()=>{const e=this.terminalWaiters.indexOf(r);-1!==e&&this.terminalWaiters.splice(e,1),s(new Error(`waitForTerminal timed out after ${t}ms`))},t),i=r.resolve,a=r.reject;r.resolve=t=>{clearTimeout(e),i(t)},r.reject=t=>{clearTimeout(e),a(t)}}})}async saveArtifacts(t){const{mkdirSync:e,writeFileSync:s}=await import("node:fs"),{join:r,resolve:i,basename:a}=await import("node:path"),n=i(t);e(n,{recursive:!0});const o=[],l=this.listArtifacts();for(let t=0;t<l.length;t++){const e=await this.downloadArtifact(l[t]),c=e.fileName??`artifact-${t}`,h=i(r(n,a(c)||`artifact-${t}`));if(!h.startsWith(n+"/")&&h!==n)throw new Error(`Artifact filename "${c}" resolves outside target directory`);s(h,e.data),o.push(h)}return o}async cancel(){if(!this.rpcConfig)throw new Error("TaskSession was not created with RPC config; use TaskClient.cancelTask() directly");await i(this.rpcConfig,"CancelTask",{taskId:this.taskId})}async terminate(){if(!this.rpcConfig)throw new Error("TaskSession was not created with RPC config; use TaskClient.terminateTask() directly");await i(this.rpcConfig,"TerminateTask",{taskId:this.taskId})}close(){if(this.closed)return;this.closed=!0,this.drainTimer&&(clearTimeout(this.drainTimer),this.drainTimer=null);for(const t of this.openStreamClients)t.isActive&&t.end().catch(()=>{});this.openStreamClients.clear();const t=new Error("TaskSession closed");for(const e of this.streamWaiters)e.reject(t);this.streamWaiters=[];for(const e of this.terminalWaiters)e.reject(t);this.terminalWaiters=[],this.pubnub&&(this.listener&&(this.pubnub.removeListener(this.listener),this.listener=null),this.pubnub.unsubscribe({channels:[this.statusChannel]}),this.ownsSubscribeClient&&this.pubnub.destroy()),this.progressCallbacks=[],this.artifactCallbacks=[],this.terminalCallbacks=[],this.eventCallbacks=[],this.streamCallbacks=[],this.errorCallbacks=[]}async asyncClose(){if(this.closed)return;const t=[...this.openStreamClients].filter(t=>t.isActive).map(t=>t.end().catch(()=>{}));await Promise.all(t),this.openStreamClients.clear(),this.close()}async[Symbol.asyncDispose](){await this.asyncClose()}get isClosed(){return this.closed}}TaskSession.SEEN_TIMETOKENS_MAX=200;
1
+ import t from"pubnub";import{invertDirection as e}from"../stream/index.js";import{StreamRef as s}from"./stream-ref.js";import{taskChannel as i}from"./channel-manager.js";import{callRpc as r}from"./rpc-client.js";import{downloadArtifact as a}from"./artifacts.js";export const TERMINAL_STATES=new Set(["completed","failed","canceled"]);export class TaskSession{constructor(t){if(this.progressCallbacks=[],this.artifactCallbacks=[],this.terminalCallbacks=[],this.eventCallbacks=[],this.streamCallbacks=[],this.errorCallbacks=[],this.streams=new Map,this.artifacts=[],this.streamWaiters=[],this.terminalWaiters=[],this.closed=!1,this.listener=null,this.terminalReceived=!1,this.drainTimer=null,this.openStreamClients=new Set,this.seenTimetokens=new Set,this.taskId=t.taskId,this.ownerId=t.ownerId,this.orgId=t.orgId??t.ownerId,this.readToken=t.readToken,this.agentName=t.agentName,this.pubnub=t.pubnub,this.ownsSubscribeClient=t.ownsSubscribeClient??!1,this.sdkOptions=t.sdkOptions,this.rpcConfig=t.rpcConfig??null,this.statusChannel=t.statusChannel??i(t.taskId,this.orgId),this.idempotent=t.idempotent,this.queued=t.queued,this.pushConfigId=t.pushConfigId,this.autoDrain=t.autoDrain??!0,this.drainWindowMs=t.drainWindowMs??3e4,this.state=t.state,this.skipSubscriptionMode=t.skipSubscription??!1,this.subscribeKey=t.sdkOptions.subscribeKey,this.publishKey=t.sdkOptions.publishKey??"",t.preloadedStreams)for(const[e,i]of t.preloadedStreams){const t=new s(i.descriptor,this.sdkOptions,{onOpen:t=>this.trackStreamClient(t),sessionState:()=>this.state});this.streams.set(e,t)}t.preloadedArtifacts&&this.artifacts.push(...t.preloadedArtifacts),t.preClosed?this.closed=!0:t.skipSubscription||(t.externalSubscription?(this.listener=t.externalSubscription.listener,t.externalSubscription.onReady(this.handleEvent.bind(this))):this.setupSubscription())}setupSubscription(){const t=this.pubnub,e=this.statusChannel;this.listener={message:t=>{if(t.channel!==e)return;const s=t.message;if(!s||"object"!=typeof s||!s.type)return;const i=null!=t.timetoken?String(t.timetoken):void 0;this.handleEvent(s,i)}},t.addListener(this.listener),t.subscribe({channels:[e],timetoken:1e3})}routeCallbackError(t,e,s){const i=t instanceof Error?t:new Error(String(t));if(this.errorCallbacks.length>0)for(const t of this.errorCallbacks)try{t(i,{entryPoint:"taskSession",callbackType:e,event:s})}catch{}else console.warn(`[TaskSession] callback error in ${e}:`,i.message)}handleEvent(t,e){if(!this.closed){if(e){if(this.seenTimetokens.has(e))return;if(this.seenTimetokens.add(e),this.seenTimetokens.size>TaskSession.SEEN_TIMETOKENS_MAX){const t=this.seenTimetokens.values().next().value;void 0!==t&&this.seenTimetokens.delete(t)}}for(const e of this.eventCallbacks)try{e(t)}catch(e){this.routeCallbackError(e,"onEvent",t)}switch(t.type){case"progress":for(const e of this.progressCallbacks)try{e(t)}catch(e){this.routeCallbackError(e,"onProgress",t)}"stream_started"===t.streamEvent&&t.streams&&this.handleStreamStarted(t);break;case"artifact":this.accumulateArtifact(t);for(const e of this.artifactCallbacks)try{e(t)}catch(e){this.routeCallbackError(e,"onArtifact",t)}break;case"terminal":this.state=t.state;for(const e of this.terminalCallbacks)try{e(t)}catch(e){this.routeCallbackError(e,"onTerminal",t)}for(const e of this.terminalWaiters)e.resolve(t);this.terminalWaiters=[],this.autoDrain?this.startAutoDrain():this.close()}}}trackStreamClient(t){this.openStreamClients.add(t),t.onInboundDone(()=>{this.openStreamClients.delete(t),t.isActive&&t.end().catch(()=>{}),this.terminalReceived&&0===this.openStreamClients.size&&(this.drainTimer&&(clearTimeout(this.drainTimer),this.drainTimer=null),this.close())})}accumulateArtifact(t){const e=t.artifactRef;e&&"object"==typeof e&&e.kind&&this.artifacts.push(e)}handleStreamStarted(t){const i=t.streams;if(!i||"object"!=typeof i)return;const r=t.declaredStream;for(const[t,a]of Object.entries(i)){if(!a||"object"!=typeof a)continue;if(this.streams.has(t))continue;const i=a.direction,n=e(i),o=a.format;if(!o||"bytes"!==o&&"events"!==o)continue;const l=a.affinity;if("dedicated"!==l&&"shared"!==l){console.warn(`[TaskSession] live stream_started: dropping stream "${t}" for task "${this.taskId}" — invalid or missing affinity (got ${JSON.stringify(a.affinity)})`);continue}const c={taskId:this.taskId,streamId:t,agentName:this.agentName,channel:a.channel,token:a.token,agentDirection:i,localDirection:n,format:o,affinity:l,metadata:a.metadata,declaredStream:r},h=new s(c,this.sdkOptions,{onOpen:t=>this.trackStreamClient(t),sessionState:()=>this.state});this.streams.set(t,h);for(const t of this.streamCallbacks)try{t(h)}catch(t){this.routeCallbackError(t,"onStream",h)}this.resolveWaiters(h)}}startAutoDrain(){this.closed||(this.terminalReceived=!0,0!==this.openStreamClients.size?this.drainTimer=setTimeout(()=>{this.drainTimer=null;for(const t of this.openStreamClients)t.isActive&&t.end().catch(()=>{});this.close()},this.drainWindowMs):this.close())}findStreamByIdOrDeclared(t){const e=this.streams.get(t);if(e)return e;for(const e of this.streams.values())if(e.descriptor.declaredStream===t)return e}resolveWaiters(t){const e=[];for(const s of this.streamWaiters){let i=!1;if(void 0!==s.streamId)i=t.descriptor.declaredStream===s.streamId||t.descriptor.streamId===s.streamId;else if(s.predicate)try{i=s.predicate(t)}catch(e){this.routeCallbackError(e,"streamPredicate",t)}else i=!0;i?s.resolve(t):e.push(s)}this.streamWaiters=e}onProgress(t){return this.progressCallbacks.push(t),()=>{this.progressCallbacks=this.progressCallbacks.filter(e=>e!==t)}}onArtifact(t){return this.artifactCallbacks.push(t),()=>{this.artifactCallbacks=this.artifactCallbacks.filter(e=>e!==t)}}onTerminal(t){if(this.terminalCallbacks.push(t),this.state&&TERMINAL_STATES.has(this.state)){const e={type:"terminal",taskId:this.taskId,state:this.state};try{t(e)}catch(t){this.routeCallbackError(t,"onTerminal",e)}}return()=>{this.terminalCallbacks=this.terminalCallbacks.filter(e=>e!==t)}}onEvent(t){return this.eventCallbacks.push(t),()=>{this.eventCallbacks=this.eventCallbacks.filter(e=>e!==t)}}onError(t){return this.errorCallbacks.push(t),()=>{this.errorCallbacks=this.errorCallbacks.filter(e=>e!==t)}}listArtifacts(){return[...this.artifacts]}async downloadArtifact(e){if(this.pubnub)return a(e,this.pubnub);const s=`blocks-dl-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`,i=new t({subscribeKey:this.subscribeKey,publishKey:this.publishKey||void 0,userId:s});this.readToken&&i.setToken(this.readToken);try{return await a(e,i)}finally{i.destroy()}}onStream(t){this.streamCallbacks.push(t);for(const e of this.streams.values())try{t(e)}catch(t){this.routeCallbackError(t,"onStream",e)}return()=>{this.streamCallbacks=this.streamCallbacks.filter(e=>e!==t)}}listStreams(){return[...this.streams.values()]}openAllStreams(t){const e=[];for(const s of this.streams.values()){const i=s.descriptor.localDirection;if("inbound"===i||"bidirectional"===i)try{e.push(s.open(t))}catch{}}return e}waitForStream(t){if(this.closed)return Promise.reject(new Error("TaskSession is closed"));if(this.skipSubscriptionMode){if(void 0!==t){const e=this.findStreamByIdOrDeclared(t);if(e)return Promise.resolve(e)}else{if(1===this.streams.size)return Promise.resolve(this.streams.values().next().value);if(this.streams.size>1)return Promise.reject(new Error(`Multiple streams exist (${this.streams.size}). Use waitForStream(streamId) or waitForStreamWhere(predicate) to select one.`))}return Promise.reject(new Error("No matching stream found. This is a terminal task session with no live subscription -- no future stream announcements will arrive."))}if(void 0!==t){const e=this.findStreamByIdOrDeclared(t);if(e)return Promise.resolve(e)}else{if(1===this.streams.size)return Promise.resolve(this.streams.values().next().value);if(this.streams.size>1)return Promise.reject(new Error(`Multiple streams exist (${this.streams.size}). Use waitForStream(streamId) or waitForStreamWhere(predicate) to select one.`))}return new Promise((e,s)=>{this.streamWaiters.push({resolve:e,reject:s,streamId:t})})}waitForStreamWhere(t){if(this.closed)return Promise.reject(new Error("TaskSession is closed"));for(const e of this.streams.values())try{if(t(e))return Promise.resolve(e)}catch(t){this.routeCallbackError(t,"streamPredicate",e)}return this.skipSubscriptionMode?Promise.reject(new Error("No matching stream found. This is a terminal task session with no live subscription -- no future stream announcements will arrive.")):new Promise((e,s)=>{this.streamWaiters.push({resolve:e,reject:s,predicate:t})})}async waitForTerminal(t){if(this.state&&TERMINAL_STATES.has(this.state))return{type:"terminal",taskId:this.taskId,state:this.state};if(this.closed)throw new Error("TaskSession closed");return new Promise((e,s)=>{const i={resolve:e,reject:s};if(this.terminalWaiters.push(i),void 0!==t){const e=setTimeout(()=>{const e=this.terminalWaiters.indexOf(i);-1!==e&&this.terminalWaiters.splice(e,1),s(new Error(`waitForTerminal timed out after ${t}ms`))},t),r=i.resolve,a=i.reject;i.resolve=t=>{clearTimeout(e),r(t)},i.reject=t=>{clearTimeout(e),a(t)}}})}async saveArtifacts(t){const{mkdirSync:e,writeFileSync:s}=await import("node:fs"),{join:i,resolve:r,basename:a}=await import("node:path"),n=r(t);e(n,{recursive:!0});const o=[],l=this.listArtifacts();for(let t=0;t<l.length;t++){const e=await this.downloadArtifact(l[t]),c=e.fileName??`artifact-${t}`,h=r(i(n,a(c)||`artifact-${t}`));if(!h.startsWith(n+"/")&&h!==n)throw new Error(`Artifact filename "${c}" resolves outside target directory`);s(h,e.data),o.push(h)}return o}async cancel(){if(!this.rpcConfig)throw new Error("TaskSession was not created with RPC config; use TaskClient.cancelTask() directly");await r(this.rpcConfig,"CancelTask",{taskId:this.taskId})}async terminate(){if(!this.rpcConfig)throw new Error("TaskSession was not created with RPC config; use TaskClient.terminateTask() directly");await r(this.rpcConfig,"TerminateTask",{taskId:this.taskId})}close(){if(this.closed)return;this.closed=!0,this.drainTimer&&(clearTimeout(this.drainTimer),this.drainTimer=null);for(const t of this.openStreamClients)t.isActive&&t.end().catch(()=>{});this.openStreamClients.clear();const t=new Error("TaskSession closed");for(const e of this.streamWaiters)e.reject(t);this.streamWaiters=[];for(const e of this.terminalWaiters)e.reject(t);this.terminalWaiters=[],this.pubnub&&(this.listener&&(this.pubnub.removeListener(this.listener),this.listener=null),this.pubnub.unsubscribe({channels:[this.statusChannel]}),this.ownsSubscribeClient&&this.pubnub.destroy()),this.progressCallbacks=[],this.artifactCallbacks=[],this.terminalCallbacks=[],this.eventCallbacks=[],this.streamCallbacks=[],this.errorCallbacks=[]}async asyncClose(){if(this.closed)return;const t=[...this.openStreamClients].filter(t=>t.isActive).map(t=>t.end().catch(()=>{}));await Promise.all(t),this.openStreamClients.clear(),this.close()}async[Symbol.asyncDispose](){await this.asyncClose()}get isClosed(){return this.closed}}TaskSession.SEEN_TIMETOKENS_MAX=200;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocks-network/sdk",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "type": "module",
5
5
  "description": "Blocks Network SDK — build and run A2A agents on PubNub",
6
6
  "exports": {