@blocks-network/sdk 1.0.15 → 1.0.16
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/dist/runtime/agent-registry.d.ts +14 -15
- package/dist/runtime/agent-registry.js +1 -1
- package/dist/runtime/auth-provider.d.ts +3 -3
- package/dist/runtime/consumer-auth.d.ts +19 -6
- package/dist/runtime/consumer-auth.js +1 -1
- package/dist/runtime/task-client.d.ts +21 -0
- package/dist/runtime/task-client.js +1 -1
- package/package.json +1 -1
|
@@ -252,30 +252,29 @@ export declare function getAgent(agentName: string, options?: {
|
|
|
252
252
|
baseUrl?: string;
|
|
253
253
|
apiKey?: string;
|
|
254
254
|
}): Promise<AgentEntry | null>;
|
|
255
|
-
/**
|
|
256
|
-
|
|
257
|
-
*/
|
|
258
|
-
export declare function fetchAgentRegistry(options?: {
|
|
255
|
+
/** Shared options for the exported registry list helpers. */
|
|
256
|
+
interface ListOptions {
|
|
259
257
|
limit?: number;
|
|
260
258
|
cursor?: string;
|
|
261
259
|
baseUrl?: string;
|
|
262
|
-
|
|
260
|
+
/** Bearer credential, sent as `Authorization: Bearer <apiKey>`. Optional on
|
|
261
|
+
* Blocks Network, where the listing is world-readable; required on a Blocks
|
|
262
|
+
* Enterprise deployment, which returns an empty page to an unauthenticated
|
|
263
|
+
* caller. */
|
|
264
|
+
apiKey?: string;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Fetch the agent registry (all agents).
|
|
268
|
+
*/
|
|
269
|
+
export declare function fetchAgentRegistry(options?: ListOptions): Promise<AgentRegistryResult>;
|
|
263
270
|
/**
|
|
264
271
|
* Fetch agents filtered by tag.
|
|
265
272
|
*/
|
|
266
|
-
export declare function fetchAgentsByTag(tag: string, options?:
|
|
267
|
-
limit?: number;
|
|
268
|
-
cursor?: string;
|
|
269
|
-
baseUrl?: string;
|
|
270
|
-
}): Promise<AgentRegistryResult>;
|
|
273
|
+
export declare function fetchAgentsByTag(tag: string, options?: ListOptions): Promise<AgentRegistryResult>;
|
|
271
274
|
/**
|
|
272
275
|
* Fetch agents filtered by listing type.
|
|
273
276
|
*/
|
|
274
|
-
export declare function fetchAgentsByListing(listing: 'private' | 'public', options?:
|
|
275
|
-
limit?: number;
|
|
276
|
-
cursor?: string;
|
|
277
|
-
baseUrl?: string;
|
|
278
|
-
}): Promise<AgentRegistryResult>;
|
|
277
|
+
export declare function fetchAgentsByListing(listing: 'private' | 'public', options?: ListOptions): Promise<AgentRegistryResult>;
|
|
279
278
|
/**
|
|
280
279
|
* Remove an agent from the registry.
|
|
281
280
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"node:os";import{registryAllChannel as
|
|
1
|
+
import t from"node:os";import{registryAllChannel as e,registryTagChannel as n,registryVisibilityChannel as a,registryLogChannel as r,normalizeTagSlug as i}from"./channel-manager.js";import{getEnv as o}from"../env.js";import{CURRENT_PROTOCOL_VERSION as s,SUPPORTED_PROTOCOL_VERSIONS as c,SDK_VERSION as u,PROTOCOL_VERSION_HEADER as l}from"./protocol-version.js";import{captureAffinity as g,injectAffinity as d}from"./write-affinity.js";function m(){try{return t.platform()}catch{return"unknown"}}async function f(t,e=3,n=500){let a;for(let r=0;r<e;r++)try{return await t()}catch(t){a=t;const i=String(t);if(!(i.includes("ENOTFOUND")||i.includes("ETIMEDOUT")||i.includes("ECONNRESET")||i.includes("NetworkIssues"))||r===e-1)throw t;const o=n*Math.pow(2,r)+100*Math.random();await new Promise(t=>setTimeout(t,o))}throw a}function h(t,e){let n=`${function(t){if(t)return t.replace(/\/+$/,"");throw new Error("[AgentRegistry] baseUrl is required: provide it via options or CDM config")}(e)}/api/v1/registry/agents`;if(t){const e=new URLSearchParams;for(const[n,a]of Object.entries(t))void 0!==a&&""!==a&&e.set(n,a);const a=e.toString();a&&(n+="?"+a)}return n}async function p(t,e){const n=new Headers(e?.headers);n.has(l)||n.set(l,s);const a={};d(a);for(const[t,e]of Object.entries(a))n.set(t,e);const r=await fetch(t,{...e,headers:n});if(g(r.headers),404===r.status)return null;if(!r.ok){const t=(await r.json().catch(()=>({}))).message??`HTTP ${r.status}`;throw new Error(`[AgentRegistry] Request failed: ${t}`)}return{data:await r.json(),status:r.status}}function y(t){return{agentName:t.agentName,displayName:t.name??t.agentName,description:t.description,tags:t.tags,scaling:t.scaling,card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:t.listing??"public",billingMode:t.billingMode,createdAt:t.registeredAt}}export async function connectAgent(t,e){if(!/^[a-zA-Z0-9_]+$/.test(t))throw new Error("agentName must contain only alphanumeric characters and underscores (no hyphens)");!function(t){if(t&&"object"==typeof t&&"skills"in t)throw new Error("[blocks-sdk] agent-card field `skills` was renamed to `tags`. Action: rename the field in your code (or in any agent-card.json you load) — same shape, only the key name changes. This SDK ships no compatibility alias. If you publish via `blocks-cli`, upgrading the CLI rewrites the field for you, but programmatic callers (this SDK) must rename.")}(e.card);h(void 0,e.baseUrl);const n=o("BLOCKS_CLI_VERSION"),a={agentName:t,instanceId:e.instanceId,billingMode:e.billingMode,listing:e.listing,expectedInstances:e.scaling?.expectedInstances,concurrency:e.scaling?.concurrency,maxPendingBacklog:e.scaling?.maxPendingBacklog,maxRunningTimeSec:e.scaling?.maxRunningTimeSec,deviceOs:m(),sdkLanguage:"Node",sdkVersion:u,protocolVersions:[...c],preferredProtocolVersion:s};let r;if(n&&(a.cliVersion=n),!e.agentAuth)throw new Error("[AgentRegistry] agentAuth is required. Set BLOCKS_API_KEY and pass agentAuth to connectAgent.");const i=await f(async()=>e.agentAuth.init(a));return r=i.pamToken??void 0,{pamToken:r,agentId:i.agentId,controlChannel:i.controlChannel}}export async function getAgent(t,e){const n=h({agentName:t},e?.baseUrl),a=e?.apiKey?{headers:{Authorization:`Bearer ${e.apiKey}`}}:void 0,r=await f(async()=>p(n,a));return r?y(r.data.agent):null}async function w(t,e){const n={...t};e?.limit&&(n.limit=String(e.limit)),e?.cursor&&(n.cursor=e.cursor);const a=h(n,e?.baseUrl),r=e?.apiKey?{headers:{Authorization:`Bearer ${e.apiKey}`}}:void 0,i=await f(async()=>p(a,r));return i?{agents:i.data.agents.map(y),next:i.data.next??void 0,totalCount:i.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentRegistry(t){return w({include:"full"},t)}export async function fetchAgentsByTag(t,e){return w({include:"full",tag:t},e)}export async function fetchAgentsByListing(t,e){return w({include:"full",listing:t},e)}export async function removeAgent(t,e){const n=h({agentName:t},e?.baseUrl);if(e?.agentAuth){return 404!==(await f(()=>e.agentAuth.authenticatedFetch(n,{method:"DELETE"}))).status}const a=o("BLOCKS_API_KEY");return null!==await f(async()=>p(n,{method:"DELETE",headers:{...a?{Authorization:`Bearer ${a}`}:{}}}))}export{m as detectDeviceOs,e as registryAllChannel,n as registryTagChannel,a as registryVisibilityChannel,r as registryLogChannel,i as normalizeTagSlug};
|
|
@@ -34,9 +34,9 @@ export interface AuthProvider {
|
|
|
34
34
|
*/
|
|
35
35
|
ensureReady?(): Promise<void>;
|
|
36
36
|
/**
|
|
37
|
-
* Returns the recorded
|
|
38
|
-
*
|
|
39
|
-
* or null otherwise. The transport calls this before issuing any
|
|
37
|
+
* Returns the recorded refresh error when the provider is in a
|
|
38
|
+
* known-broken state — proactive refresh exhausted its retries, or a
|
|
39
|
+
* reactive refresh failed — or null otherwise. The transport calls this before issuing any
|
|
40
40
|
* authenticated request and throws the error directly so consumers see
|
|
41
41
|
* the typed `AuthRefreshFailedError` instead of an opaque downstream
|
|
42
42
|
* 401. Implementations without proactive refresh return null / omit.
|
|
@@ -50,10 +50,22 @@ export interface ConsumerAuthOptions {
|
|
|
50
50
|
onAuthError?: (error: Error) => void;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* Set when the underlying `ConsumerAuth` enters a known-broken refresh state —
|
|
54
|
+
* proactive refresh permanently failed after 3 retries, or a reactive refresh
|
|
55
|
+
* failed.
|
|
56
|
+
*
|
|
57
|
+
* Thrown by any authenticated call that runs the `preflightAuthOrThrow` gate:
|
|
58
|
+
* the RPC methods on `TaskClient` and `TaskSession`, `connect()`, and the
|
|
59
|
+
* file-upload helpers. Enumerating them here would drift the moment one is
|
|
60
|
+
* added, so the rule is the gate rather than a list.
|
|
61
|
+
*
|
|
62
|
+
* Also thrown by `getAgentCard()`, for a different reason: it cannot tell a
|
|
63
|
+
* rejected credential from a missing agent on its own, because the registry read
|
|
64
|
+
* is on optional auth, so a stale bearer degrades to anonymous and 404s rather
|
|
65
|
+
* than 401ing.
|
|
66
|
+
*
|
|
67
|
+
* Carries the original failure as `.cause`. Cleared by a subsequent successful
|
|
68
|
+
* token apply.
|
|
57
69
|
*/
|
|
58
70
|
export declare class AuthRefreshFailedError extends Error {
|
|
59
71
|
readonly cause: Error;
|
|
@@ -83,8 +95,9 @@ export declare class ConsumerAuth implements AuthProvider {
|
|
|
83
95
|
getAuthHeader(): string | null;
|
|
84
96
|
getUserId(): string | null;
|
|
85
97
|
/**
|
|
86
|
-
* Returns the last
|
|
87
|
-
*
|
|
98
|
+
* Returns the last refresh failure, or null. Set when proactive refresh
|
|
99
|
+
* exhausts its 3 retries, and when a reactive refresh fails; cleared
|
|
100
|
+
* atomically on the next successful token apply.
|
|
88
101
|
* Callers (TaskClient.sendMessage / connect) use this to fail fast before
|
|
89
102
|
* making any authenticated request.
|
|
90
103
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{log as e}from"./logger.js";import{CURRENT_PROTOCOL_VERSION as t,PROTOCOL_VERSION_HEADER as r}from"./protocol-version.js";import{captureAffinity as i,injectAffinity as s}from"./write-affinity.js";const n=(t,r,i)=>e("[ConsumerAuth]",t,r,i),o="API_KEY_INVALID";export class AuthRefreshFailedError extends Error{constructor(e){super(`Consumer auth refresh permanently failed: ${e.message}`),this.name="AuthRefreshFailedError",this.cause=e}}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._lastAuthError=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}getLastAuthError(){return this._lastAuthError}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 e=`${this._baseUrl}/api/v1/auth/agent/consumer-token`,n={"Content-Type":"application/json",[r]:t};s(n);const o=await fetch(e,{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._refreshToken=h.refreshToken,this._applyTokenResult({token:h.accessToken,expiresIn:h.expiresIn,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 r=await fetch(e,{method:"POST",headers:t,body:JSON.stringify({refreshToken:this._refreshToken})});if(i(r.headers),!r.ok){const e=await r.json().catch(()=>({}));if("REFRESH_TOKEN_INVALID"===e.code)return void await this._initApiKey();if(e.code===o)throw new Error(`API key invalid or revoked: ${e.error??o}`);throw new Error(`token refresh failed: ${e.error??`HTTP ${r.status}`}`)}const n=await r.json();this._token=n.accessToken,this._refreshToken=n.refreshToken,"number"==typeof n.expiresIn&&(this._expiresIn=n.expiresIn),this._lastAuthError=null}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 i=t?void 0:e.credentials,s=t?{}:e.headers??{},n=t?{}:e.body??{},o={method:"POST",headers:{"Content-Type":"application/json",...s},body:JSON.stringify(n)};void 0!==i&&(o.credentials=i);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),this._lastAuthError=null}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(e=>{const t=e instanceof Error?e:new Error(String(e));return n("warn","reactive refresh failed",{event:"consumer_auth_reactive_refresh_failed",error:t.message}),!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._lastAuthError=new AuthRefreshFailedError(e),n("warn","proactive refresh permanently failed",{event:"consumer_auth_proactive_refresh_failed",error:e.message}),this._onAuthError&&this._onAuthError(e)}}}}
|
|
1
|
+
import{log as e}from"./logger.js";import{CURRENT_PROTOCOL_VERSION as t,PROTOCOL_VERSION_HEADER as r}from"./protocol-version.js";import{captureAffinity as i,injectAffinity as s}from"./write-affinity.js";const n=(t,r,i)=>e("[ConsumerAuth]",t,r,i),o="API_KEY_INVALID";export class AuthRefreshFailedError extends Error{constructor(e){super(`Consumer auth refresh permanently failed: ${e.message}`),this.name="AuthRefreshFailedError",this.cause=e}}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._lastAuthError=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}getLastAuthError(){return this._lastAuthError}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 e=`${this._baseUrl}/api/v1/auth/agent/consumer-token`,n={"Content-Type":"application/json",[r]:t};s(n);const o=await fetch(e,{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._refreshToken=h.refreshToken,this._applyTokenResult({token:h.accessToken,expiresIn:h.expiresIn,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 r=await fetch(e,{method:"POST",headers:t,body:JSON.stringify({refreshToken:this._refreshToken})});if(i(r.headers),!r.ok){const e=await r.json().catch(()=>({}));if("REFRESH_TOKEN_INVALID"===e.code)return void await this._initApiKey();if(e.code===o)throw new Error(`API key invalid or revoked: ${e.error??o}`);throw new Error(`token refresh failed: ${e.error??`HTTP ${r.status}`}`)}const n=await r.json();this._token=n.accessToken,this._refreshToken=n.refreshToken,"number"==typeof n.expiresIn&&(this._expiresIn=n.expiresIn),this._lastAuthError=null}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 i=t?void 0:e.credentials,s=t?{}:e.headers??{},n=t?{}:e.body??{},o={method:"POST",headers:{"Content-Type":"application/json",...s},body:JSON.stringify(n)};void 0!==i&&(o.credentials=i);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),this._lastAuthError=null}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(e=>{const t=e instanceof Error?e:new Error(String(e));return this._lastAuthError=new AuthRefreshFailedError(t),n("warn","reactive refresh failed",{event:"consumer_auth_reactive_refresh_failed",error:t.message}),!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._lastAuthError=new AuthRefreshFailedError(e),n("warn","proactive refresh permanently failed",{event:"consumer_auth_proactive_refresh_failed",error:e.message}),this._onAuthError&&this._onAuthError(e)}}}}
|
|
@@ -257,8 +257,29 @@ export declare class TaskClient {
|
|
|
257
257
|
/**
|
|
258
258
|
* Look up an agent's card by name from the registry.
|
|
259
259
|
* Returns null if the agent is not found or has no card.
|
|
260
|
+
*
|
|
261
|
+
* Forwards the client's credential when it has one. The registry read is
|
|
262
|
+
* mounted on optional auth, so this is not required on Blocks Network — but a
|
|
263
|
+
* Blocks Enterprise deployment serves agent metadata to authenticated callers
|
|
264
|
+
* only, and would answer an unauthenticated lookup with null even for a
|
|
265
|
+
* correctly configured client.
|
|
266
|
+
*
|
|
267
|
+
* Throws `AuthRefreshFailedError` when a configured credential cannot be
|
|
268
|
+
* produced. `null` means "no such agent, or no card"; it must not also mean
|
|
269
|
+
* "your token expired", or an auth problem would read as a missing agent.
|
|
260
270
|
*/
|
|
261
271
|
getAgentCard(agentName: string): Promise<AgentCard | null>;
|
|
272
|
+
/**
|
|
273
|
+
* The client's bearer credential, or undefined when it has none.
|
|
274
|
+
*
|
|
275
|
+
* `getAgent`'s `apiKey` option is sent verbatim as `Authorization: Bearer
|
|
276
|
+
* <value>`, so whatever bearer credential the provider is holding works. In
|
|
277
|
+
* practice that is the consumer JWT in all three `ConsumerAuth` modes: API-key
|
|
278
|
+
* mode exchanges the key for a JWT during `init()` and never sends the raw key
|
|
279
|
+
* here. The provider owns refresh, so reading the header per call rather than
|
|
280
|
+
* caching it here keeps a rotated token from going stale.
|
|
281
|
+
*/
|
|
282
|
+
private bearerCredential;
|
|
262
283
|
/**
|
|
263
284
|
* Update keyset keys after an environment switch.
|
|
264
285
|
* Updates both the RPC config and the keys used for PubNub client creation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"pubnub";import{buildPubNubLogConfig as t}from"./pubnub-client.js";import{callRpc as n}from"./rpc-client.js";import{taskChannel as s}from"./channel-manager.js";import{TaskSession as r}from"./task-session.js";import{TerminalDeliveryTracker as i}from"./terminal-delivery-tracker.js";import{preflightAuthOrThrow as a}from"./auth-provider.js";import{ConsumerAuth as o}from"./consumer-auth.js";import{shouldInlineArtifact as c,buildArtifactRef as u}from"./artifacts.js";import{uploadFile as d}from"./file-upload.js";import{normalizeFileInput as l}from"./file-input.js";import{fetchCdmConfig as b}from"./cdm-config.js";import{captureAffinity as h,injectAffinity as p}from"./write-affinity.js";import{getEnv as f}from"../env.js";import{log as m}from"./logger.js";import{StreamRef as g}from"./stream-ref.js";import{invertDirection as k}from"../stream/index.js";import{asPubNubFetcher as y}from"./pubnub-types.js";import{CURRENT_PROTOCOL_VERSION as w,PROTOCOL_VERSION_HEADER as T}from"./protocol-version.js";import{getAgent as I}from"./agent-registry.js";const S=(e,t,n)=>m("[TaskClient]",e,t,n),C=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 P(e,t,n,s){const r=new Map,i=[],a=[];let o,c="0",u="0";for(const d of e){d.timetoken&&d.timetoken>c&&(c=d.timetoken);const e=d.message;if(e&&"object"==typeof e&&e.type){if(a.push(e),"terminal"===e.type){const t=String(d.timetoken??"0");if(t>=u){const n=e.state;"completed"!==n&&"failed"!==n&&"canceled"!==n||(o=n,u=t)}}if("progress"===e.type&&"stream_started"===e.streamEvent&&e.streams){const i=e.declaredStream,a=e.streams;for(const[e,o]of Object.entries(a)){if(!o||"object"!=typeof o||r.has(e))continue;const a=o.direction,c=k(a),u=o.format;if(!u||"bytes"!==u&&"events"!==u)continue;const d=o.affinity;if("dedicated"!==d&&"shared"!==d){S("warn",`history-preload: dropping stream "${e}" for task "${t}" — invalid or missing affinity`,{event:"history_preload_invalid_affinity",streamId:e,taskId:t,receivedAffinity:o.affinity});continue}const l={taskId:t,streamId:e,agentName:n,channel:o.channel,token:o.token,agentDirection:a,localDirection:c,format:u,affinity:d,metadata:o.metadata,declaredStream:i};r.set(e,new g(l,s))}}if("artifact"===e.type&&e.artifactRef){const t=e.artifactRef;t&&"object"==typeof t&&t.kind&&i.push(t)}}}return{streams:r,artifacts:i,events:a,highWaterMark:c,terminalState:o}}async function K(e,t){const n=[];let s;for(;;){const r={channels:[t],count:100};s&&(r.start=s);const i=await e.fetchMessages(r),a=i.channels?.[t]??[];if(0===a.length)break;if(n.push(...a),a.length<100)break;s=a[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,rpcHeaders:e.rpcHeaders},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(n){const s=n??{},r=s.billingMode;if("free"!==r&&"paid"!==r)throw new Error("TaskClient.create() requires a billingMode option ('free' or 'paid')");const i=!!(s.apiKey||s.tokenEndpoint||s.tokenProvider),a=!!s.anonFingerprint;if([s.apiKey,s.tokenEndpoint,s.tokenProvider,s.anonFingerprint].filter(Boolean).length>1)throw new Error("Only one token provider mode may be specified");if(a&&"free"!==r)throw new Error("TaskClient.create() with anonFingerprint requires billingMode: 'free'");const c=s.cdmUrl??f("BLOCKS_CDM_URL"),u=await b(c),d="paid"===r?u.network:u.playground,l=s.subscribeKey??f("BLOCKS_SUBSCRIBE_KEY")??d.subscribeKey,h=s.publishKey??f("BLOCKS_PUBLISH_KEY")??d.publishKey,p=s.baseUrl??f("BLOCKS_BACKEND_URL")??u.api.baseUrl;if(!l)throw new Error("TaskClient.create() could not resolve subscribeKey from options, env, or CDM");if(!p)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 m=()=>{const n=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;return new e({subscribeKey:l,publishKey:h||void 0,userId:n,enableEventEngine:!0,...t()})};if(i){const e=new o({apiKey:s.apiKey,tokenEndpoint:s.tokenEndpoint,tokenProvider:s.tokenProvider,baseUrl:p,onAuthError:s.onAuthError});await e.init();const t=new TaskClient({billingMode:r,subscribeKey:l,publishKey:h,baseUrl:p,createSessionPubNub:m,defaultOwnerId:e.getUserId()??void 0,rpcHeaders:s.rpcHeaders});return t.config.authProvider=e,t._consumerAuth=e,t}return new TaskClient({billingMode:r,subscribeKey:l,publishKey:h,baseUrl:p,createSessionPubNub:m,anonFingerprint:s.anonFingerprint,rpcHeaders:s.rpcHeaders})}getUserId(){return this._consumerAuth?.getUserId()??null}async getAgentCard(e){const t=await I(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(n,s,r){let i;const a=s??this._subscribeKey,o=r??this._publishKey;if(this._createSessionPubNub&&a===this._subscribeKey)i=this._createSessionPubNub();else{const n=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;i=new e({subscribeKey:a,publishKey:o||void 0,userId:n,enableEventEngine:!0,...t()})}return n&&i.setToken(n),i}async fetchConsumerReadToken(e,t="consumer"){if(!this.config.baseUrl)throw new Error("connect() requires a backend baseUrl. Set baseUrl in TaskClientOptions.");const n=`${this.config.baseUrl.replace(/\/+$/,"")}/api/v1/auth/task-read-token`,s=async()=>{const s={"Content-Type":"application/json",[T]:w},r=this.config.authProvider?.getAuthHeader();return r&&(s.Authorization=r),p(s),fetch(n,{method:"POST",headers:s,body:JSON.stringify({taskId:e,role:t})})};let r=await s();if(h(r.headers),401===r.status&&this.config.authProvider){await this.config.authProvider.onAuthFailure()&&(r=await s(),h(r.headers))}if(!r.ok){const e=await r.text().catch(()=>"");throw new Error(`task-read-token failed: HTTP ${r.status}${e?` ${e}`:""}`)}return r.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",[T]:w};p(n);const s=await fetch(t,{method:"POST",headers:n,body:JSON.stringify({taskId:e,fingerprint:this._anonFingerprint})});if(h(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()");await a(this.config.authProvider),this.config.authProvider?.ensureReady&&await this.config.authProvider.ensureReady();const t=e.ownerId||this.defaultOwnerId||"",i=e.taskKind,o=e.duration;if("pipe"===i){if(null==o||!Number.isInteger(o)||o<1||o>43200)throw new Error("Pipe tasks require an integer duration between 1 and 43200 minutes")}else if(null!=o)throw new Error("Request tasks must not include a duration. Duration is only valid for pipe tasks.");let b;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=l(t.file),s=t.fileName??"unnamed",r=t.contentType??"application/octet-stream";if(c(n.size)){const e=await n.getBytes(),i=u({data:e,mimeType:r,fileName:s}),{file:a,fileName:o,text:c,...d}=t;h.push({...d,artifactRef:i})}else{if(!this.config.baseUrl)throw new Error("File upload requires a backend baseUrl. Set baseUrl in TaskClientOptions.");const i={baseUrl:this.config.baseUrl,authProvider:this.config.authProvider,agentAuth:this.config.agentAuth},a={role:"consumer-input",agentName:e.agentName,fileName:s,fileSize:n.size,mimeType:r,partId:t.partId,uploadSessionId:b},o=await d(i,a,n.uploadBody);o.uploadSessionId&&(b=o.uploadSessionId);const{file:c,fileName:u,...l}=t;h.push({partId:l.partId,...l.contentType?{contentType:l.contentType}:{}})}}else{const{file:e,fileName:n,...s}=t;h.push(s)}const p={agentName:e.agentName,billingMode:this._billingMode,requestParts:h};b&&(p.uploadSessionId=b),e.idempotencyKey&&(p.idempotencyKey=e.idempotencyKey),p.ownerId=t;const f={};i&&(f.taskKind=i),null!=o&&(f.duration=o),e.consumerPublicKey&&(f.consumerPublicKey=e.consumerPublicKey),void 0!==e.stream&&(f.stream=e.stream),Object.keys(f).length>0&&(p.extensions={blocks:f}),e.pushNotificationConfig&&(p.pushNotificationConfig=e.pushNotificationConfig),e.retryPolicy&&(p.retryPolicy=e.retryPolicy);const m=await n(this.config,"SendMessage",p),g=m.extensions?.blocks,k=g?.readToken??null,w=g?.streamChannels?.status??void 0,T=g?.subscribeKey??this._subscribeKey,I=g?.publishKey??this._publishKey;if(!0===m.idempotent&&!!m.state&&C.has(m.state))return new r({taskId:m.taskId,ownerId:t,orgId:m.orgId??t,readToken:k,statusChannel:w,agentName:e.agentName,pubnub:null,ownsSubscribeClient:!1,sdkOptions:{subscribeKey:T,publishKey:I},rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,preClosed:!0,state:m.state});const S=this.createPerSessionPubNub(k,T,I),_={subscribeKey:T,publishKey:I},v=w??s(m.taskId,m.orgId??t);try{const n=await S.time(),s=String(n.timetoken),i=y(S);let a,o=new Map,c=[],u="0";if(i?.fetchMessages){const t=P(await K(i,v),m.taskId,e.agentName,_);o=t.streams,c=t.artifacts,u=t.highWaterMark,a=t.terminalState}if(a&&C.has(a))return new r({taskId:m.taskId,ownerId:t,orgId:m.orgId??t,readToken:k,statusChannel:v,agentName:e.agentName,pubnub:S,ownsSubscribeClient:!0,sdkOptions:_,rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,state:a,skipSubscription:!0,preloadedStreams:o,preloadedArtifacts:c});const d="0"!==u?u:s,l=[];let b,h=!1;const p={message:e=>{if(e.channel!==v)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})}};S.addListener(p),S.subscribe({channels:[v],timetoken:d});const f=new r({taskId:m.taskId,ownerId:t,orgId:m.orgId??t,readToken:k,statusChannel:v,agentName:e.agentName,pubnub:S,ownsSubscribeClient:!0,sdkOptions:_,rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,preloadedStreams:o,preloadedArtifacts:c,externalSubscription:{listener:p,channel:v,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,f}catch{return new r({taskId:m.taskId,ownerId:t,orgId:m.orgId??t,readToken:k,statusChannel:v,agentName:e.agentName,pubnub:S,ownsSubscribeClient:!0,sdkOptions:_,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??"",r=n.state??"",i=await this.fetchAnonConsumerReadToken(t);return this._openSessionFromToken({task:n,agentName:s,taskState:r,tokenResult:i,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}if(await a(this.config.authProvider),!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??"",r=n.state??"",i=await this.fetchConsumerReadToken(t,e.role);return this._openSessionFromToken({task:n,agentName:s,taskState:r,tokenResult:i,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}async _openSessionFromToken(e){const{task:t,agentName:n,taskState:s,tokenResult:i,autoDrain:a,drainWindowMs:o}=e,c=t.taskId,{pamToken:u,channel:d}=i,l={subscribeKey:this._subscribeKey,publishKey:this._publishKey},b=this.createPerSessionPubNub(u);try{const e=await b.time(),i=String(e.timetoken),h=y(b);let p,f=new Map,m=[],g=[],k="0";if(h?.fetchMessages){const e=P(await K(h,d),c,n,l);f=e.streams,m=e.artifacts,g=e.events,k=e.highWaterMark,p=e.terminalState}const w=p&&C.has(p)?p:s;if(C.has(w))return new r({taskId:c,ownerId:t.owner??"",readToken:u,statusChannel:d,agentName:n,pubnub:b,ownsSubscribeClient:!0,sdkOptions:l,rpcConfig:this.config,autoDrain:a,drainWindowMs:o,state:w,skipSubscription:!0,preloadedStreams:f,preloadedArtifacts:m,preloadedEvents:g});const T="0"!==k?k:i,I=[];let S,_=!1;const v={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");_&&S?S(t,n):I.push({message:t,timetoken:n})}};b.addListener(v),b.subscribe({channels:[d],timetoken:T});const E=new r({taskId:c,ownerId:t.owner??"",readToken:u,statusChannel:d,agentName:n,pubnub:b,ownsSubscribeClient:!0,sdkOptions:l,rpcConfig:this.config,autoDrain:a,drainWindowMs:o,state:s,preloadedStreams:f,preloadedArtifacts:m,preloadedEvents:g,externalSubscription:{listener:v,channel:d,onReady:e=>{S=e}}});if(S){const e=S;for(const t of I)t.timetoken>T&&(E._appendHistoryEvent(t.message,t.timetoken),e(t.message,t.timetoken))}return I.length=0,_=!0,E}catch(e){throw b.destroy(),e}}async getTask(e){return(await n(this.config,"GetTask",{taskId:e})).task}async listTasks(e){return n(this.config,"ListTasks",{...e})}async cancelTask(e){await n(this.config,"CancelTask",{taskId:e})}async pauseTask(e){await n(this.config,"PauseTask",{taskId:e})}async resumeTask(e){await n(this.config,"ResumeTask",{taskId:e})}async retryTask(e){await n(this.config,"RetryTask",{taskId:e})}async terminateTask(e){await n(this.config,"TerminateTask",{taskId:e})}subscribeToTask(e,t,n){return function(e,t,n,r){const a=s(t,n),o=new i;let c=!1;const u={message:e=>{if(e.channel!==a)return;const t=e.message;if(t&&"object"==typeof t&&t.type){if(r.onEvent)try{r.onEvent(t)}catch(e){_(e,"onEvent",t,r.onError)}switch(t.type){case"progress":if(r.onProgress)try{r.onProgress(t)}catch(e){_(e,"onProgress",t,r.onError)}break;case"artifact":if(r.onArtifact)try{r.onArtifact(t)}catch(e){_(e,"onArtifact",t,r.onError)}break;case"terminal":o.tryDeliver(t,e=>{if(r.onTerminal)try{r.onTerminal(e)}catch(t){_(t,"onTerminal",e,r.onError)}});break;case"cancel_requested":if(o.isDelivered)break;if(c)break;if(c=!0,r.onCancelRequested)try{r.onCancelRequested(t)}catch(e){_(e,"onCancelRequested",t,r.onError)}break;case"system":if(r.onSystem)try{r.onSystem(t)}catch(e){_(e,"onSystem",t,r.onError)}}}}};return e.addListener(u),e.subscribe({channels:[a],timetoken:1e3}),{unsubscribe(){e.removeListener(u),e.unsubscribe({channels:[a]})}}}(this.getPubNub(),e,t,n)}}function _(e,t,n,s){const r=e instanceof Error?e:new Error(String(e));if(s)try{s(r,{entryPoint:"subscribeToTask",callbackType:t,event:n})}catch{}else S("warn",`subscribeToTask callback error in ${t}`,{event:"subscribe_callback_error",callbackType:t,error:r.message})}
|
|
1
|
+
import e from"pubnub";import{buildPubNubLogConfig as t}from"./pubnub-client.js";import{callRpc as n}from"./rpc-client.js";import{taskChannel as s}from"./channel-manager.js";import{TaskSession as r}from"./task-session.js";import{TerminalDeliveryTracker as i}from"./terminal-delivery-tracker.js";import{preflightAuthOrThrow as a}from"./auth-provider.js";import{ConsumerAuth as o}from"./consumer-auth.js";import{shouldInlineArtifact as c,buildArtifactRef as u}from"./artifacts.js";import{uploadFile as d}from"./file-upload.js";import{normalizeFileInput as l}from"./file-input.js";import{fetchCdmConfig as h}from"./cdm-config.js";import{captureAffinity as f,injectAffinity as b}from"./write-affinity.js";import{getEnv as p}from"../env.js";import{log as m}from"./logger.js";import{StreamRef as g}from"./stream-ref.js";import{invertDirection as k}from"../stream/index.js";import{asPubNubFetcher as y}from"./pubnub-types.js";import{CURRENT_PROTOCOL_VERSION as w,PROTOCOL_VERSION_HEADER as T}from"./protocol-version.js";import{getAgent as I}from"./agent-registry.js";const C=(e,t,n)=>m("[TaskClient]",e,t,n),S=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 P(e,t,n,s){const r=new Map,i=[],a=[];let o,c="0",u="0";for(const d of e){d.timetoken&&d.timetoken>c&&(c=d.timetoken);const e=d.message;if(e&&"object"==typeof e&&e.type){if(a.push(e),"terminal"===e.type){const t=String(d.timetoken??"0");if(t>=u){const n=e.state;"completed"!==n&&"failed"!==n&&"canceled"!==n||(o=n,u=t)}}if("progress"===e.type&&"stream_started"===e.streamEvent&&e.streams){const i=e.declaredStream,a=e.streams;for(const[e,o]of Object.entries(a)){if(!o||"object"!=typeof o||r.has(e))continue;const a=o.direction,c=k(a),u=o.format;if(!u||"bytes"!==u&&"events"!==u)continue;const d=o.affinity;if("dedicated"!==d&&"shared"!==d){C("warn",`history-preload: dropping stream "${e}" for task "${t}" — invalid or missing affinity`,{event:"history_preload_invalid_affinity",streamId:e,taskId:t,receivedAffinity:o.affinity});continue}const l={taskId:t,streamId:e,agentName:n,channel:o.channel,token:o.token,agentDirection:a,localDirection:c,format:u,affinity:d,metadata:o.metadata,declaredStream:i};r.set(e,new g(l,s))}}if("artifact"===e.type&&e.artifactRef){const t=e.artifactRef;t&&"object"==typeof t&&t.kind&&i.push(t)}}}return{streams:r,artifacts:i,events:a,highWaterMark:c,terminalState:o}}async function v(e,t){const n=[];let s;for(;;){const r={channels:[t],count:100};s&&(r.start=s);const i=await e.fetchMessages(r),a=i.channels?.[t]??[];if(0===a.length)break;if(n.push(...a),a.length<100)break;s=a[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,rpcHeaders:e.rpcHeaders},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(n){const s=n??{},r=s.billingMode;if("free"!==r&&"paid"!==r)throw new Error("TaskClient.create() requires a billingMode option ('free' or 'paid')");const i=!!(s.apiKey||s.tokenEndpoint||s.tokenProvider),a=!!s.anonFingerprint;if([s.apiKey,s.tokenEndpoint,s.tokenProvider,s.anonFingerprint].filter(Boolean).length>1)throw new Error("Only one token provider mode may be specified");if(a&&"free"!==r)throw new Error("TaskClient.create() with anonFingerprint requires billingMode: 'free'");const c=s.cdmUrl??p("BLOCKS_CDM_URL"),u=await h(c),d="paid"===r?u.network:u.playground,l=s.subscribeKey??p("BLOCKS_SUBSCRIBE_KEY")??d.subscribeKey,f=s.publishKey??p("BLOCKS_PUBLISH_KEY")??d.publishKey,b=s.baseUrl??p("BLOCKS_BACKEND_URL")??u.api.baseUrl;if(!l)throw new Error("TaskClient.create() could not resolve subscribeKey from options, env, or CDM");if(!b)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 m=()=>{const n=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;return new e({subscribeKey:l,publishKey:f||void 0,userId:n,enableEventEngine:!0,...t()})};if(i){const e=new o({apiKey:s.apiKey,tokenEndpoint:s.tokenEndpoint,tokenProvider:s.tokenProvider,baseUrl:b,onAuthError:s.onAuthError});await e.init();const t=new TaskClient({billingMode:r,subscribeKey:l,publishKey:f,baseUrl:b,createSessionPubNub:m,defaultOwnerId:e.getUserId()??void 0,rpcHeaders:s.rpcHeaders});return t.config.authProvider=e,t._consumerAuth=e,t}return new TaskClient({billingMode:r,subscribeKey:l,publishKey:f,baseUrl:b,createSessionPubNub:m,anonFingerprint:s.anonFingerprint,rpcHeaders:s.rpcHeaders})}getUserId(){return this._consumerAuth?.getUserId()??null}async getAgentCard(e){const t=this.config.authProvider,n=async()=>I(e,{baseUrl:this.config.baseUrl,apiKey:await this.bearerCredential()}),s=await n();if(s)return s.card??null;const r=this.config.agentAuth;if(!t&&r){await r.refresh();const e=await n();return e?.card??null}if(!t)return null;if(await t.onAuthFailure()){const e=await n();return e?.card??null}const i=t.getLastAuthError?.();if(i)throw i;return null}async bearerCredential(){const e=this.config.authProvider;if(!e)return this.config.agentAuth?.getAccessToken()??void 0;await a(e),await(e.ensureReady?.());const t=e.getAuthHeader();if(!t)return;const n="Bearer ";return t.startsWith(n)?t.slice(7):t}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(n,s,r){let i;const a=s??this._subscribeKey,o=r??this._publishKey;if(this._createSessionPubNub&&a===this._subscribeKey)i=this._createSessionPubNub();else{const n=`blocks-task-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,6)}`;i=new e({subscribeKey:a,publishKey:o||void 0,userId:n,enableEventEngine:!0,...t()})}return n&&i.setToken(n),i}async fetchConsumerReadToken(e,t="consumer"){if(!this.config.baseUrl)throw new Error("connect() requires a backend baseUrl. Set baseUrl in TaskClientOptions.");const n=`${this.config.baseUrl.replace(/\/+$/,"")}/api/v1/auth/task-read-token`,s=async()=>{const s={"Content-Type":"application/json",[T]:w},r=this.config.authProvider?.getAuthHeader();return r&&(s.Authorization=r),b(s),fetch(n,{method:"POST",headers:s,body:JSON.stringify({taskId:e,role:t})})};let r=await s();if(f(r.headers),401===r.status&&this.config.authProvider){await this.config.authProvider.onAuthFailure()&&(r=await s(),f(r.headers))}if(!r.ok){const e=await r.text().catch(()=>"");throw new Error(`task-read-token failed: HTTP ${r.status}${e?` ${e}`:""}`)}return r.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",[T]:w};b(n);const s=await fetch(t,{method:"POST",headers:n,body:JSON.stringify({taskId:e,fingerprint:this._anonFingerprint})});if(f(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()");await a(this.config.authProvider),this.config.authProvider?.ensureReady&&await this.config.authProvider.ensureReady();const t=e.ownerId||this.defaultOwnerId||"",i=e.taskKind,o=e.duration;if("pipe"===i){if(null==o||!Number.isInteger(o)||o<1||o>43200)throw new Error("Pipe tasks require an integer duration between 1 and 43200 minutes")}else if(null!=o)throw new Error("Request tasks must not include a duration. Duration is only valid for pipe tasks.");let h;const f=[];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=l(t.file),s=t.fileName??"unnamed",r=t.contentType??"application/octet-stream";if(c(n.size)){const e=await n.getBytes(),i=u({data:e,mimeType:r,fileName:s}),{file:a,fileName:o,text:c,...d}=t;f.push({...d,artifactRef:i})}else{if(!this.config.baseUrl)throw new Error("File upload requires a backend baseUrl. Set baseUrl in TaskClientOptions.");const i={baseUrl:this.config.baseUrl,authProvider:this.config.authProvider,agentAuth:this.config.agentAuth},a={role:"consumer-input",agentName:e.agentName,fileName:s,fileSize:n.size,mimeType:r,partId:t.partId,uploadSessionId:h},o=await d(i,a,n.uploadBody);o.uploadSessionId&&(h=o.uploadSessionId);const{file:c,fileName:u,...l}=t;f.push({partId:l.partId,...l.contentType?{contentType:l.contentType}:{}})}}else{const{file:e,fileName:n,...s}=t;f.push(s)}const b={agentName:e.agentName,billingMode:this._billingMode,requestParts:f};h&&(b.uploadSessionId=h),e.idempotencyKey&&(b.idempotencyKey=e.idempotencyKey),b.ownerId=t;const p={};i&&(p.taskKind=i),null!=o&&(p.duration=o),e.consumerPublicKey&&(p.consumerPublicKey=e.consumerPublicKey),void 0!==e.stream&&(p.stream=e.stream),Object.keys(p).length>0&&(b.extensions={blocks:p}),e.pushNotificationConfig&&(b.pushNotificationConfig=e.pushNotificationConfig),e.retryPolicy&&(b.retryPolicy=e.retryPolicy);const m=await n(this.config,"SendMessage",b),g=m.extensions?.blocks,k=g?.readToken??null,w=g?.streamChannels?.status??void 0,T=g?.subscribeKey??this._subscribeKey,I=g?.publishKey??this._publishKey;if(!0===m.idempotent&&!!m.state&&S.has(m.state))return new r({taskId:m.taskId,ownerId:t,orgId:m.orgId??t,readToken:k,statusChannel:w,agentName:e.agentName,pubnub:null,ownsSubscribeClient:!1,sdkOptions:{subscribeKey:T,publishKey:I},rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,preClosed:!0,state:m.state});const C=this.createPerSessionPubNub(k,T,I),K={subscribeKey:T,publishKey:I},_=w??s(m.taskId,m.orgId??t);try{const n=await C.time(),s=String(n.timetoken),i=y(C);let a,o=new Map,c=[],u="0";if(i?.fetchMessages){const t=P(await v(i,_),m.taskId,e.agentName,K);o=t.streams,c=t.artifacts,u=t.highWaterMark,a=t.terminalState}if(a&&S.has(a))return new r({taskId:m.taskId,ownerId:t,orgId:m.orgId??t,readToken:k,statusChannel:_,agentName:e.agentName,pubnub:C,ownsSubscribeClient:!0,sdkOptions:K,rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,state:a,skipSubscription:!0,preloadedStreams:o,preloadedArtifacts:c});const d="0"!==u?u:s,l=[];let h,f=!1;const b={message:e=>{if(e.channel!==_)return;const t=e.message;if(!t||"object"!=typeof t||!t.type)return;const n=String(e.timetoken??"0");f&&h?h(t,n):l.push({message:t,timetoken:n})}};C.addListener(b),C.subscribe({channels:[_],timetoken:d});const p=new r({taskId:m.taskId,ownerId:t,orgId:m.orgId??t,readToken:k,statusChannel:_,agentName:e.agentName,pubnub:C,ownsSubscribeClient:!0,sdkOptions:K,rpcConfig:this.config,idempotent:m.idempotent,queued:m.queued,pushConfigId:m.pushConfigId,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs,preloadedStreams:o,preloadedArtifacts:c,externalSubscription:{listener:b,channel:_,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,f=!0,p}catch{return new r({taskId:m.taskId,ownerId:t,orgId:m.orgId??t,readToken:k,statusChannel:_,agentName:e.agentName,pubnub:C,ownsSubscribeClient:!0,sdkOptions:K,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??"",r=n.state??"",i=await this.fetchAnonConsumerReadToken(t);return this._openSessionFromToken({task:n,agentName:s,taskState:r,tokenResult:i,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}if(await a(this.config.authProvider),!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??"",r=n.state??"",i=await this.fetchConsumerReadToken(t,e.role);return this._openSessionFromToken({task:n,agentName:s,taskState:r,tokenResult:i,autoDrain:e.autoDrain,drainWindowMs:e.drainWindowMs})}async _openSessionFromToken(e){const{task:t,agentName:n,taskState:s,tokenResult:i,autoDrain:a,drainWindowMs:o}=e,c=t.taskId,{pamToken:u,channel:d}=i,l={subscribeKey:this._subscribeKey,publishKey:this._publishKey},h=this.createPerSessionPubNub(u);try{const e=await h.time(),i=String(e.timetoken),f=y(h);let b,p=new Map,m=[],g=[],k="0";if(f?.fetchMessages){const e=P(await v(f,d),c,n,l);p=e.streams,m=e.artifacts,g=e.events,k=e.highWaterMark,b=e.terminalState}const w=b&&S.has(b)?b:s;if(S.has(w))return new r({taskId:c,ownerId:t.owner??"",readToken:u,statusChannel:d,agentName:n,pubnub:h,ownsSubscribeClient:!0,sdkOptions:l,rpcConfig:this.config,autoDrain:a,drainWindowMs:o,state:w,skipSubscription:!0,preloadedStreams:p,preloadedArtifacts:m,preloadedEvents:g});const T="0"!==k?k:i,I=[];let C,K=!1;const _={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&&C?C(t,n):I.push({message:t,timetoken:n})}};h.addListener(_),h.subscribe({channels:[d],timetoken:T});const E=new r({taskId:c,ownerId:t.owner??"",readToken:u,statusChannel:d,agentName:n,pubnub:h,ownsSubscribeClient:!0,sdkOptions:l,rpcConfig:this.config,autoDrain:a,drainWindowMs:o,state:s,preloadedStreams:p,preloadedArtifacts:m,preloadedEvents:g,externalSubscription:{listener:_,channel:d,onReady:e=>{C=e}}});if(C){const e=C;for(const t of I)t.timetoken>T&&(E._appendHistoryEvent(t.message,t.timetoken),e(t.message,t.timetoken))}return I.length=0,K=!0,E}catch(e){throw h.destroy(),e}}async getTask(e){return(await n(this.config,"GetTask",{taskId:e})).task}async listTasks(e){return n(this.config,"ListTasks",{...e})}async cancelTask(e){await n(this.config,"CancelTask",{taskId:e})}async pauseTask(e){await n(this.config,"PauseTask",{taskId:e})}async resumeTask(e){await n(this.config,"ResumeTask",{taskId:e})}async retryTask(e){await n(this.config,"RetryTask",{taskId:e})}async terminateTask(e){await n(this.config,"TerminateTask",{taskId:e})}subscribeToTask(e,t,n){return function(e,t,n,r){const a=s(t,n),o=new i;let c=!1;const u={message:e=>{if(e.channel!==a)return;const t=e.message;if(t&&"object"==typeof t&&t.type){if(r.onEvent)try{r.onEvent(t)}catch(e){K(e,"onEvent",t,r.onError)}switch(t.type){case"progress":if(r.onProgress)try{r.onProgress(t)}catch(e){K(e,"onProgress",t,r.onError)}break;case"artifact":if(r.onArtifact)try{r.onArtifact(t)}catch(e){K(e,"onArtifact",t,r.onError)}break;case"terminal":o.tryDeliver(t,e=>{if(r.onTerminal)try{r.onTerminal(e)}catch(t){K(t,"onTerminal",e,r.onError)}});break;case"cancel_requested":if(o.isDelivered)break;if(c)break;if(c=!0,r.onCancelRequested)try{r.onCancelRequested(t)}catch(e){K(e,"onCancelRequested",t,r.onError)}break;case"system":if(r.onSystem)try{r.onSystem(t)}catch(e){K(e,"onSystem",t,r.onError)}}}}};return e.addListener(u),e.subscribe({channels:[a],timetoken:1e3}),{unsubscribe(){e.removeListener(u),e.unsubscribe({channels:[a]})}}}(this.getPubNub(),e,t,n)}}function K(e,t,n,s){const r=e instanceof Error?e:new Error(String(e));if(s)try{s(r,{entryPoint:"subscribeToTask",callbackType:t,event:n})}catch{}else C("warn",`subscribeToTask callback error in ${t}`,{event:"subscribe_callback_error",callbackType:t,error:r.message})}
|