@blocks-network/sdk 0.1.52 → 0.1.53

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.
@@ -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 g,createExternalStreamObject as h,runOnActivate as y}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 E,isProtocolVersionSupported as $}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 A=(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 x=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,R="playground",M=R;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 N=m("BLOCKS_API_KEY");if(!N)throw new Error("BLOCKS_API_KEY is required. Run 'blocks publish' to set up credentials.");let P;const L=t.baseUrl??_?.api.baseUrl;let j,U;if(N&&L&&(P=new v(N,L),console.log("[AgentInstance] AgentAuth created — will authenticate via connect")),_){const e=t.baseUrl??_.api.baseUrl,r=await d(a,{baseUrl:e,apiKey:N});if(!r)throw new Error(`[AgentInstance] Agent "${a}" not found in registry. Register the agent (e.g. via \`blocks publish\`) before starting an instance.`);if(!r.billingMode)throw new Error(`[AgentInstance] Agent "${a}" registry entry is missing billingMode. Re-register the agent so the registry persists an explicit billingMode.`);j=r.billingMode,U=r.listing,R="paid"===j?"network":"playground",console.log(`[AgentInstance] Registry billingMode: ${j} — using ${R} environment`),M=R}const q=j??"free";let D,O=!1;if(t.pubnub)D=t.pubnub,O=!1,l&&D.setToken(l);else{const e=f[M];D=s({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20}),O=!0,l&&D.setToken(l)}let V=f[M].subscribeKey,B=f[M].publishKey;const F=new w({billingMode:q,subscribeKey:V,authProvider:l?new p(l):void 0,baseUrl:t.baseUrl??_?.api.baseUrl,agentAuth:P,createPubNub:()=>{const e=s({publishKey:B,subscribeKey:V,userId:`${c}-taskclient`});return l&&e.setToken(l),e}}),G=t.concurrency??u.concurrency,z=t.expectedInstances??u.expectedInstances,H=t.maxPendingBacklog,Y=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),W=Date.now();let Z=0;const J=new k,Q=new I,X=new Map,ee=new Map,te=new Map,ae=new Map,re=new Map,se=new Set,ne=new Set,oe=new Map,ie=new Set,de=new Map;let ce,le;t.logChannel;const ue=async()=>{if("function"==typeof D.setState&&le)try{await D.setState({channels:[le],state:{instanceId:c,activeTasks:Z,activeStreams:J.activeStreamCount,concurrency:G,startedAt:W,preferredProtocolVersion:S,protocolVersions:[...E]}})}catch{}},me=(e,t)=>{const a=X.get(e);a&&(a.delete(t),0===a.size&&X.delete(e))},fe=async e=>{(e=>{for(const[t,a]of X)a.has(e)&&(a.delete(e),0===a.size&&X.delete(t))})(e);const t=J.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},pe=async(e,t)=>{me(e,t);const a=J.get(e);if(0===J.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{}}ue().catch(()=>{})},ke=async(e,t)=>{const a=J.forceRemove(e);if((e=>{X.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=Q.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{}Q.remove(e)}}await ue()}},Ie=async(e,t)=>{const a=Q.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 fe(e),Q.remove(e),await ue()},ge=async(s,n,i,d,c)=>{const u=a,m="pipe"===s.taskKind,f=m?"pipe":"request",k=computeStreamDurationMinutes(s.duration,m,Y);if(m&&"number"==typeof s.durationExpiresAtMs){const e=Math.max(0,s.durationExpiresAtMs-Date.now());if(e>0){const t=setTimeout(()=>{se.add(s.taskId),c.abort()},e);oe.set(s.taskId,t)}}await K(n,s.taskId,d,u,{type:"progress",taskId:s.taskId,progress:0,state:"running"}),de.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=>{ae.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 se.has(s.taskId)},hasStream:!!s.hasStream,cancelSignal:c.signal,taskClient:F,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:P}:void 0;await x(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=(de.get(s.taskId)??0)+1;de.set(s.taskId,e),S=`${s.taskId}-${e}`}const E=o.streamChannel(S);if("shared"===T){const e=X.get(S)?.get(s.taskId);if(e)return e}const{entry:$,isNew:K,isNewForTask:A}=J.acquire(S,s.taskId,{direction:p,format:I,external:w,affinity:T});Q.addStream(s.taskId,S);const x=e=>{if("shared"!==T)return;let t=X.get(S);t||(t=new Map,X.set(S,t)),t.set(s.taskId,e)};if(!A){$.setupPromise&&await $.setupPromise;const e=X.get(S)?.get(s.taskId);if(e)return e;if($.streamClient){const e=g(S,$.streamClient,s.taskId,{releaseStream:pe});return x(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($.setupPromise&&await $.setupPromise,"shared"===T&&await(async()=>{await C(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:E,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:"activate",metadata:b,declaredStream:c})})(),$.streamClient){const e=g(S,$.streamClient,s.taskId,{releaseStream:pe});return x(e),e}if(w)return h(S,E,"",async()=>{throw new Error("External stream already activated")});throw new Error(`Stream "${S}" exists but has no client`)}catch(e){me(S,s.taskId);try{await J.release(S,s.taskId)}catch{}throw e}let _,R;$.setupPromise=new Promise((e,t)=>{_=e,R=t}),$.setupPromise.catch(()=>{});const M=w?"token_request":"embedded";let N;try{N=await C(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:E,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:M,metadata:b,declaredStream:c})}catch(e){R(e),me(S,s.taskId);try{await J.release(S,s.taskId)}catch{}throw e}if(w){_(),$.setupPromise=null;const e=N.token??"";return h(S,E,e,async e=>{await C(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:E,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:"activate",metadata:e?.metadata??b,declaredStream:c})})}const P=N.token;if(!P){const e=new Error("No T7a token received from stream setup handshake");R(e),me(S,s.taskId);try{await J.release(S,s.taskId)}catch{}throw e}const L=new e({subscribeKey:V,publishKey:B,token:P,agentName:u,streamId:S,channel:E,format:I,direction:p,affinity:T,gating:m,bundleSizeBytes:a?.bundleSizeBytes,maxLatencyMs:a?.maxLatencyMs});$.streamClient=L;const j=g(S,L,s.taskId,{releaseStream:pe});if(x(j),_(),$.setupPromise=null,"outbound"===p||"bidirectional"===p){const e=a?.subscribeGraceMs??1e3;e>0&&await new Promise(t=>setTimeout(t,e))}return v&&($.activated=!0,$.activatePromise=y(S,j,v,ke)),j}};let S;try{S=t.handler?await t.handler(s,T):void 0}finally{de.delete(s.taskId),b&&(clearTimeout(b),b=null),v()}const E=L?{baseUrl:L,authProvider:l?new p(l):void 0,agentAuth:P}: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 x(n,s.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,E)}const $=c.signal.aborted;if(m){const e=se.has(s.taskId),t=ne.has(s.taskId);if($||e||t){await fe(s.taskId);const a=$&&!e?"canceled":"completed";await K(n,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),Q.remove(s.taskId),A("info",`Task ${s.taskId} ${a}`,{taskId:s.taskId,agentName:u,owner:i})}else A("info",`Task ${s.taskId} handler returned (pipe, no auto-terminal)`,{taskId:s.taskId,agentName:u,owner:i})}else{const e=ne.has(s.taskId);await fe(s.taskId);const t=$?"canceled":"completed";await K(n,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:t,...e?{reason:"terminated"}:{}}),Q.remove(s.taskId),A("info",`Task ${s.taskId} ${t}`,{taskId:s.taskId,agentName:u,owner:i})}},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{D.destroy()}catch{}}}})(),ye={status:e=>{const t=e,a=String(t.category??""),r=String(t.operation??"");"PNConnectedCategory"===a?console.log(`[AgentInstance] PubNub connected to ${le}`):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!==M&&t in f&&((e,t)=>{if(A("info",`SwitchEnvironment requested: ${M} -> ${e}`,{pamToken:t?"present":"absent"}),!t)return void A("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=D,r=O;try{a.removeListener(ye),le&&a.unsubscribe({channels:[le]})}catch{}const n=f[e];if(D=s({publishKey:n.publishKey,subscribeKey:n.subscribeKey,userId:c,presenceTimeout:20}),O=!0,ce=void 0,D.setToken(t),0!==z&&"function"==typeof D.setFilterExpression&&D.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),D.addListener(ye),le&&D.subscribe({channels:[le]}),r)try{a.destroy()}catch{}M=e,V=n.subscribeKey,B=n.publishKey,F.updateKeys(n.subscribeKey,n.publishKey),A("info",`Switched to ${e} environment`),ue().catch(()=>{})})(t,e.pamToken))}(async(e,r)=>{if("StartTask"===e.type){const n=M,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(ie.has(e.taskId))return void A("warn",`Ignoring duplicate StartTask for in-flight task ${e.taskId}`,{taskId:e.taskId});const d=Boolean(r?.broadcast);if(e.protocolVersion&&!$(e.protocolVersion))return d?void A("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await K(D,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void A("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 A("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${r})`,{taskId:e.taskId,instanceId:c}),void await K(D,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=Z+J.activeStreamCount;if(G>0&&l>=G)return d?void A("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${G})`,{taskId:e.taskId,instanceId:c}):(await K(D,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void A("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:Z}));ee.set(e.taskId,o),te.set(e.taskId,i),ie.add(e.taskId),Z++,e.controlToken&&(ce=e.controlToken);const u=new AbortController;re.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),Q.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:n}),await ue();const{writeToken:d,controlToken:l,...p}=e;t.onStartTask?await t.onStartTask(e,m):await ge(p,m,o,i,u)}catch(r){const s=r?.message??"Agent instance error",n=m??D;try{await fe(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),A("error",s,{taskId:e.taskId}),Q.remove(e.taskId)}finally{const t=oe.get(e.taskId);if(t&&(clearTimeout(t),oe.delete(e.taskId)),ie.delete(e.taskId),ee.delete(e.taskId),te.delete(e.taskId),ae.delete(e.taskId),re.delete(e.taskId),se.delete(e.taskId),ne.delete(e.taskId),m)try{m.destroy()}catch{}Z--,0===Z&&ce&&D.setToken(ce),await ue()}}else if("CancelTask"===e.type){const t=re.get(e.taskId);t?(t.abort(),A("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):Q.get(e.taskId)&&await Ie(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)re.has(e.taskId)?(se.add(e.taskId),re.get(e.taskId)?.abort(),A("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):Q.get(e.taskId)&&await Ie(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)re.has(e.taskId)?(ne.add(e.taskId),re.get(e.taskId)?.abort()):Q.get(e.taskId)&&await Ie(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if(ie.has(e.taskId)){const t=te.get(e.taskId)??"anonymous";await K(D,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if(ie.has(e.taskId)){const t=te.get(e.taskId)??"anonymous";await K(D,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&A("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)})}};D.addListener(ye),i(a,{instanceId:c,billingMode:q,description:t.description,scaling:{expectedInstances:z,concurrency:G,maxPendingBacklog:H,maxRunningTimeSec:Y},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:U??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??_?.api.baseUrl,agentAuth:P}).then(e=>{if(console.log(`[AgentInstance] Registered agent: ${a} (instance: ${c})`),!e.controlChannel)throw new Error("Connect response missing controlChannel — server may be outdated");le=e.controlChannel,e.pamToken&&D.setToken(e.pamToken),0!==z&&"function"==typeof D.setFilterExpression&&D.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),D.subscribe({channels:[le]}),ue().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)}),A("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{F.destroy(),D.removeListener(ye),le&&D.unsubscribe({channels:[le]}),O&&D.destroy();for(const e of J.streamIds()){const t=J.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}J.clear(),X.clear(),Q.clear()}catch{}},agentName:a,instanceId:c,get controlChannel(){return le},get pubnub(){return D},get subscribeKey(){return f[M].subscribeKey},taskClient:F,publishTerminal:Ie,failStream:ke,cdmConfig:_}};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 g,runOnActivate as y}from"./stream-context.js";import{TaskClient as w}from"./task-client.js";import{ConsumerAuth as b}from"./consumer-auth.js";import{fetchCdmConfig as v}from"./cdm-config.js";import{AgentAuth as T,AgentAuthFatalError as S}from"./agent-auth.js";import{CURRENT_PROTOCOL_VERSION as E,SUPPORTED_PROTOCOL_VERSIONS as $,isProtocolVersionSupported as K}from"./protocol-version.js";const x=async(e,t,a,r,s,i=E)=>{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 A=(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 C=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 x(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:E};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,R=null,M="playground",N=M;if(t.pubnub){const e=t.pubnub._configuration?.keySet,a={publishKey:e?.publishKey??"",subscribeKey:e?.subscribeKey??""};f={playground:a,network:a}}else R=await v(t.cdmUrl),f={playground:R.playground,network:R.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 L;const j=t.baseUrl??R?.api.baseUrl;let U,q;if(P&&j&&(L=new T(P,j),console.log("[AgentInstance] AgentAuth created — will authenticate via connect")),R){const e=t.baseUrl??R.api.baseUrl,r=await d(a,{baseUrl:e,apiKey:P});if(!r)throw new Error(`[AgentInstance] Agent "${a}" not found in registry. Register the agent (e.g. via \`blocks publish\`) before starting an instance.`);if(!r.billingMode)throw new Error(`[AgentInstance] Agent "${a}" registry entry is missing billingMode. Re-register the agent so the registry persists an explicit billingMode.`);U=r.billingMode,q=r.listing,M="paid"===U?"network":"playground",console.log(`[AgentInstance] Registry billingMode: ${U} — using ${M} environment`),N=M}const D=U??"free";let O,V=!1;if(t.pubnub)O=t.pubnub,V=!1,l&&O.setToken(l);else{const e=f[N];O=s({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20}),V=!0,l&&O.setToken(l)}let B=f[N].subscribeKey,F=f[N].publishKey;const G=j?new b({apiKey:P,baseUrl:j}):void 0,z=new w({billingMode:D,subscribeKey:B,publishKey:F,authProvider:G,baseUrl:j,createPubNub:()=>s({publishKey:F,subscribeKey:B,userId:`${c}-taskclient`})}),H=t.concurrency??u.concurrency,Y=t.expectedInstances??u.expectedInstances,W=t.maxPendingBacklog,Z=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),J=Date.now();let Q=0;const X=new k,ee=new I,te=new Map,ae=new Map,re=new Map,se=new Map,ne=new Map,oe=new Set,ie=new Set,de=new Map,ce=new Set,le=new Map;let ue,me;t.logChannel;const fe=async()=>{if("function"==typeof O.setState&&me)try{await O.setState({channels:[me],state:{instanceId:c,activeTasks:Q,activeStreams:X.activeStreamCount,concurrency:H,startedAt:J,preferredProtocolVersion:E,protocolVersions:[...$]}})}catch{}},pe=(e,t)=>{const a=te.get(e);a&&(a.delete(t),0===a.size&&te.delete(e))},ke=async e=>{(e=>{for(const[t,a]of te)a.has(e)&&(a.delete(e),0===a.size&&te.delete(t))})(e);const t=X.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},Ie=async(e,t)=>{pe(e,t);const a=X.get(e);if(0===X.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{}}fe().catch(()=>{})},he=async(e,t)=>{const a=X.forceRemove(e);if((e=>{te.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=ee.get(e);if(a){try{const r=f[a.environment??M]??f[M],n=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c});n.setToken(a.writeToken),await x(n,e,a.orgId,a.agentName,{type:"terminal",taskId:e,state:"failed",error:t}),n.destroy()}catch{}ee.remove(e)}}await fe()}},ge=async(e,t)=>{const a=ee.get(e);if(!a)throw new Error(`No cached credentials for task ${e}`);const r=f[a.environment??M]??f[M],n=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c});n.setToken(a.writeToken);try{await x(n,e,a.orgId,a.agentName,{...t,type:"terminal",taskId:e})}finally{n.destroy()}await ke(e),ee.remove(e),await fe()},ye=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(()=>{oe.add(s.taskId),c.abort()},e);de.set(s.taskId,t)}}await x(n,s.taskId,d,u,{type:"progress",taskId:s.taskId,progress:0,state:"running"}),le.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(),x(n,s.taskId,d,u,{type:"progress",taskId:s.taskId,message:e}).catch(()=>{})},T={taskId:s.taskId,requestParts:s.requestParts??[],reportStatus:e=>{se.set(s.taskId,e);const t=Date.now();if(t-I>=1e3)w=null,I=t,x(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 oe.has(s.taskId)},hasStream:!!s.hasStream,cancelSignal:c.signal,taskClient:z,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=j?{baseUrl:j,authProvider:l?new p(l):void 0,agentAuth:L}:void 0;await C(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=(le.get(s.taskId)??0)+1;le.set(s.taskId,e),S=`${s.taskId}-${e}`}const E=o.streamChannel(S);if("shared"===T){const e=te.get(S)?.get(s.taskId);if(e)return e}const{entry:$,isNew:K,isNewForTask:x}=X.acquire(S,s.taskId,{direction:p,format:I,external:w,affinity:T});ee.addStream(s.taskId,S);const A=e=>{if("shared"!==T)return;let t=te.get(S);t||(t=new Map,te.set(S,t)),t.set(s.taskId,e)};if(!x){$.setupPromise&&await $.setupPromise;const e=te.get(S)?.get(s.taskId);if(e)return e;if($.streamClient){const e=h(S,$.streamClient,s.taskId,{releaseStream:Ie});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($.setupPromise&&await $.setupPromise,"shared"===T&&await(async()=>{await _(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:E,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:"activate",metadata:b,declaredStream:c})})(),$.streamClient){const e=h(S,$.streamClient,s.taskId,{releaseStream:Ie});return A(e),e}if(w)return g(S,E,"",async()=>{throw new Error("External stream already activated")});throw new Error(`Stream "${S}" exists but has no client`)}catch(e){pe(S,s.taskId);try{await X.release(S,s.taskId)}catch{}throw e}let C,R;$.setupPromise=new Promise((e,t)=>{C=e,R=t}),$.setupPromise.catch(()=>{});const M=w?"token_request":"embedded";let N;try{N=await _(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:E,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:M,metadata:b,declaredStream:c})}catch(e){R(e),pe(S,s.taskId);try{await X.release(S,s.taskId)}catch{}throw e}if(w){C(),$.setupPromise=null;const e=N.token??"";return g(S,E,e,async e=>{await _(n,{taskId:s.taskId,orgId:d,agentName:u,streamId:S,channel:E,direction:p,format:I,taskKind:f,durationMinutes:k,affinity:T,phase:"activate",metadata:e?.metadata??b,declaredStream:c})})}const P=N.token;if(!P){const e=new Error("No T7a token received from stream setup handshake");R(e),pe(S,s.taskId);try{await X.release(S,s.taskId)}catch{}throw e}const L=new e({subscribeKey:B,publishKey:F,token:P,agentName:u,streamId:S,channel:E,format:I,direction:p,affinity:T,gating:m,bundleSizeBytes:a?.bundleSizeBytes,maxLatencyMs:a?.maxLatencyMs});$.streamClient=L;const j=h(S,L,s.taskId,{releaseStream:Ie});if(A(j),C(),$.setupPromise=null,"outbound"===p||"bidirectional"===p){const e=a?.subscribeGraceMs??1e3;e>0&&await new Promise(t=>setTimeout(t,e))}return v&&($.activated=!0,$.activatePromise=y(S,j,v,he)),j}};let S;try{S=t.handler?await t.handler(s,T):void 0}finally{le.delete(s.taskId),b&&(clearTimeout(b),b=null),v()}const E=j?{baseUrl:j,authProvider:l?new p(l):void 0,agentAuth:L}: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 C(n,s.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,E)}const $=c.signal.aborted;if(m){const e=oe.has(s.taskId),t=ie.has(s.taskId);if($||e||t){await ke(s.taskId);const a=$&&!e?"canceled":"completed";await x(n,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),ee.remove(s.taskId),A("info",`Task ${s.taskId} ${a}`,{taskId:s.taskId,agentName:u,owner:i})}else A("info",`Task ${s.taskId} handler returned (pipe, no auto-terminal)`,{taskId:s.taskId,agentName:u,owner:i})}else{const e=ie.has(s.taskId);await ke(s.taskId);const t=$?"canceled":"completed";await x(n,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:t,...e?{reason:"terminated"}:{}}),ee.remove(s.taskId),A("info",`Task ${s.taskId} ${t}`,{taskId:s.taskId,agentName:u,owner:i})}},we=(()=>{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{O.destroy()}catch{}}}})(),be={status:e=>{const t=e,a=String(t.category??""),r=String(t.operation??"");"PNConnectedCategory"===a?console.log(`[AgentInstance] PubNub connected to ${me}`):t.error&&console.error(`[AgentInstance] PubNub status: ${a} (op=${r}, code=${t.statusCode})`),we(a)},message:e=>{const r=e.message;if(!r)return;if("SwitchEnvironment"===r.type){const e=r,t=e.environment;return void(t&&t!==N&&t in f&&((e,t)=>{if(A("info",`SwitchEnvironment requested: ${N} -> ${e}`,{pamToken:t?"present":"absent"}),!t)return void A("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=O,r=V;try{a.removeListener(be),me&&a.unsubscribe({channels:[me]})}catch{}const n=f[e];if(O=s({publishKey:n.publishKey,subscribeKey:n.subscribeKey,userId:c,presenceTimeout:20}),V=!0,ue=void 0,O.setToken(t),0!==Y&&"function"==typeof O.setFilterExpression&&O.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),O.addListener(be),me&&O.subscribe({channels:[me]}),r)try{a.destroy()}catch{}N=e,B=n.subscribeKey,F=n.publishKey,z.updateKeys(n.subscribeKey,n.publishKey),A("info",`Switched to ${e} environment`),fe().catch(()=>{})})(t,e.pamToken))}(async(e,r)=>{if("StartTask"===e.type){const n=N,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(ce.has(e.taskId))return void A("warn",`Ignoring duplicate StartTask for in-flight task ${e.taskId}`,{taskId:e.taskId});const d=Boolean(r?.broadcast);if(e.protocolVersion&&!K(e.protocolVersion))return d?void A("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await x(O,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void A("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 A("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${r})`,{taskId:e.taskId,instanceId:c}),void await x(O,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=Q+X.activeStreamCount;if(H>0&&l>=H)return d?void A("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${H})`,{taskId:e.taskId,instanceId:c}):(await x(O,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void A("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:Q}));ae.set(e.taskId,o),re.set(e.taskId,i),ce.add(e.taskId),Q++,e.controlToken&&(ue=e.controlToken);const u=new AbortController;ne.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),ee.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:n}),await fe();const{writeToken:d,controlToken:l,...p}=e;t.onStartTask?await t.onStartTask(e,m):await ye(p,m,o,i,u)}catch(r){const s=r?.message??"Agent instance error",n=m??O;try{await ke(e.taskId)}catch{}await x(n,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:s}),t.onError&&await t.onError(e.taskId,r),A("error",s,{taskId:e.taskId}),ee.remove(e.taskId)}finally{const t=de.get(e.taskId);if(t&&(clearTimeout(t),de.delete(e.taskId)),ce.delete(e.taskId),ae.delete(e.taskId),re.delete(e.taskId),se.delete(e.taskId),ne.delete(e.taskId),oe.delete(e.taskId),ie.delete(e.taskId),m)try{m.destroy()}catch{}Q--,0===Q&&ue&&O.setToken(ue),await fe()}}else if("CancelTask"===e.type){const t=ne.get(e.taskId);t?(t.abort(),A("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):ee.get(e.taskId)&&await ge(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)ne.has(e.taskId)?(oe.add(e.taskId),ne.get(e.taskId)?.abort(),A("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):ee.get(e.taskId)&&await ge(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)ne.has(e.taskId)?(ie.add(e.taskId),ne.get(e.taskId)?.abort()):ee.get(e.taskId)&&await ge(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if(ce.has(e.taskId)){const t=re.get(e.taskId)??"anonymous";await x(O,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if(ce.has(e.taskId)){const t=re.get(e.taskId)??"anonymous";await x(O,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&A("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)})}};O.addListener(be),i(a,{instanceId:c,billingMode:D,description:t.description,scaling:{expectedInstances:Y,concurrency:H,maxPendingBacklog:W,maxRunningTimeSec:Z},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:q??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??R?.api.baseUrl,agentAuth:L}).then(e=>{if(console.log(`[AgentInstance] Registered agent: ${a} (instance: ${c})`),!e.controlChannel)throw new Error("Connect response missing controlChannel — server may be outdated");me=e.controlChannel,e.pamToken&&O.setToken(e.pamToken),0!==Y&&"function"==typeof O.setFilterExpression&&O.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),O.subscribe({channels:[me]}),fe().catch(()=>{})}).catch(e=>{const t=e instanceof Error?e.message:String(e);console.error(`[AgentInstance] Failed to register agent: ${a} — ${t}`),e instanceof S&&process.exit(1)}),A("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{G?.destroy(),z.destroy(),O.removeListener(be),me&&O.unsubscribe({channels:[me]}),V&&O.destroy();for(const e of X.streamIds()){const t=X.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}X.clear(),te.clear(),ee.clear()}catch{}},agentName:a,instanceId:c,get controlChannel(){return me},get pubnub(){return O},get subscribeKey(){return f[N].subscribeKey},taskClient:z,publishTerminal:ge,failStream:he,cdmConfig:R}};export const artifactUtils={buildArtifactRef:t,shouldInlineArtifact:a};
@@ -26,6 +26,13 @@ export interface AuthProvider {
26
26
  * Returns false if no refresh is possible and the 401 should propagate.
27
27
  */
28
28
  onAuthFailure(): Promise<boolean>;
29
+ /**
30
+ * Async initialization hook. Called by the transport before the first
31
+ * request. Implementations that require an async bootstrap (e.g.
32
+ * ConsumerAuth token exchange) perform it here. Idempotent — repeated
33
+ * calls are no-ops after the first success.
34
+ */
35
+ ensureReady?(): Promise<void>;
29
36
  }
30
37
  /**
31
38
  * Wraps a static JWT string. No refresh capability -- onAuthFailure()
@@ -62,6 +62,8 @@ export declare class ConsumerAuth implements AuthProvider {
62
62
  private _destroyed;
63
63
  private _refreshTimer;
64
64
  private _refreshPromise;
65
+ private _initialized;
66
+ private _initPromise;
65
67
  constructor(options: ConsumerAuthOptions);
66
68
  /**
67
69
  * Acquire the initial token. Must be called before any other method.
@@ -80,6 +82,7 @@ export declare class ConsumerAuth implements AuthProvider {
80
82
  * sessions can still use the last-known token for its remaining TTL.
81
83
  */
82
84
  destroy(): void;
85
+ ensureReady(): Promise<void>;
83
86
  private _initApiKey;
84
87
  private _refreshApiKey;
85
88
  private _initTokenEndpoint;
@@ -1 +1 @@
1
- import{CURRENT_PROTOCOL_VERSION as e,PROTOCOL_VERSION_HEADER as t}from"./protocol-version.js";import{captureAffinity as r,injectAffinity as s}from"./write-affinity.js";const i="API_KEY_INVALID";export class ConsumerAuth{constructor(e){this._token=null,this._refreshToken=null,this._userId=null,this._expiresIn=0,this._destroyed=!1,this._refreshTimer=null,this._refreshPromise=null,this._apiKey=e.apiKey,this._tokenEndpoint=e.tokenEndpoint,this._tokenProvider=e.tokenProvider,this._baseUrl=(e.baseUrl??"").replace(/\/+$/,""),this._onAuthError=e.onAuthError}async init(){if(this._apiKey)await this._initApiKey();else if(this._tokenEndpoint)await this._initTokenEndpoint();else{if(!this._tokenProvider)throw new Error("ConsumerAuth requires one of: apiKey, tokenEndpoint, or tokenProvider");await this._initTokenProvider()}this._scheduleRefresh()}getAuthHeader(){return this._token?`Bearer ${this._token}`:null}getUserId(){return this._userId}async onAuthFailure(){return!this._destroyed&&this._doRefreshDedup()}destroy(){this._destroyed=!0,this._refreshTimer&&(clearTimeout(this._refreshTimer),this._refreshTimer=null)}async _initApiKey(){const i=`${this._baseUrl}/api/v1/auth/agent/consumer-token`,n={"Content-Type":"application/json",[t]:e};s(n);const o=await fetch(i,{method:"POST",headers:n,body:JSON.stringify({apiKey:this._apiKey})});if(r(o.headers),!o.ok){const e=await o.text().catch(()=>"");throw new Error(`consumer-token failed: HTTP ${o.status}${e?` ${e}`:""}`)}const h=await o.json();this._token=h.accessToken,this._refreshToken=h.refreshToken,this._expiresIn=h.expiresIn,this._userId=h.userId}async _refreshApiKey(){const e=`${this._baseUrl}/api/v1/auth/agent/refresh`,t={Authorization:`Bearer ${this._apiKey}`,"Content-Type":"application/json"};s(t);const n=await fetch(e,{method:"POST",headers:t,body:JSON.stringify({refreshToken:this._refreshToken})});if(r(n.headers),!n.ok){const e=await n.json().catch(()=>({}));if("REFRESH_TOKEN_INVALID"===e.code)return void await this._initApiKey();if(e.code===i)throw new Error(`API key invalid or revoked: ${e.error??i}`);throw new Error(`token refresh failed: ${e.error??`HTTP ${n.status}`}`)}const o=await n.json();this._token=o.accessToken,this._refreshToken=o.refreshToken,"number"==typeof o.expiresIn&&(this._expiresIn=o.expiresIn)}async _initTokenEndpoint(){const e=await this._fetchTokenEndpoint();this._applyTokenResult(e)}async _fetchTokenEndpoint(){const e=this._tokenEndpoint,t="string"==typeof e,r=t?e:e.url;if("string"!=typeof r||!r)throw new Error("TokenEndpointConfig.url is required and must be a non-empty string");const s=t?void 0:e.credentials,i=t?{}:e.headers??{},n=t?{}:e.body??{},o={method:"POST",headers:{"Content-Type":"application/json",...i},body:JSON.stringify(n)};void 0!==s&&(o.credentials=s);const h=await fetch(r,o);if(!h.ok){const e=await h.text().catch(()=>"");throw new Error(`token endpoint failed: HTTP ${h.status}${e?` ${e}`:""}`)}return h.json()}async _initTokenProvider(){const e=await this._tokenProvider();this._applyTokenResult(e)}_applyTokenResult(e){this._token=e.token,this._expiresIn=e.expiresIn,void 0!==e.userId&&(this._userId=e.userId)}async _refreshOnce(){if(this._apiKey)await this._refreshApiKey();else if(this._tokenEndpoint){const e=await this._fetchTokenEndpoint();this._applyTokenResult(e)}else if(this._tokenProvider){const e=await this._tokenProvider();this._applyTokenResult(e)}}_doRefreshDedup(){return this._refreshPromise||(this._refreshPromise=this._refreshOnce().then(()=>(this._scheduleRefresh(),!0)).catch(()=>!1).finally(()=>{this._refreshPromise=null})),this._refreshPromise}_scheduleRefresh(){if(this._destroyed||this._expiresIn<=0)return;this._refreshTimer&&clearTimeout(this._refreshTimer);const e=1e3*this._expiresIn*.8;this._refreshTimer=setTimeout(()=>{this._refreshTimer=null,this._proactiveRefreshWithRetry(0)},e)}async _proactiveRefreshWithRetry(e){if(!this._destroyed)try{await this._refreshOnce(),this._scheduleRefresh()}catch(t){if(e<2){const t=Math.min(5e3*Math.pow(2,e),3e4);this._destroyed||(this._refreshTimer=setTimeout(()=>{this._refreshTimer=null,this._proactiveRefreshWithRetry(e+1)},t))}else{const e=t instanceof Error?t:new Error(String(t));this._onAuthError?this._onAuthError(e):console.warn("[ConsumerAuth] proactive refresh permanently failed:",e.message)}}}}
1
+ import{CURRENT_PROTOCOL_VERSION as e,PROTOCOL_VERSION_HEADER as t}from"./protocol-version.js";import{captureAffinity as i,injectAffinity as s}from"./write-affinity.js";const r="API_KEY_INVALID";export class ConsumerAuth{constructor(e){this._token=null,this._refreshToken=null,this._userId=null,this._expiresIn=0,this._destroyed=!1,this._refreshTimer=null,this._refreshPromise=null,this._initialized=!1,this._initPromise=null,this._apiKey=e.apiKey,this._tokenEndpoint=e.tokenEndpoint,this._tokenProvider=e.tokenProvider,this._baseUrl=(e.baseUrl??"").replace(/\/+$/,""),this._onAuthError=e.onAuthError}async init(){if(this._apiKey)await this._initApiKey();else if(this._tokenEndpoint)await this._initTokenEndpoint();else{if(!this._tokenProvider)throw new Error("ConsumerAuth requires one of: apiKey, tokenEndpoint, or tokenProvider");await this._initTokenProvider()}this._scheduleRefresh(),this._initialized=!0}getAuthHeader(){return this._token?`Bearer ${this._token}`:null}getUserId(){return this._userId}async onAuthFailure(){return!this._destroyed&&this._doRefreshDedup()}destroy(){this._destroyed=!0,this._refreshTimer&&(clearTimeout(this._refreshTimer),this._refreshTimer=null)}async ensureReady(){this._initialized||(this._initPromise||(this._initPromise=this.init().then(()=>{this._initialized=!0},e=>{throw this._initPromise=null,e})),await this._initPromise)}async _initApiKey(){const r=`${this._baseUrl}/api/v1/auth/agent/consumer-token`,n={"Content-Type":"application/json",[t]:e};s(n);const o=await fetch(r,{method:"POST",headers:n,body:JSON.stringify({apiKey:this._apiKey})});if(i(o.headers),!o.ok){const e=await o.text().catch(()=>"");throw new Error(`consumer-token failed: HTTP ${o.status}${e?` ${e}`:""}`)}const h=await o.json();this._token=h.accessToken,this._refreshToken=h.refreshToken,this._expiresIn=h.expiresIn,this._userId=h.userId}async _refreshApiKey(){const e=`${this._baseUrl}/api/v1/auth/agent/refresh`,t={Authorization:`Bearer ${this._apiKey}`,"Content-Type":"application/json"};s(t);const n=await fetch(e,{method:"POST",headers:t,body:JSON.stringify({refreshToken:this._refreshToken})});if(i(n.headers),!n.ok){const e=await n.json().catch(()=>({}));if("REFRESH_TOKEN_INVALID"===e.code)return void await this._initApiKey();if(e.code===r)throw new Error(`API key invalid or revoked: ${e.error??r}`);throw new Error(`token refresh failed: ${e.error??`HTTP ${n.status}`}`)}const o=await n.json();this._token=o.accessToken,this._refreshToken=o.refreshToken,"number"==typeof o.expiresIn&&(this._expiresIn=o.expiresIn)}async _initTokenEndpoint(){const e=await this._fetchTokenEndpoint();this._applyTokenResult(e)}async _fetchTokenEndpoint(){const e=this._tokenEndpoint,t="string"==typeof e,i=t?e:e.url;if("string"!=typeof i||!i)throw new Error("TokenEndpointConfig.url is required and must be a non-empty string");const s=t?void 0:e.credentials,r=t?{}:e.headers??{},n=t?{}:e.body??{},o={method:"POST",headers:{"Content-Type":"application/json",...r},body:JSON.stringify(n)};void 0!==s&&(o.credentials=s);const h=await fetch(i,o);if(!h.ok){const e=await h.text().catch(()=>"");throw new Error(`token endpoint failed: HTTP ${h.status}${e?` ${e}`:""}`)}return h.json()}async _initTokenProvider(){const e=await this._tokenProvider();this._applyTokenResult(e)}_applyTokenResult(e){this._token=e.token,this._expiresIn=e.expiresIn,void 0!==e.userId&&(this._userId=e.userId)}async _refreshOnce(){if(this._apiKey)await this._refreshApiKey();else if(this._tokenEndpoint){const e=await this._fetchTokenEndpoint();this._applyTokenResult(e)}else if(this._tokenProvider){const e=await this._tokenProvider();this._applyTokenResult(e)}}_doRefreshDedup(){return this._refreshPromise||(this._refreshPromise=this._refreshOnce().then(()=>(this._scheduleRefresh(),!0)).catch(()=>!1).finally(()=>{this._refreshPromise=null})),this._refreshPromise}_scheduleRefresh(){if(this._destroyed||this._expiresIn<=0)return;this._refreshTimer&&clearTimeout(this._refreshTimer);const e=1e3*this._expiresIn*.8;this._refreshTimer=setTimeout(()=>{this._refreshTimer=null,this._proactiveRefreshWithRetry(0)},e)}async _proactiveRefreshWithRetry(e){if(!this._destroyed)try{await this._refreshOnce(),this._scheduleRefresh()}catch(t){if(e<2){const t=Math.min(5e3*Math.pow(2,e),3e4);this._destroyed||(this._refreshTimer=setTimeout(()=>{this._refreshTimer=null,this._proactiveRefreshWithRetry(e+1)},t))}else{const e=t instanceof Error?t:new Error(String(t));this._onAuthError?this._onAuthError(e):console.warn("[ConsumerAuth] proactive refresh permanently failed:",e.message)}}}}
@@ -1 +1 @@
1
- import{CURRENT_PROTOCOL_VERSION as r,PROTOCOL_VERSION_HEADER as t}from"./protocol-version.js";import{captureAffinity as e,injectAffinity as o}from"./write-affinity.js";export async function withRetry(r,t=3,e=500){let o;for(let n=0;n<t;n++)try{return await r()}catch(r){o=r;const i=String(r);if(!(i.includes("ENOTFOUND")||i.includes("ETIMEDOUT")||i.includes("ECONNRESET")||i.includes("NetworkIssues"))||n===t-1)throw r;const s=e*Math.pow(2,n)+100*Math.random();await new Promise(r=>setTimeout(r,s))}throw o}export function rpcEndpoint(r,t){return t?`${t.replace(/\/+$/,"")}/api/v1/rpc`:`https://ps.pndsn.com/v1/blocks/sub-key/${r}/rpc`}export class RpcError extends Error{constructor(r,t,e){super(`[RPC] ${r}`),this.name="RpcError",this.rpcMessage=r,this.code=t,this.data=e}}export class BillingModeMismatchError extends RpcError{constructor(r,t,e,o,n){super(r,o,n),this.name="BillingModeMismatchError",this.expected=t,this.got=e}}export async function callRpc(n,i,s){const a=rpcEndpoint(n.subscribeKey,n.baseUrl),c=`rpc-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,d=e=>{const i={"Content-Type":"application/json",[t]:r},s=n.authProvider?.getAuthHeader();return s&&(i.Authorization=s),e&&o(i),i},u={jsonrpc:"2.0",id:c,method:i,params:s},p=async()=>{let r;if(n.agentAuth?r=await n.agentAuth.authenticatedFetch(a,{method:"POST",headers:d(!1),body:JSON.stringify(u)}):(r=await fetch(a,{method:"POST",headers:d(!0),body:JSON.stringify(u)}),e(r.headers)),!r.ok)throw new RpcError(`HTTP ${r.status}`,r.status);const t=await r.json();if(t.error){const r=t.error.data?.message??t.error.message??"Unknown RPC error",e=function(r,t,e){if(!e||"object"!=typeof e)return null;const o=e;if("BillingModeMismatch"!==o.code)return null;const n=o.details;if(!n||"object"!=typeof n)return null;const i=n;return"free"!==i.expected&&"paid"!==i.expected||"free"!==i.got&&"paid"!==i.got?null:new BillingModeMismatchError(r,i.expected,i.got,t,e)}(r,t.error.code,t.error.data);if(e)throw e;throw new RpcError(r,t.error.code,t.error.data)}return t.result};return withRetry(async()=>{try{return await p()}catch(r){if(r instanceof RpcError&&401===r.code&&n.authProvider&&!n.agentAuth){if(await n.authProvider.onAuthFailure())return p()}throw r}})}
1
+ import{CURRENT_PROTOCOL_VERSION as r,PROTOCOL_VERSION_HEADER as t}from"./protocol-version.js";import{captureAffinity as e,injectAffinity as o}from"./write-affinity.js";export async function withRetry(r,t=3,e=500){let o;for(let n=0;n<t;n++)try{return await r()}catch(r){o=r;const i=String(r);if(!(i.includes("ENOTFOUND")||i.includes("ETIMEDOUT")||i.includes("ECONNRESET")||i.includes("NetworkIssues"))||n===t-1)throw r;const a=e*Math.pow(2,n)+100*Math.random();await new Promise(r=>setTimeout(r,a))}throw o}export function rpcEndpoint(r,t){return t?`${t.replace(/\/+$/,"")}/api/v1/rpc`:`https://ps.pndsn.com/v1/blocks/sub-key/${r}/rpc`}export class RpcError extends Error{constructor(r,t,e){super(`[RPC] ${r}`),this.name="RpcError",this.rpcMessage=r,this.code=t,this.data=e}}export class BillingModeMismatchError extends RpcError{constructor(r,t,e,o,n){super(r,o,n),this.name="BillingModeMismatchError",this.expected=t,this.got=e}}export async function callRpc(n,i,a){const s=rpcEndpoint(n.subscribeKey,n.baseUrl),c=`rpc-${Date.now()}-${Math.random().toString(36).slice(2,8)}`,d=e=>{const i={"Content-Type":"application/json",[t]:r},a=n.authProvider?.getAuthHeader();return a&&(i.Authorization=a),e&&o(i),i},u={jsonrpc:"2.0",id:c,method:i,params:a},h=async()=>{let r;if(n.agentAuth?r=await n.agentAuth.authenticatedFetch(s,{method:"POST",headers:d(!1),body:JSON.stringify(u)}):(r=await fetch(s,{method:"POST",headers:d(!0),body:JSON.stringify(u)}),e(r.headers)),!r.ok)throw new RpcError(`HTTP ${r.status}`,r.status);const t=await r.json();if(t.error){const r=t.error.data?.message??t.error.message??"Unknown RPC error",e=function(r,t,e){if(!e||"object"!=typeof e)return null;const o=e;if("BillingModeMismatch"!==o.code)return null;const n=o.details;if(!n||"object"!=typeof n)return null;const i=n;return"free"!==i.expected&&"paid"!==i.expected||"free"!==i.got&&"paid"!==i.got?null:new BillingModeMismatchError(r,i.expected,i.got,t,e)}(r,t.error.code,t.error.data);if(e)throw e;throw new RpcError(r,t.error.code,t.error.data)}return t.result};return n.authProvider?.ensureReady&&await n.authProvider.ensureReady(),withRetry(async()=>{try{return await h()}catch(r){if(r instanceof RpcError&&401===r.code&&n.authProvider&&!n.agentAuth){if(await n.authProvider.onAuthFailure())return h()}throw r}})}
@@ -1 +1 @@
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 l}from"./write-affinity.js";import{getEnv as b}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 g}from"./protocol-version.js";import{getAgent as k}from"./agent-registry.js";const y=new Set(["completed","failed","canceled"]);export class AnonTaskAccessDeniedError extends Error{constructor(e="anon-task-read-token denied: HTTP 403 Forbidden"){super(e),this.name="AnonTaskAccessDeniedError"}}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=f(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 l={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 h(l,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 T(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){if(this._ownsPubNub=!1,"free"!==e.billingMode&&"paid"!==e.billingMode)throw new Error("TaskClient requires a billingMode option ('free' or 'paid')");this._billingMode=e.billingMode,this._anonFingerprint=e.anonFingerprint??null,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.billingMode;if("free"!==s&&"paid"!==s)throw new Error("TaskClient.create() requires a billingMode option ('free' or 'paid')");const r=!!(n.apiKey||n.tokenEndpoint||n.tokenProvider),o=!!n.anonFingerprint;if([n.apiKey,n.tokenEndpoint,n.tokenProvider,n.anonFingerprint].filter(Boolean).length>1)throw new Error("Only one token provider mode may be specified");if(o&&"free"!==s)throw new Error("TaskClient.create() with anonFingerprint requires billingMode: 'free'");const a=n.cdmUrl??b("BLOCKS_CDM_URL"),c=await u(a),d="paid"===s?c.network:c.playground,l=n.subscribeKey??b("BLOCKS_SUBSCRIBE_KEY")??d.subscribeKey,h=n.publishKey??b("BLOCKS_PUBLISH_KEY")??d.publishKey,f=n.baseUrl??b("BLOCKS_BACKEND_URL")??c.api.baseUrl;if(!l)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 p=()=>{const t=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;return new e({subscribeKey:l,publishKey:h||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({billingMode:s,subscribeKey:l,publishKey:h,baseUrl:f,createSessionPubNub:p,defaultOwnerId:e.getUserId()??void 0});return t.config.authProvider=e,t._consumerAuth=e,t}return new TaskClient({billingMode:s,subscribeKey:l,publishKey:h,baseUrl:f,createSessionPubNub:p,anonFingerprint:n.anonFingerprint})}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),l(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 fetchAnonConsumerReadToken(e){if(!this.config.baseUrl)throw new Error("connect() requires a backend baseUrl. Set baseUrl in TaskClientOptions.");if(!this._anonFingerprint)throw new Error("fetchAnonConsumerReadToken called without anonFingerprint");const t=`${this.config.baseUrl.replace(/\/+$/,"")}/api/v1/auth/anon-task-read-token`,n={"Content-Type":"application/json",[g]:m};l(n);const s=await fetch(t,{method:"POST",headers:n,body:JSON.stringify({taskId:e,fingerprint:this._anonFingerprint})});if(d(s.headers),403===s.status){const e=await s.text().catch(()=>"");throw new AnonTaskAccessDeniedError("anon-task-read-token denied: HTTP 403"+(e?` ${e}`:""))}if(!s.ok){const e=await s.text().catch(()=>"");throw new Error(`anon-task-read-token failed: HTTP ${s.status}${e?` ${e}`:""}`)}return s.json()}async sendMessage(e){if(this._anonFingerprint)throw new Error("anon-mode TaskClient does not support sendMessage()");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 l;const b=[];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;b.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:l},c=await a(r,o,n.uploadBody);c.uploadSessionId&&(l=c.uploadSessionId);const{file:u,fileName:d,...h}=t;b.push({partId:h.partId,...h.contentType?{contentType:h.contentType}:{}})}}else{const{file:e,fileName:n,...s}=t;b.push(s)}const h={agentName:e.agentName,billingMode:this._billingMode,requestParts:b};l&&(h.uploadSessionId=l),e.idempotencyKey&&(h.idempotencyKey=e.idempotencyKey),h.ownerId=i;const f={};u&&(f.taskKind=u),null!=d&&(f.duration=d),e.consumerPublicKey&&(f.consumerPublicKey=e.consumerPublicKey),Object.keys(f).length>0&&(h.extensions={blocks:f}),e.pushNotificationConfig&&(h.pushNotificationConfig=e.pushNotificationConfig),e.retryPolicy&&(h.retryPolicy=e.retryPolicy);const m=await t(this.config,"SendMessage",h),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 I=this.createPerSessionPubNub(k),C={subscribeKey:this._subscribeKey,publishKey:this._publishKey},K=S??n(m.taskId,m.orgId??i);try{const t=await I.time(),n=String(t.timetoken),r=p(I);let o,a=new Map,c=[],u="0";if(r?.fetchMessages){const t=w(await T(r,K),m.taskId,e.agentName,C);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:I,ownsSubscribeClient:!0,sdkOptions:C,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,l=[];let b,h=!1;const f={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");h&&b?b(t,n):l.push({message:t,timetoken:n})}};I.addListener(f),I.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:I,ownsSubscribeClient:!0,sdkOptions:C,rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,preloadedStreams:a,preloadedArtifacts:c,externalSubscription:{listener:f,channel:K,onReady:e=>{b=e}}});if(b){const e=b;for(const t of l)t.timetoken>d&&e(t.message,t.timetoken)}return l.length=0,h=!0,g}catch{return new s({taskId:m.taskId,ownerId:i,orgId:m.orgId??i,readToken:k,statusChannel:K,agentName:e.agentName,pubnub:I,ownsSubscribeClient:!0,sdkOptions:C,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._anonFingerprint){const n=await this.getTask(t);if(!n)throw new Error(`Task not found: ${t}`);const s=n.agentName??"",i=n.state??"",r=await this.fetchAnonConsumerReadToken(t);return this._openSessionFromToken({task:n,agentName:s,taskState:i,tokenResult:r,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}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 s=n.agentName??"",i=n.state??"",r=await this.fetchConsumerReadToken(t);return this._openSessionFromToken({task:n,agentName:s,taskState:i,tokenResult:r,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}async _openSessionFromToken(e){const{task:t,agentName:n,taskState:i,tokenResult:r,autoDrain:o,drainWindowMs:a}=e,c=t.taskId,{pamToken:u,channel:d}=r,l={subscribeKey:this._subscribeKey,publishKey:this._publishKey},b=this.createPerSessionPubNub(u);try{const e=await b.time(),r=String(e.timetoken),h=p(b);let f,m=new Map,g=[],k="0";if(h?.fetchMessages){const e=w(await T(h,d),c,n,l);m=e.streams,g=e.artifacts,k=e.highWaterMark,f=e.terminalState}const S=f&&y.has(f)?f:i;if(y.has(S))return new s({taskId:c,ownerId:t.owner??"",readToken:u,statusChannel:d,agentName:n,pubnub:b,ownsSubscribeClient:!0,sdkOptions:l,rpcConfig:this.config,autoDrain:o,drainWindowMs:a,state:S,skipSubscription:!0,preloadedStreams:m,preloadedArtifacts:g});const I="0"!==k?k:r,C=[];let K,P=!1;const N={message:e=>{if(e.channel!==d)return;const t=e.message;if(!t||"object"!=typeof t||!t.type)return;const n=String(e.timetoken??"0");P&&K?K(t,n):C.push({message:t,timetoken:n})}};b.addListener(N),b.subscribe({channels:[d],timetoken:I});const _=new s({taskId:c,ownerId:t.owner??"",readToken:u,statusChannel:d,agentName:n,pubnub:b,ownsSubscribeClient:!0,sdkOptions:l,rpcConfig:this.config,autoDrain:o,drainWindowMs:a,state:i,preloadedStreams:m,preloadedArtifacts:g,externalSubscription:{listener:N,channel:d,onReady:e=>{K=e}}});if(K){const e=K;for(const t of C)t.timetoken>I&&e(t.message,t.timetoken)}return C.length=0,P=!0,_}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,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
+ 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 l}from"./write-affinity.js";import{getEnv as h}from"../env.js";import{StreamRef as b}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 g}from"./protocol-version.js";import{getAgent as k}from"./agent-registry.js";const y=new Set(["completed","failed","canceled"]);export class AnonTaskAccessDeniedError extends Error{constructor(e="anon-task-read-token denied: HTTP 403 Forbidden"){super(e),this.name="AnonTaskAccessDeniedError"}}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=f(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 l={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 b(l,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 T(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){if(this._ownsPubNub=!1,"free"!==e.billingMode&&"paid"!==e.billingMode)throw new Error("TaskClient requires a billingMode option ('free' or 'paid')");this._billingMode=e.billingMode,this._anonFingerprint=e.anonFingerprint??null,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.billingMode;if("free"!==s&&"paid"!==s)throw new Error("TaskClient.create() requires a billingMode option ('free' or 'paid')");const r=!!(n.apiKey||n.tokenEndpoint||n.tokenProvider),o=!!n.anonFingerprint;if([n.apiKey,n.tokenEndpoint,n.tokenProvider,n.anonFingerprint].filter(Boolean).length>1)throw new Error("Only one token provider mode may be specified");if(o&&"free"!==s)throw new Error("TaskClient.create() with anonFingerprint requires billingMode: 'free'");const a=n.cdmUrl??h("BLOCKS_CDM_URL"),c=await u(a),d="paid"===s?c.network:c.playground,l=n.subscribeKey??h("BLOCKS_SUBSCRIBE_KEY")??d.subscribeKey,b=n.publishKey??h("BLOCKS_PUBLISH_KEY")??d.publishKey,f=n.baseUrl??h("BLOCKS_BACKEND_URL")??c.api.baseUrl;if(!l)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 p=()=>{const t=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;return new e({subscribeKey:l,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({billingMode:s,subscribeKey:l,publishKey:b,baseUrl:f,createSessionPubNub:p,defaultOwnerId:e.getUserId()??void 0});return t.config.authProvider=e,t._consumerAuth=e,t}return new TaskClient({billingMode:s,subscribeKey:l,publishKey:b,baseUrl:f,createSessionPubNub:p,anonFingerprint:n.anonFingerprint})}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),l(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 fetchAnonConsumerReadToken(e){if(!this.config.baseUrl)throw new Error("connect() requires a backend baseUrl. Set baseUrl in TaskClientOptions.");if(!this._anonFingerprint)throw new Error("fetchAnonConsumerReadToken called without anonFingerprint");const t=`${this.config.baseUrl.replace(/\/+$/,"")}/api/v1/auth/anon-task-read-token`,n={"Content-Type":"application/json",[g]:m};l(n);const s=await fetch(t,{method:"POST",headers:n,body:JSON.stringify({taskId:e,fingerprint:this._anonFingerprint})});if(d(s.headers),403===s.status){const e=await s.text().catch(()=>"");throw new AnonTaskAccessDeniedError("anon-task-read-token denied: HTTP 403"+(e?` ${e}`:""))}if(!s.ok){const e=await s.text().catch(()=>"");throw new Error(`anon-task-read-token failed: HTTP ${s.status}${e?` ${e}`:""}`)}return s.json()}async sendMessage(e){if(this._anonFingerprint)throw new Error("anon-mode TaskClient does not support sendMessage()");this.config.authProvider?.ensureReady&&await this.config.authProvider.ensureReady();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 l;const h=[];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;h.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:l},c=await a(r,o,n.uploadBody);c.uploadSessionId&&(l=c.uploadSessionId);const{file:u,fileName:d,...b}=t;h.push({partId:b.partId,...b.contentType?{contentType:b.contentType}:{}})}}else{const{file:e,fileName:n,...s}=t;h.push(s)}const b={agentName:e.agentName,billingMode:this._billingMode,requestParts:h};l&&(b.uploadSessionId=l),e.idempotencyKey&&(b.idempotencyKey=e.idempotencyKey),b.ownerId=i;const f={};u&&(f.taskKind=u),null!=d&&(f.duration=d),e.consumerPublicKey&&(f.consumerPublicKey=e.consumerPublicKey),Object.keys(f).length>0&&(b.extensions={blocks:f}),e.pushNotificationConfig&&(b.pushNotificationConfig=e.pushNotificationConfig),e.retryPolicy&&(b.retryPolicy=e.retryPolicy);const m=await t(this.config,"SendMessage",b),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 I=this.createPerSessionPubNub(k),C={subscribeKey:this._subscribeKey,publishKey:this._publishKey},P=S??n(m.taskId,m.orgId??i);try{const t=await I.time(),n=String(t.timetoken),r=p(I);let o,a=new Map,c=[],u="0";if(r?.fetchMessages){const t=w(await T(r,P),m.taskId,e.agentName,C);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:P,agentName:e.agentName,pubnub:I,ownsSubscribeClient:!0,sdkOptions:C,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,l=[];let h,b=!1;const f={message:e=>{if(e.channel!==P)return;const t=e.message;if(!t||"object"!=typeof t||!t.type)return;const n=String(e.timetoken??"0");b&&h?h(t,n):l.push({message:t,timetoken:n})}};I.addListener(f),I.subscribe({channels:[P],timetoken:d});const g=new s({taskId:m.taskId,ownerId:i,orgId:m.orgId??i,readToken:k,statusChannel:P,agentName:e.agentName,pubnub:I,ownsSubscribeClient:!0,sdkOptions:C,rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,preloadedStreams:a,preloadedArtifacts:c,externalSubscription:{listener:f,channel:P,onReady:e=>{h=e}}});if(h){const e=h;for(const t of l)t.timetoken>d&&e(t.message,t.timetoken)}return l.length=0,b=!0,g}catch{return new s({taskId:m.taskId,ownerId:i,orgId:m.orgId??i,readToken:k,statusChannel:P,agentName:e.agentName,pubnub:I,ownsSubscribeClient:!0,sdkOptions:C,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._anonFingerprint){const n=await this.getTask(t);if(!n)throw new Error(`Task not found: ${t}`);const s=n.agentName??"",i=n.state??"",r=await this.fetchAnonConsumerReadToken(t);return this._openSessionFromToken({task:n,agentName:s,taskState:i,tokenResult:r,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}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 s=n.agentName??"",i=n.state??"",r=await this.fetchConsumerReadToken(t);return this._openSessionFromToken({task:n,agentName:s,taskState:i,tokenResult:r,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}async _openSessionFromToken(e){const{task:t,agentName:n,taskState:i,tokenResult:r,autoDrain:o,drainWindowMs:a}=e,c=t.taskId,{pamToken:u,channel:d}=r,l={subscribeKey:this._subscribeKey,publishKey:this._publishKey},h=this.createPerSessionPubNub(u);try{const e=await h.time(),r=String(e.timetoken),b=p(h);let f,m=new Map,g=[],k="0";if(b?.fetchMessages){const e=w(await T(b,d),c,n,l);m=e.streams,g=e.artifacts,k=e.highWaterMark,f=e.terminalState}const S=f&&y.has(f)?f:i;if(y.has(S))return new s({taskId:c,ownerId:t.owner??"",readToken:u,statusChannel:d,agentName:n,pubnub:h,ownsSubscribeClient:!0,sdkOptions:l,rpcConfig:this.config,autoDrain:o,drainWindowMs:a,state:S,skipSubscription:!0,preloadedStreams:m,preloadedArtifacts:g});const I="0"!==k?k:r,C=[];let P,K=!1;const N={message:e=>{if(e.channel!==d)return;const t=e.message;if(!t||"object"!=typeof t||!t.type)return;const n=String(e.timetoken??"0");K&&P?P(t,n):C.push({message:t,timetoken:n})}};h.addListener(N),h.subscribe({channels:[d],timetoken:I});const _=new s({taskId:c,ownerId:t.owner??"",readToken:u,statusChannel:d,agentName:n,pubnub:h,ownsSubscribeClient:!0,sdkOptions:l,rpcConfig:this.config,autoDrain:o,drainWindowMs:a,state:i,preloadedStreams:m,preloadedArtifacts:g,externalSubscription:{listener:N,channel:d,onReady:e=>{P=e}}});if(P){const e=P;for(const t of C)t.timetoken>I&&e(t.message,t.timetoken)}return C.length=0,K=!0,_}catch(e){throw h.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)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocks-network/sdk",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
4
4
  "type": "module",
5
5
  "description": "Blocks Network SDK — build and run A2A agents on PubNub",
6
6
  "exports": {