@blocks-network/sdk 0.1.61 → 0.1.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -43
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/runtime/agent-instance.d.ts +1 -1
- package/dist/runtime/agent-instance.js +1 -1
- package/dist/runtime/agent-registry.d.ts +16 -16
- package/dist/runtime/agent-registry.js +1 -1
- package/dist/runtime/auth-provider.d.ts +30 -0
- package/dist/runtime/auth-provider.js +1 -1
- package/dist/runtime/channel-manager.d.ts +9 -9
- package/dist/runtime/channel-manager.js +1 -1
- package/dist/runtime/consumer-auth.d.ts +18 -0
- package/dist/runtime/consumer-auth.js +1 -1
- package/dist/runtime/file-upload.js +1 -1
- package/dist/runtime/logger.d.ts +14 -12
- package/dist/runtime/logger.js +1 -1
- package/dist/runtime/pubnub-client.d.ts +35 -12
- package/dist/runtime/pubnub-client.js +1 -1
- package/dist/runtime/rpc-client.js +1 -1
- package/dist/runtime/task-client.js +1 -1
- package/dist/runtime/task-session.js +1 -1
- package/dist/runtime/transport-categories.d.ts +60 -0
- package/dist/runtime/transport-categories.js +1 -0
- package/dist/stream/index.d.ts +1 -1
- package/dist/stream/stream-client.d.ts +17 -22
- package/dist/stream/stream-client.js +1 -1
- package/dist/stream/types.d.ts +43 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Create an `agent-card.json`:
|
|
|
34
34
|
"provider": { "organization": "Your Org" }
|
|
35
35
|
},
|
|
36
36
|
"capabilities": { "taskKinds": ["request"] },
|
|
37
|
-
"
|
|
37
|
+
"tags": [{ "id": "main", "name": "Main" }],
|
|
38
38
|
"runtime": {
|
|
39
39
|
"handler": "./handler.ts"
|
|
40
40
|
}
|
|
@@ -77,7 +77,7 @@ The agent card follows the A2A specification with a `runtime` extension:
|
|
|
77
77
|
"provider": { "organization": "Your Org" }
|
|
78
78
|
},
|
|
79
79
|
"capabilities": { "taskKinds": ["request"] },
|
|
80
|
-
"
|
|
80
|
+
"tags": [{ "id": "main", "name": "Main" }],
|
|
81
81
|
"runtime": {
|
|
82
82
|
"handler": "./handler.ts",
|
|
83
83
|
"handlerExport": "default",
|
|
@@ -89,29 +89,25 @@ The agent card follows the A2A specification with a `runtime` extension:
|
|
|
89
89
|
Note: `identity.agentName` must use only alphanumeric characters and
|
|
90
90
|
underscores (no hyphens). The pattern is `^[a-zA-Z0-9_]+$`.
|
|
91
91
|
|
|
92
|
-
##
|
|
93
|
-
|
|
94
|
-
The
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
`
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
**Per-task and per-stream clients** keep PubNub's default 6-attempt
|
|
113
|
-
retry budget. They are short-lived; a stuck task should fail cleanly
|
|
114
|
-
rather than loop forever.
|
|
92
|
+
## Subscribe Strategy
|
|
93
|
+
|
|
94
|
+
The SDK keeps the control client subscribed for the agent's lifetime
|
|
95
|
+
and configures the underlying realtime transport to retry indefinitely
|
|
96
|
+
on transient failures. The agent therefore resumes within seconds of a
|
|
97
|
+
network outage ending without any manual intervention.
|
|
98
|
+
|
|
99
|
+
Connectivity-state changes are surfaced through the SDK log:
|
|
100
|
+
|
|
101
|
+
- `event: 'transport_degraded'` (warn) — emitted when the transport
|
|
102
|
+
reports network down, network issues, timeout, or a malformed
|
|
103
|
+
response. The log line reads `connectivity degraded: <category>`,
|
|
104
|
+
where `<category>` is one of `network_down`, `network_issues`,
|
|
105
|
+
`timeout`, `malformed_response`.
|
|
106
|
+
- `event: 'transport_restored'` (info) — emitted on reconnection.
|
|
107
|
+
|
|
108
|
+
Per-task and per-stream clients keep the default short retry budget.
|
|
109
|
+
They are short-lived; a stuck task should fail cleanly rather than
|
|
110
|
+
loop forever.
|
|
115
111
|
|
|
116
112
|
## API
|
|
117
113
|
|
|
@@ -128,9 +124,19 @@ Go CLI's `blocks login` command writes the API key to `.env` when
|
|
|
128
124
|
invoked with `--write-env` (or by answering "y" to its interactive
|
|
129
125
|
prompt).
|
|
130
126
|
|
|
131
|
-
|
|
127
|
+
Access tokens for realtime channel access are managed by the SDK at runtime
|
|
132
128
|
(granted at registration, refreshed per-task on the control channel).
|
|
133
|
-
No CLI involvement is needed for
|
|
129
|
+
No CLI involvement is needed for token management.
|
|
130
|
+
|
|
131
|
+
### Logging
|
|
132
|
+
|
|
133
|
+
The SDK respects two log-related environment variables:
|
|
134
|
+
|
|
135
|
+
- `LOG_LEVEL` (default: `info`) — `error` | `warn` | `info` | `debug`. Threshold for the SDK's own log lines (`[AgentInstance]`, `[Transport]`).
|
|
136
|
+
- `BLOCKS_DEBUG_INTERNAL` (default: unset) — comma-separated list of subsystems to enable. Neither subsystem is implied by `LOG_LEVEL=debug`.
|
|
137
|
+
- `diagnostics` — Blocks-authored transport-connectivity diagnostics (connection-state transitions, alive snapshots).
|
|
138
|
+
- `forward_transport` — routes the underlying transport's own log output through the Blocks logger with a `[Transport]` prefix, filtered by `LOG_LEVEL`. Off by default because the forwarded messages reference internal SDK config concepts that aren't meaningful to a Blocks developer. **Security note:** forwarded entries may include constructed transport URLs whose query strings carry the access-token (`auth=…`) or request-signature (`signature=…`) parameters. Do not enable in environments that ship logs to less-trusted sinks (third-party log shippers, public Sentry projects, support-ticket attachments).
|
|
139
|
+
- Example: `BLOCKS_DEBUG_INTERNAL=diagnostics,forward_transport`
|
|
134
140
|
|
|
135
141
|
## Examples
|
|
136
142
|
|
|
@@ -387,7 +393,7 @@ extends `Uint8Array`.
|
|
|
387
393
|
|
|
388
394
|
### Stream Consumer APIs
|
|
389
395
|
|
|
390
|
-
All consumer iterators (`bytes()`, `events()`, `readable()`, `inbound`) deliver messages in sequence order. The SDK's reorder buffer transparently corrects out-of-order
|
|
396
|
+
All consumer iterators (`bytes()`, `events()`, `readable()`, `inbound`) deliver messages in sequence order. The SDK's reorder buffer transparently corrects out-of-order delivery and drops duplicate messages. To customize or disable reordering, pass `reorderTimeoutMs` to `open()`:
|
|
391
397
|
|
|
392
398
|
```typescript
|
|
393
399
|
// Default: reorder buffer with 750ms gap timeout
|
|
@@ -427,10 +433,10 @@ readable.pipe(createWriteStream('./output.bin'));
|
|
|
427
433
|
### Handling Stream Errors
|
|
428
434
|
|
|
429
435
|
Every `StreamClient` exposes an `onError(cb)` registration method.
|
|
430
|
-
The callback fires whenever the stream's
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
436
|
+
The callback fires whenever the stream's subscribe loop surfaces an
|
|
437
|
+
error-category status event: access revocation, network issues,
|
|
438
|
+
timeouts, or any other transport error category. The payload is a
|
|
439
|
+
typed `StreamError`:
|
|
434
440
|
|
|
435
441
|
```typescript
|
|
436
442
|
import type { StreamError } from '@blocks-network/sdk/stream';
|
|
@@ -450,14 +456,15 @@ the stream so `for await` / `for msg in ...` loops exit cleanly
|
|
|
450
456
|
instead of hanging waiting for a `stream_end` that will never
|
|
451
457
|
arrive:
|
|
452
458
|
|
|
453
|
-
- `
|
|
459
|
+
- `access_denied` — access grant revoked (admin-terminate or
|
|
454
460
|
token denied). This is the signal that the server-side grant is
|
|
455
|
-
gone even if the cached
|
|
456
|
-
- `
|
|
461
|
+
gone even if the cached token's `exp` claim has not elapsed.
|
|
462
|
+
- `bad_request` — auth configuration or malformed grant.
|
|
457
463
|
|
|
458
|
-
All other error categories (
|
|
459
|
-
|
|
460
|
-
|
|
464
|
+
All other error categories (`network_down`, `network_issues`,
|
|
465
|
+
`timeout`, `malformed_response`, `other`) fire `onError` with
|
|
466
|
+
`fatal: false` and leave the stream running so the transport's
|
|
467
|
+
built-in retry machinery can recover.
|
|
461
468
|
|
|
462
469
|
### Opening Task Streams
|
|
463
470
|
|
|
@@ -519,8 +526,8 @@ The option is supported on both `sendMessage()` and `connect()`.
|
|
|
519
526
|
|
|
520
527
|
### Reconnecting to Terminal Tasks
|
|
521
528
|
|
|
522
|
-
Stream data is **live-only** —
|
|
523
|
-
payloads. When `client.connect({ taskId })` returns a session for a
|
|
529
|
+
Stream data is **live-only** — the realtime transport does not persist
|
|
530
|
+
stream payloads. When `client.connect({ taskId })` returns a session for a
|
|
524
531
|
task that has already finished, a stream that was **never opened
|
|
525
532
|
while the task was active** throws a typed `StreamUnavailableError`
|
|
526
533
|
from `StreamRef.open()` instead of subscribing to a dead channel.
|
|
@@ -601,8 +608,8 @@ are browser-safe — no Node.js `Buffer` polyfill needed:
|
|
|
601
608
|
`Blob` field. `fetch` computes the multipart boundary
|
|
602
609
|
automatically — no manual `Content-Type: multipart/form-data`
|
|
603
610
|
header, no `Buffer.concat`.
|
|
604
|
-
- `downloadArtifact()` handles the three
|
|
605
|
-
(raw `Uint8Array`, `Blob`, and legacy
|
|
611
|
+
- `downloadArtifact()` handles the three download shapes returned by
|
|
612
|
+
the transport (raw `Uint8Array`, `Blob`, and legacy file object with
|
|
606
613
|
`toArrayBuffer()`) with typeof-guarded branches — no
|
|
607
614
|
`Buffer.isBuffer` OR-order short-circuits.
|
|
608
615
|
- `decodeInlineArtifact()` uses `atob` + `Uint8Array` and
|
|
@@ -643,4 +650,4 @@ the SDK does not read from `process.env` in browser environments:
|
|
|
643
650
|
|
|
644
651
|
## License
|
|
645
652
|
|
|
646
|
-
|
|
653
|
+
See [LICENSE](./LICENSE).
|
package/dist/index.d.ts
CHANGED
|
@@ -2,13 +2,13 @@ export { startAgentInstance } from './runtime/agent-instance.js';
|
|
|
2
2
|
export type { ArtifactEntry, HandlerFn, HandlerResult, RequestPart, StartTaskMessage, ExpireTaskMessage, TaskContext, CreateStreamOptions, AgentInstanceOptions, AgentInstancePresenceState, AgentInstanceHandle, } from './runtime/agent-instance.js';
|
|
3
3
|
export type { StreamObject, OnActivateCallback } from './runtime/stream-context.js';
|
|
4
4
|
export type { InboundMessage, StreamError } from './stream/index.js';
|
|
5
|
-
export type { AgentCard, OutputAgentCard,
|
|
5
|
+
export type { AgentCard, OutputAgentCard, AgentTag } from './runtime/agent-registry.js';
|
|
6
6
|
export { createPubNubClient, type PubNubClientConfig } from './runtime/pubnub-client.js';
|
|
7
7
|
export { createChannelManager, ChannelManager, streamChannel } from './runtime/channel-manager.js';
|
|
8
8
|
export { buildArtifactRef, shouldInlineArtifact, decodeInlineArtifact, downloadArtifact, type ArtifactRef, type DownloadedArtifact, } from './runtime/artifacts.js';
|
|
9
|
-
export { connectAgent, fetchAgentRegistry, getAgent, removeAgent,
|
|
9
|
+
export { connectAgent, fetchAgentRegistry, getAgent, removeAgent, fetchAgentsByTag, fetchAgentsByListing, type AgentEntry, type AgentRegistryResult, } from './runtime/agent-registry.js';
|
|
10
10
|
export { DEFAULTS } from './defaults.js';
|
|
11
|
-
/** Platform-wide upload ceiling (bytes). Mirrors
|
|
11
|
+
/** Platform-wide upload ceiling (bytes). Mirrors the service's MAX_FILE_SIZE_BYTES. */
|
|
12
12
|
export declare const BLOCKS_MAX_UPLOAD_BYTES: number;
|
|
13
13
|
export { loadBlocksConfig, type BlocksConfig } from './config-loader.js';
|
|
14
14
|
export { TaskClient, AnonTaskAccessDeniedError } from './runtime/task-client.js';
|
|
@@ -22,7 +22,7 @@ export type { StreamDescriptor } from './runtime/stream-ref.js';
|
|
|
22
22
|
export { AgentAuth, AgentAuthFatalError } from './runtime/agent-auth.js';
|
|
23
23
|
export type { RegistrationPayload, RegistrationResult } from './runtime/agent-auth.js';
|
|
24
24
|
export type { AuthProvider } from './runtime/auth-provider.js';
|
|
25
|
-
export { ConsumerAuth } from './runtime/consumer-auth.js';
|
|
25
|
+
export { ConsumerAuth, AuthRefreshFailedError } from './runtime/consumer-auth.js';
|
|
26
26
|
export type { ConsumerAuthOptions, TokenEndpointConfig, TokenEndpointCredentials, TokenResult, } from './runtime/consumer-auth.js';
|
|
27
27
|
export { StreamRegistry } from './runtime/stream-registry.js';
|
|
28
28
|
export { CredentialCache } from './runtime/credential-cache.js';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{startAgentInstance}from"./runtime/agent-instance.js";export{createPubNubClient}from"./runtime/pubnub-client.js";export{createChannelManager,ChannelManager,streamChannel}from"./runtime/channel-manager.js";export{buildArtifactRef,shouldInlineArtifact,decodeInlineArtifact,downloadArtifact}from"./runtime/artifacts.js";export{connectAgent,fetchAgentRegistry,getAgent,removeAgent,
|
|
1
|
+
export{startAgentInstance}from"./runtime/agent-instance.js";export{createPubNubClient}from"./runtime/pubnub-client.js";export{createChannelManager,ChannelManager,streamChannel}from"./runtime/channel-manager.js";export{buildArtifactRef,shouldInlineArtifact,decodeInlineArtifact,downloadArtifact}from"./runtime/artifacts.js";export{connectAgent,fetchAgentRegistry,getAgent,removeAgent,fetchAgentsByTag,fetchAgentsByListing}from"./runtime/agent-registry.js";export{DEFAULTS}from"./defaults.js";import{DEFAULTS as e}from"./defaults.js";export const BLOCKS_MAX_UPLOAD_BYTES=e.maxUploadBytes;export{loadBlocksConfig}from"./config-loader.js";export{TaskClient,AnonTaskAccessDeniedError}from"./runtime/task-client.js";export{RpcError,BillingModeMismatchError}from"./runtime/rpc-client.js";export{TaskSession}from"./runtime/task-session.js";export{textPart,filePart,filePartFromPath}from"./runtime/part-helpers.js";export{StreamRef,StreamUnavailableError}from"./runtime/stream-ref.js";export{AgentAuth,AgentAuthFatalError}from"./runtime/agent-auth.js";export{ConsumerAuth,AuthRefreshFailedError}from"./runtime/consumer-auth.js";export{StreamRegistry}from"./runtime/stream-registry.js";export{CredentialCache}from"./runtime/credential-cache.js";export{fetchCdmConfig,DEFAULT_CDM_URL}from"./runtime/cdm-config.js";
|
|
@@ -192,7 +192,7 @@ export interface ControlMessage {
|
|
|
192
192
|
/**
|
|
193
193
|
* True when the diag entry is "parked" — past the staleness threshold
|
|
194
194
|
* AND not currently in the connected state. A long-silent
|
|
195
|
-
*
|
|
195
|
+
* 'connected' entry is healthy by definition: at LOG_LEVEL=info
|
|
196
196
|
* the SDK suppresses successful-heartbeat status events
|
|
197
197
|
* (announceSuccessfulHeartbeats=false), so lastStatusAt does not
|
|
198
198
|
* advance on a healthy idle client. Without the category gate, the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{StreamClient as e}from"../stream/index.js";import{buildArtifactRef as t,shouldInlineArtifact as a,decodeInlineArtifact as n}from"./artifacts.js";import{createPubNubClient as s}from"./pubnub-client.js";import{createChannelManager as r}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{log as f,isInternalDebugEnabled as p,_resolveLogLevel as k,_LOG_LEVEL_ORDER as g}from"./logger.js";import{uploadFile as y}from"./file-upload.js";import{StaticAuthProvider as h}from"./auth-provider.js";import{StreamRegistry as b}from"./stream-registry.js";import{CredentialCache as I}from"./credential-cache.js";import{createStreamObject as w,createExternalStreamObject as v,runOnActivate as S}from"./stream-context.js";import{TaskClient as T}from"./task-client.js";import{ConsumerAuth as C}from"./consumer-auth.js";import{fetchCdmConfig as $}from"./cdm-config.js";import{AgentAuth as K,AgentAuthFatalError as _}from"./agent-auth.js";import{CURRENT_PROTOCOL_VERSION as A,SUPPORTED_PROTOCOL_VERSIONS as E,isProtocolVersionSupported as x}from"./protocol-version.js";const M=async(e,t,a,n,s,i=A)=>{const d=r(n);try{await e.publish({channel:d.taskChannel(t,a),message:o({...s,protocolVersion:i}),storeInHistory:!0,sendByPost:!0,meta:o({agentName:n,taskId:t,protocolVersion:i})})}catch{}};export function _isDiagEntryStale(e){const{lastStatusAt:t,lastCategory:a,now:n,thresholdMs:s}=e;return null!==t&&("PNConnectedCategory"!==a&&n-t>s)}const N=(e,t,a)=>f("[AgentInstance]",e,t,a);export function resolveMaxRunningTimeSec(e,t){return void 0!==e&&void 0!==t&&e!==t&&f("[AgentInstance]","info",`opts.maxRunningTimeSec (${e}) overrides card.runtime.maxRunningTimeSec (${t})`,{event:"max_running_time_override",optsValue:e,cardValue:t}),e??t}export function computeStreamDurationMinutes(e,t,a){return"number"==typeof e?e:t?60:Math.ceil((a??3600)/60)}const R=async(e,n,s,r,o,i,d,c,l)=>{const u={type:"artifact",taskId:n};if(c&&(u.outputId=c),a(o.length))await M(e,n,s,r,{...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 y(l,{role:"provider-output",taskId:n,fileName:d??`${n}-artifact`,fileSize:o.length,mimeType:i,outputId:c},o)}};async function P(e,t){const a=`setup.${t.orgId}.${t.taskId}`,n={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:A};t.phase&&(n.phase=t.phase),t.metadata&&(n.metadata=t.metadata);try{throw await e.publish({channel:a,message:o(n),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=r(a),c=t.instanceId??`AG-${a}-${crypto.randomUUID()}`,l=t.token;let f,y=null,j="playground",U=j;if(t.pubnub){const e=t.pubnub._configuration?.keySet,a={publishKey:e?.publishKey??"",subscribeKey:e?.subscribeKey??""};f={playground:a,network:a}}else y=await $(t.cdmUrl),f={playground:y.playground,network:y.network},N("info","CDM config loaded — environment switching enabled",{event:"cdm_config_loaded"});const q=m("BLOCKS_API_KEY");if(!q)throw new Error("BLOCKS_API_KEY is required. Run 'blocks login --write-env' to set up credentials.");let D;const B=t.baseUrl??y?.api.baseUrl;let V,F;if(q&&B&&(D=new K(q,B),N("info","AgentAuth created — will authenticate via connect",{event:"agent_auth_created"})),y){const e=t.baseUrl??y.api.baseUrl,n=await d(a,{baseUrl:e,apiKey:q});if(!n)throw new Error(`[AgentInstance] Agent "${a}" not found in registry. Register the agent (e.g. via \`blocks publish\`) before starting an instance.`);if(!n.billingMode)throw new Error(`[AgentInstance] Agent "${a}" registry entry is missing billingMode. Re-register the agent so the registry persists an explicit billingMode.`);V=n.billingMode,F=n.listing,j="paid"===V?"network":"playground",N("info",`registry billingMode: ${V} — using ${j} environment`,{event:"registry_billing_mode_resolved",billingMode:V,environment:j}),U=j}const L=V??"free",O=[];let z=null;const H=p(),G=(e,t)=>{if(!H)return;const a={label:e,pn:t,listener:void 0,lastStatusAt:null,lastConnectedAt:null,lastMessageAt:null,lastCategory:null,lastOperation:null,lastStatusCode:null};a.listener=(e=>({status:t=>{const a=t,n=String(a.category??""),s=String(a.operation??""),r="number"==typeof a.statusCode?a.statusCode:null,o=Date.now(),i=e.lastCategory;e.lastStatusAt=o,e.lastCategory=n,e.lastOperation=s,e.lastStatusCode=r,"PNConnectedCategory"===n&&(e.lastConnectedAt=o),N("debug","pubnub status event",{event:"pubnub_status",client:e.label,category:n,operation:s,statusCode:r,error:Boolean(a.error),affectedChannels:Array.isArray(a.affectedChannels)?a.affectedChannels.length:void 0,instanceId:c}),n&&n!==i&&N("info",`pubnub status transition [${e.label}]: ${i??"(none)"} -> ${n}`,{event:"pubnub_status_transition",client:e.label,from:i,to:n,operation:s,statusCode:r,instanceId:c})},message:()=>{e.lastMessageAt=Date.now()}}))(a),O.push(a),t.addListener(a.listener)},Y=e=>{if(!H)return;const t=O.findIndex(t=>t.pn===e);if(t<0)return;const a=O[t];try{e.removeListener(a.listener)}catch{}O.splice(t,1)};let W,Z=!1;if(t.pubnub)W=t.pubnub,Z=!1,l&&W.setToken(l);else{const e=f[U];W=s({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0,onRetry:e=>{N("warn","pubnub transport retrying",{event:"pubnub_transport_retry",message:e,instanceId:c})}}),Z=!0,l&&W.setToken(l)}let J=f[U].subscribeKey,Q=f[U].publishKey;const X=B?new C({apiKey:q,baseUrl:B}):void 0,ee=new T({billingMode:L,subscribeKey:J,publishKey:Q,authProvider:X,baseUrl:B,createPubNub:()=>{const e=s({publishKey:Q,subscribeKey:J,userId:`${c}-taskclient`,subscribeRetryUnbounded:!1});return G("consumer-task",e),e}}),te=t.concurrency??u.concurrency,ae=t.expectedInstances??u.expectedInstances,ne=t.maxPendingBacklog,se=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),re=Date.now();let oe=0;const ie=new b,de=new I,ce=new Map,le=new Map,ue=new Map,me=new Map,fe=new Map,pe=new Set,ke=new Set,ge=new Map,ye=new Set,he=new Map;let be,Ie;t.logChannel;const we=async()=>{if("function"==typeof W.setState&&Ie)try{await W.setState({channels:[Ie],state:{instanceId:c,activeTasks:oe,activeStreams:ie.activeStreamCount,concurrency:te,startedAt:re,preferredProtocolVersion:A,protocolVersions:[...E]}})}catch{}},ve=(e,t)=>{const a=ce.get(e);a&&(a.delete(t),0===a.size&&ce.delete(e))},Se=async e=>{(e=>{for(const[t,a]of ce)a.has(e)&&(a.delete(e),0===a.size&&ce.delete(t))})(e);const t=ie.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},Te=async(e,t)=>{ve(e,t);const a=ie.get(e);if(0===ie.release(e,t)&&a?.streamClient){N("info","last-ref teardown",{event:"stream_registry_last_ref_teardown",streamId:e,affinity:a.affinity,releasingTaskId:t});try{await a.streamClient.end()}catch{}}we().catch(()=>{})},Ce=async(e,t)=>{const a=ie.forceRemove(e);if((e=>{ce.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=de.get(e);if(a){try{const n=f[a.environment??j]??f[j],r=s({publishKey:n.publishKey,subscribeKey:n.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken),await M(r,e,a.orgId,a.agentName,{type:"terminal",taskId:e,state:"failed",error:t}),r.destroy()}catch{}de.remove(e)}}await we()}},$e=async(e,t)=>{const a=de.get(e);if(!a)throw new Error(`No cached credentials for task ${e}`);const n=f[a.environment??j]??f[j],r=s({publishKey:n.publishKey,subscribeKey:n.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken);try{await M(r,e,a.orgId,a.agentName,{...t,type:"terminal",taskId:e})}finally{r.destroy()}await Se(e),de.remove(e),await we()},Ke=async(s,r,i,d,c)=>{const u=a,m="pipe"===s.taskKind,f=m?"pipe":"request",p=computeStreamDurationMinutes(s.duration,m,se);if(m&&"number"==typeof s.durationExpiresAtMs){const e=Math.max(0,s.durationExpiresAtMs-Date.now());if(e>0){const t=setTimeout(()=>{pe.add(s.taskId),c.abort()},e);ge.set(s.taskId,t)}}await M(r,s.taskId,d,u,{type:"progress",taskId:s.taskId,progress:0,state:"running"}),he.set(s.taskId,0);let k=0,g=null,y=null;const b=()=>{if(null===g)return;const e=g;g=null,k=Date.now(),M(r,s.taskId,d,u,{type:"progress",taskId:s.taskId,message:e}).catch(()=>{})},I={taskId:s.taskId,requestParts:s.requestParts??[],reportStatus:e=>{me.set(s.taskId,e);const t=Date.now();if(t-k>=1e3)g=null,k=t,M(r,s.taskId,d,u,{type:"progress",taskId:s.taskId,message:e}).catch(()=>{});else if(g=e,!y){y=setTimeout(()=>{y=null,b()},1e3-(t-k))}},get isCancelled(){return c.signal.aborted},get isExpired(){return pe.has(s.taskId)},hasStream:!!s.hasStream,cancelSignal:c.signal,taskClient:ee,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(n(t));if(!t.channel||!t.fileId||!t.fileName)throw new Error("File artifactRef missing channel, fileId, or fileName");if(!r.downloadFile)throw new Error("PubNub client does not support downloadFile");const a=await r.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,n=t?.mimeType??"application/octet-stream",o=B?{baseUrl:B,authProvider:l?new h(l):void 0,agentAuth:D}:void 0;await R(r,s.taskId,d,u,a,n,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 n=t.card.streams;if(!n||0===Object.keys(n).length)throw new Error("Agent card has no streams block. Streaming requires declared streams in the card.");const i=Object.keys(n);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=n[c];if(!l)throw new Error(`Stream "${c}" is not declared in the agent card. Available streams: ${i.join(", ")}`);const k=a?.direction??l.direction,g=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 y=a?.external??!1,h=a?.metadata,b=a?.onActivate,I=l.affinity??"dedicated";if("shared"===I&&y)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"===I)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"!==k)throw new Error("Request tasks only support outbound streams");if(y)throw new Error("Request tasks cannot use external streams")}let T;if("shared"===I)T=c;else{const e=(he.get(s.taskId)??0)+1;he.set(s.taskId,e),T=`${s.taskId}-${e}`}const C=o.streamChannel(T);if("shared"===I){const e=ce.get(T)?.get(s.taskId);if(e)return e}const{entry:$,isNew:K,isNewForTask:_}=ie.acquire(T,s.taskId,{direction:k,format:g,external:y,affinity:I});de.addStream(s.taskId,T);const A=e=>{if("shared"!==I)return;let t=ce.get(T);t||(t=new Map,ce.set(T,t)),t.set(s.taskId,e)};if(!_){$.setupPromise&&await $.setupPromise;const e=ce.get(T)?.get(s.taskId);if(e)return e;if($.streamClient){const e=w(T,$.streamClient,s.taskId,{releaseStream:Te});return A(e),e}throw new Error(`Stream "${T}" 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"===I&&await(async()=>{await P(r,{taskId:s.taskId,orgId:d,agentName:u,streamId:T,channel:C,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:I,phase:"activate",metadata:h,declaredStream:c})})(),$.streamClient){const e=w(T,$.streamClient,s.taskId,{releaseStream:Te});return A(e),e}if(y)return v(T,C,"",async()=>{throw new Error("External stream already activated")});throw new Error(`Stream "${T}" exists but has no client`)}catch(e){ve(T,s.taskId);try{await ie.release(T,s.taskId)}catch{}throw e}let E,x;$.setupPromise=new Promise((e,t)=>{E=e,x=t}),$.setupPromise.catch(()=>{});const M=y?"token_request":"embedded";let N;try{N=await P(r,{taskId:s.taskId,orgId:d,agentName:u,streamId:T,channel:C,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:I,phase:M,metadata:h,declaredStream:c})}catch(e){x(e),ve(T,s.taskId);try{await ie.release(T,s.taskId)}catch{}throw e}if(y){E(),$.setupPromise=null;const e=N.token??"";return v(T,C,e,async e=>{await P(r,{taskId:s.taskId,orgId:d,agentName:u,streamId:T,channel:C,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:I,phase:"activate",metadata:e?.metadata??h,declaredStream:c})})}const R=N.token;if(!R){const e=new Error("No T7a token received from stream setup handshake");x(e),ve(T,s.taskId);try{await ie.release(T,s.taskId)}catch{}throw e}const j=new e({subscribeKey:J,publishKey:Q,token:R,agentName:u,streamId:T,channel:C,format:g,direction:k,affinity:I,gating:m,bundleSizeBytes:a?.bundleSizeBytes,maxLatencyMs:a?.maxLatencyMs});$.streamClient=j;const U=w(T,j,s.taskId,{releaseStream:Te});if(A(U),E(),$.setupPromise=null,"outbound"===k||"bidirectional"===k){const e=a?.subscribeGraceMs??1e3;e>0&&await new Promise(t=>setTimeout(t,e))}return b&&($.activated=!0,$.activatePromise=S(T,U,b,Ce)),U}};let T;try{T=t.handler?await t.handler(s,I):void 0}finally{he.delete(s.taskId),y&&(clearTimeout(y),y=null),b()}const C=B?{baseUrl:B,authProvider:l?new h(l):void 0,agentAuth:D}:void 0;if(T?.artifacts&&T.artifacts.length>0)for(const e of T.artifacts){const t="string"==typeof e.data?Buffer.from(e.data,"utf-8"):e.data;await R(r,s.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,C)}const $=c.signal.aborted;if(m){const e=pe.has(s.taskId),t=ke.has(s.taskId);if($||e||t){await Se(s.taskId);const a=$&&!e?"canceled":"completed";await M(r,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),de.remove(s.taskId),N("info",`Task ${s.taskId} ${a}`,{taskId:s.taskId,agentName:u,owner:i})}else N("info",`Task ${s.taskId} handler returned (pipe, no auto-terminal)`,{taskId:s.taskId,agentName:u,owner:i})}else{const e=ke.has(s.taskId);await Se(s.taskId);const t=$?"canceled":"completed";await M(r,s.taskId,d,u,{type:"terminal",taskId:s.taskId,state:t,...e?{reason:"terminated"}:{}}),de.remove(s.taskId),N("info",`Task ${s.taskId} ${t}`,{taskId:s.taskId,agentName:u,owner:i})}},_e=(()=>{let e=!1;return(t,a,n)=>{if(N("debug","pamDeniedHandler invoked",{event:"pam_handler_invoked",category:t,operation:a,statusCode:n,alreadyHandled:e,instanceId:c}),"PNAccessDeniedCategory"===t&&!e){e=!0,N("error","PAM access denied — destroying control client (agent will go silent)",{event:"pam_denied_destroy",category:t,operation:a,statusCode:n,instanceId:c,controlChannel:Ie}),N("error","PAM token expired or revoked — agent is no longer receiving tasks. Re-register the agent to resume.",{event:"pam_denied_user_message",instanceId:c});try{W.destroy()}catch{}}}})(),Ae={status:e=>{const t=e,a=String(t.category??""),n=String(t.operation??""),s="number"==typeof t.statusCode?t.statusCode:null;"PNConnectedCategory"===a&&N("debug","pubnub control client connected",{event:"control_connected",controlChannel:Ie}),_e(a,n,s)},message:e=>{const n=e.message;if(!n)return;if("SwitchEnvironment"===n.type){const e=n,t=e.environment;return void(t&&t!==U&&t in f&&((e,t)=>{if(N("info",`SwitchEnvironment requested: ${U} -> ${e}`,{pamToken:t?"present":"absent"}),!t)return void N("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=W,n=Z;try{a.removeListener(Ae),Ie&&a.unsubscribe({channels:[Ie]})}catch{}Y(a);const r=f[e];if(W=s({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0,onRetry:e=>{N("warn","pubnub transport retrying",{event:"pubnub_transport_retry",message:e,instanceId:c})}}),Z=!0,be=void 0,W.setToken(t),0!==ae&&"function"==typeof W.setFilterExpression&&W.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),W.addListener(Ae),G("control",W),Ie&&W.subscribe({channels:[Ie]}),n)try{a.destroy()}catch{}U=e,J=r.subscribeKey,Q=r.publishKey,ee.updateKeys(r.subscribeKey,r.publishKey),N("info",`Switched to ${e} environment`),we().catch(()=>{})})(t,e.pamToken))}(async(e,n)=>{if("StartTask"===e.type){const r=U,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(ye.has(e.taskId))return void N("warn",`Ignoring duplicate StartTask for in-flight task ${e.taskId}`,{taskId:e.taskId});const d=Boolean(n?.broadcast);if(e.protocolVersion&&!x(e.protocolVersion))return d?void N("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await M(W,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void N("warn",`Task ${e.taskId} rejected: unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId,instanceId:c}));if("pipe"===(e.taskKind||"request")){const t=e.duration,n=e.durationExpiresAtMs;if(null==t||!Number.isInteger(t)||t<1||t>43200||"number"!=typeof n||n<=0)return N("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${n})`,{taskId:e.taskId,instanceId:c}),void await M(W,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=oe+ie.activeStreamCount;if(te>0&&l>=te)return d?void N("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${te})`,{taskId:e.taskId,instanceId:c}):(await M(W,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void N("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:oe}));le.set(e.taskId,o),ue.set(e.taskId,i),ye.add(e.taskId),oe++,e.controlToken&&(be=e.controlToken);const u=new AbortController;fe.set(e.taskId,u);let m=null;try{const n=f[r];if(!n.publishKey||!n.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:n.publishKey,subscribeKey:n.subscribeKey,userId:c,subscribeRetryUnbounded:!1}),e.writeToken&&m.setToken(e.writeToken),G(`task-sub:${e.taskId}`,m),de.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:r}),await we();const{writeToken:d,controlToken:l,...p}=e;t.onStartTask?await t.onStartTask(e,m):await Ke(p,m,o,i,u)}catch(n){const s=n?.message??"Agent instance error",r=m??W;try{await Se(e.taskId)}catch{}await M(r,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:s}),t.onError&&await t.onError(e.taskId,n),N("error",s,{taskId:e.taskId}),de.remove(e.taskId)}finally{const t=ge.get(e.taskId);if(t&&(clearTimeout(t),ge.delete(e.taskId)),ye.delete(e.taskId),le.delete(e.taskId),ue.delete(e.taskId),me.delete(e.taskId),fe.delete(e.taskId),pe.delete(e.taskId),ke.delete(e.taskId),m){Y(m);try{m.destroy()}catch{}}oe--,0===oe&&be&&W.setToken(be),await we()}}else if("CancelTask"===e.type){const t=fe.get(e.taskId);t?(t.abort(),N("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):de.get(e.taskId)&&await $e(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)fe.has(e.taskId)?(pe.add(e.taskId),fe.get(e.taskId)?.abort(),N("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):de.get(e.taskId)&&await $e(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)fe.has(e.taskId)?(ke.add(e.taskId),fe.get(e.taskId)?.abort()):de.get(e.taskId)&&await $e(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if(ye.has(e.taskId)){const t=ue.get(e.taskId)??"anonymous";await M(W,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if(ye.has(e.taskId)){const t=ue.get(e.taskId)??"anonymous";await M(W,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&N("info","RetryTask received (no-op)",{taskId:e.taskId})})(n,e.userMetadata&&"object"==typeof e.userMetadata?e.userMetadata:void 0).catch(e=>{N("error","unhandled error in message handler",{event:"message_handler_error",error:e instanceof Error?e.message:String(e),stack:e instanceof Error?e.stack:void 0})})}};W.addListener(Ae),G("control",W),H&&null===z&&(z=setInterval(()=>{const e=Date.now();let t=!1;const a=O.map(a=>{const n=null!==a.lastStatusAt?e-a.lastStatusAt:null,s=_isDiagEntryStale({lastStatusAt:a.lastStatusAt,lastCategory:a.lastCategory,now:e,thresholdMs:6e4});s&&(t=!0);const r="function"==typeof a.pn.getSubscribedChannels?a.pn.getSubscribedChannels().length:null;return{client:a.label,lastCategory:a.lastCategory,lastOperation:a.lastOperation,lastStatusCode:a.lastStatusCode,msSinceStatus:n,msSinceConnected:null!==a.lastConnectedAt?e-a.lastConnectedAt:null,msSinceMessage:null!==a.lastMessageAt?e-a.lastMessageAt:null,subscribedChannels:r,stale:s}}),n={event:"pubnub_alive_snapshot",instanceId:c,clients:a};t?N("info","pubnub alive snapshot — STALE clients present",n):N("debug","pubnub alive snapshot",n)},1e4),"function"==typeof z.unref&&z.unref()),H&&N("info","pubnub diagnostics armed",{event:"pubnub_diagnostics_armed",snapshotIntervalMs:1e4,staleThresholdMs:6e4,instanceId:c}),i(a,{instanceId:c,billingMode:L,description:t.description,scaling:{expectedInstances:ae,concurrency:te,maxPendingBacklog:ne,maxRunningTimeSec:se},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:F??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??y?.api.baseUrl,agentAuth:D}).then(e=>{if(N("info",`registered agent: ${a} (instance: ${c})`,{event:"agent_registered",agentName:a,instanceId:c}),!e.controlChannel)throw new Error("Connect response missing controlChannel — server may be outdated");Ie=e.controlChannel,e.pamToken&&W.setToken(e.pamToken),0!==ae&&"function"==typeof W.setFilterExpression&&W.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),W.subscribe({channels:[Ie]}),we().catch(()=>{})}).catch(e=>{const t=e instanceof Error?e.message:String(e);N("error",`failed to register agent: ${a} — ${t}`,{event:"agent_registration_failed",agentName:a,instanceId:c,error:t}),e instanceof _&&process.exit(1)}),N("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{null!==z&&(clearInterval(z),z=null);for(const e of O)try{e.pn.removeListener(e.listener)}catch{}O.length=0,X?.destroy(),ee.destroy(),W.removeListener(Ae),Ie&&W.unsubscribe({channels:[Ie]}),Z&&W.destroy();for(const e of ie.streamIds()){const t=ie.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}ie.clear(),ce.clear(),de.clear()}catch{}},agentName:a,instanceId:c,get controlChannel(){return Ie},get pubnub(){return W},get subscribeKey(){return f[U].subscribeKey},taskClient:ee,publishTerminal:$e,failStream:Ce,cdmConfig:y}};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 s}from"./artifacts.js";import{createPubNubClient as n}from"./pubnub-client.js";import{createChannelManager as r}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{log as f,isDebugSubsystemEnabled as p,_resolveLogLevel as k,_LOG_LEVEL_ORDER as g}from"./logger.js";import{mapTransportCategory as h,mapTransportOperation as y,isAccessDeniedStatus as I,DEGRADED_TRANSPORT_CATEGORIES as b,RESTORED_TRANSPORT_CATEGORIES as w}from"./transport-categories.js";import{uploadFile as v}from"./file-upload.js";import{StaticAuthProvider as S}from"./auth-provider.js";import{StreamRegistry as T}from"./stream-registry.js";import{CredentialCache as C}from"./credential-cache.js";import{createStreamObject as $,createExternalStreamObject as _,runOnActivate as K}from"./stream-context.js";import{TaskClient as A}from"./task-client.js";import{ConsumerAuth as E}from"./consumer-auth.js";import{fetchCdmConfig as x}from"./cdm-config.js";import{AgentAuth as M,AgentAuthFatalError as N}from"./agent-auth.js";import{CURRENT_PROTOCOL_VERSION as R,SUPPORTED_PROTOCOL_VERSIONS as j,isProtocolVersionSupported as P}from"./protocol-version.js";const U=async(e,t,a,s,n,i=R)=>{const d=r(s);try{await e.publish({channel:d.taskChannel(t,a),message:o({...n,protocolVersion:i}),storeInHistory:!0,sendByPost:!0,meta:o({agentName:s,taskId:t,protocolVersion:i})})}catch{}};export function _isDiagEntryStale(e){const{lastStatusAt:t,lastCategory:a,now:s,thresholdMs:n}=e;return null!==t&&("connected"!==a&&s-t>n)}const q=(e,t,a)=>f("[AgentInstance]",e,t,a);export function resolveMaxRunningTimeSec(e,t){return void 0!==e&&void 0!==t&&e!==t&&f("[AgentInstance]","info",`opts.maxRunningTimeSec (${e}) overrides card.runtime.maxRunningTimeSec (${t})`,{event:"max_running_time_override",optsValue:e,cardValue:t}),e??t}export function computeStreamDurationMinutes(e,t,a){return"number"==typeof e?e:t?60:Math.ceil((a??3600)/60)}const B=async(e,s,n,r,o,i,d,c,l)=>{const u={type:"artifact",taskId:s};if(c&&(u.outputId=c),a(o.length))await U(e,s,n,r,{...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 v(l,{role:"provider-output",taskId:s,fileName:d??`${s}-artifact`,fileSize:o.length,mimeType:i,outputId:c},o)}};async function D(e,t){const a=`setup.${t.orgId}.${t.taskId}`,s={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:R};t.phase&&(s.phase=t.phase),t.metadata&&(s.metadata=t.metadata);try{throw await e.publish({channel:a,message:o(s),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=r(a),c=t.instanceId??`AG-${a}-${crypto.randomUUID()}`,l=t.token;let f,v=null,L="playground",V=L;if(t.pubnub){const e=t.pubnub._configuration?.keySet,a={publishKey:e?.publishKey??"",subscribeKey:e?.subscribeKey??""};f={playground:a,network:a}}else v=await x(t.cdmUrl),f={playground:v.playground,network:v.network},q("info","CDM config loaded — environment switching enabled",{event:"cdm_config_loaded"});const F=m("BLOCKS_API_KEY");if(!F)throw new Error("BLOCKS_API_KEY is required. Run 'blocks login --write-env' to set up credentials.");let O;const z=t.baseUrl??v?.api.baseUrl;let H,G;if(F&&z&&(O=new M(F,z),q("info","AgentAuth created — will authenticate via connect",{event:"agent_auth_created"})),v){const e=t.baseUrl??v.api.baseUrl,s=await d(a,{baseUrl:e,apiKey:F});if(!s)throw new Error(`[AgentInstance] Agent "${a}" not found in registry. Register the agent (e.g. via \`blocks publish\`) before starting an instance.`);if(!s.billingMode)throw new Error(`[AgentInstance] Agent "${a}" registry entry is missing billingMode. Re-register the agent so the registry persists an explicit billingMode.`);H=s.billingMode,G=s.listing,L="paid"===H?"network":"playground",q("info",`registry billingMode: ${H} — using ${L} environment`,{event:"registry_billing_mode_resolved",billingMode:H,environment:L}),V=L}const Y=H??"free",W=[];let Z=null;const J=p("diagnostics"),Q=()=>{let e=!1;return{status:t=>{const a=h(t),s=b.has(a);s&&!e?(e=!0,q("warn",`connectivity degraded: ${a}`,{event:"transport_degraded",category:a,instanceId:c})):!s&&e&&w.has(a)&&(e=!1,q("info","connectivity restored",{event:"transport_restored",category:a,instanceId:c}))}}},X=(e,t)=>{if(!J)return;const a={label:e,pn:t,listener:void 0,lastStatusAt:null,lastConnectedAt:null,lastMessageAt:null,lastCategory:null,lastOperation:null,lastStatusCode:null};a.listener=(e=>({status:t=>{const a=t,s=h(a),n=y("string"==typeof a.operation?a.operation:void 0),r="number"==typeof a.statusCode?a.statusCode:null,o=Date.now(),i=e.lastCategory;e.lastStatusAt=o,e.lastCategory=s,e.lastOperation=n,e.lastStatusCode=r,"connected"===s&&(e.lastConnectedAt=o),q("debug","transport status event",{event:"transport_status",client:e.label,category:s,operation:n,statusCode:r,error:Boolean(a.error),affectedChannels:Array.isArray(a.affectedChannels)?a.affectedChannels.length:void 0,instanceId:c}),s&&s!==i&&q("info",`transport status transition [${e.label}]: ${i??"(none)"} -> ${s}`,{event:"transport_status_transition",client:e.label,from:i,to:s,operation:n,statusCode:r,instanceId:c})},message:()=>{e.lastMessageAt=Date.now()}}))(a),W.push(a),t.addListener(a.listener)},ee=e=>{if(!J)return;const t=W.findIndex(t=>t.pn===e);if(t<0)return;const a=W[t];try{e.removeListener(a.listener)}catch{}W.splice(t,1)};let te,ae=!1;if(t.pubnub)te=t.pubnub,ae=!1,l&&te.setToken(l);else{const e=f[V];te=n({publishKey:e.publishKey,subscribeKey:e.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0}),ae=!0,l&&te.setToken(l)}let se=f[V].subscribeKey,ne=f[V].publishKey;const re=z?new E({apiKey:F,baseUrl:z}):void 0,oe=new A({billingMode:Y,subscribeKey:se,publishKey:ne,authProvider:re,baseUrl:z,createPubNub:()=>{const e=n({publishKey:ne,subscribeKey:se,userId:`${c}-taskclient`,subscribeRetryUnbounded:!1});return X("consumer-task",e),e}}),ie=t.concurrency??u.concurrency,de=t.expectedInstances??u.expectedInstances,ce=t.maxPendingBacklog,le=resolveMaxRunningTimeSec(t.maxRunningTimeSec,t.card?.runtime?.maxRunningTimeSec),ue=Date.now();let me=0;const fe=new T,pe=new C,ke=new Map,ge=new Map,he=new Map,ye=new Map,Ie=new Map,be=new Set,we=new Set,ve=new Map,Se=new Set,Te=new Map;let Ce,$e;t.logChannel;const _e=async()=>{if("function"==typeof te.setState&&$e)try{await te.setState({channels:[$e],state:{instanceId:c,activeTasks:me,activeStreams:fe.activeStreamCount,concurrency:ie,startedAt:ue,preferredProtocolVersion:R,protocolVersions:[...j]}})}catch{}},Ke=(e,t)=>{const a=ke.get(e);a&&(a.delete(t),0===a.size&&ke.delete(e))},Ae=async e=>{(e=>{for(const[t,a]of ke)a.has(e)&&(a.delete(e),0===a.size&&ke.delete(t))})(e);const t=fe.releaseAllForTask(e);for(const e of t)if(e.streamClient)try{await e.streamClient.end()}catch{}},Ee=async(e,t)=>{Ke(e,t);const a=fe.get(e);if(0===fe.release(e,t)&&a?.streamClient){q("info","last-ref teardown",{event:"stream_registry_last_ref_teardown",streamId:e,affinity:a.affinity,releasingTaskId:t});try{await a.streamClient.end()}catch{}}_e().catch(()=>{})},xe=async(e,t)=>{const a=fe.forceRemove(e);if((e=>{ke.delete(e)})(e),a){if(a.streamClient)try{await a.streamClient.end()}catch{}for(const e of a.taskIds){const a=pe.get(e);if(a){try{const s=f[a.environment??L]??f[L],r=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken),await U(r,e,a.orgId,a.agentName,{type:"terminal",taskId:e,state:"failed",error:t}),r.destroy()}catch{}pe.remove(e)}}await _e()}},Me=async(e,t)=>{const a=pe.get(e);if(!a)throw new Error(`No cached credentials for task ${e}`);const s=f[a.environment??L]??f[L],r=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1});r.setToken(a.writeToken);try{await U(r,e,a.orgId,a.agentName,{...t,type:"terminal",taskId:e})}finally{r.destroy()}await Ae(e),pe.remove(e),await _e()},Ne=async(n,r,i,d,c)=>{const u=a,m="pipe"===n.taskKind,f=m?"pipe":"request",p=computeStreamDurationMinutes(n.duration,m,le);if(m&&"number"==typeof n.durationExpiresAtMs){const e=Math.max(0,n.durationExpiresAtMs-Date.now());if(e>0){const t=setTimeout(()=>{be.add(n.taskId),c.abort()},e);ve.set(n.taskId,t)}}await U(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,progress:0,state:"running"}),Te.set(n.taskId,0);let k=0,g=null,h=null;const y=()=>{if(null===g)return;const e=g;g=null,k=Date.now(),U(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,message:e}).catch(()=>{})},I={taskId:n.taskId,requestParts:n.requestParts??[],reportStatus:e=>{ye.set(n.taskId,e);const t=Date.now();if(t-k>=1e3)g=null,k=t,U(r,n.taskId,d,u,{type:"progress",taskId:n.taskId,message:e}).catch(()=>{});else if(g=e,!h){h=setTimeout(()=>{h=null,y()},1e3-(t-k))}},get isCancelled(){return c.signal.aborted},get isExpired(){return be.has(n.taskId)},hasStream:!!n.hasStream,cancelSignal:c.signal,taskClient:oe,consumerPublicKey:n.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(s(t));if(!t.channel||!t.fileId||!t.fileName)throw new Error("File artifactRef missing channel, fileId, or fileName");if(!r.downloadFile)throw new Error("PubNub client does not support downloadFile");const a=await r.downloadFile({channel:t.channel,id:t.fileId,name:t.fileName}),n=a?.data;if(Buffer.isBuffer(n))return n;if(n instanceof Uint8Array)return Buffer.from(n);if(n&&"function"==typeof n.arrayBuffer){const e=await n.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,s=t?.mimeType??"application/octet-stream",o=z?{baseUrl:z,authProvider:l?new S(l):void 0,agentAuth:O}:void 0;await B(r,n.taskId,d,u,a,s,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(!n.hasStream)throw new Error("Streaming was not negotiated for this task. Ensure the agent card is registered with streaming capability.");const s=t.card.streams;if(!s||0===Object.keys(s).length)throw new Error("Agent card has no streams block. Streaming requires declared streams in the card.");const i=Object.keys(s);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=s[c];if(!l)throw new Error(`Stream "${c}" is not declared in the agent card. Available streams: ${i.join(", ")}`);const k=a?.direction??l.direction,g=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 h=a?.external??!1,y=a?.metadata,I=a?.onActivate,b=l.affinity??"dedicated";if("shared"===b&&h)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"===b)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"!==k)throw new Error("Request tasks only support outbound streams");if(h)throw new Error("Request tasks cannot use external streams")}let w;if("shared"===b)w=c;else{const e=(Te.get(n.taskId)??0)+1;Te.set(n.taskId,e),w=`${n.taskId}-${e}`}const v=o.streamChannel(w);if("shared"===b){const e=ke.get(w)?.get(n.taskId);if(e)return e}const{entry:S,isNew:T,isNewForTask:C}=fe.acquire(w,n.taskId,{direction:k,format:g,external:h,affinity:b});pe.addStream(n.taskId,w);const A=e=>{if("shared"!==b)return;let t=ke.get(w);t||(t=new Map,ke.set(w,t)),t.set(n.taskId,e)};if(!C){S.setupPromise&&await S.setupPromise;const e=ke.get(w)?.get(n.taskId);if(e)return e;if(S.streamClient){const e=$(w,S.streamClient,n.taskId,{releaseStream:Ee});return A(e),e}throw new Error(`Stream "${w}" 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(!T)try{if(S.setupPromise&&await S.setupPromise,"shared"===b&&await(async()=>{await D(r,{taskId:n.taskId,orgId:d,agentName:u,streamId:w,channel:v,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:b,phase:"activate",metadata:y,declaredStream:c})})(),S.streamClient){const e=$(w,S.streamClient,n.taskId,{releaseStream:Ee});return A(e),e}if(h)return _(w,v,"",async()=>{throw new Error("External stream already activated")});throw new Error(`Stream "${w}" exists but has no client`)}catch(e){Ke(w,n.taskId);try{await fe.release(w,n.taskId)}catch{}throw e}let E,x;S.setupPromise=new Promise((e,t)=>{E=e,x=t}),S.setupPromise.catch(()=>{});const M=h?"token_request":"embedded";let N;try{N=await D(r,{taskId:n.taskId,orgId:d,agentName:u,streamId:w,channel:v,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:b,phase:M,metadata:y,declaredStream:c})}catch(e){x(e),Ke(w,n.taskId);try{await fe.release(w,n.taskId)}catch{}throw e}if(h){E(),S.setupPromise=null;const e=N.token??"";return _(w,v,e,async e=>{await D(r,{taskId:n.taskId,orgId:d,agentName:u,streamId:w,channel:v,direction:k,format:g,taskKind:f,durationMinutes:p,affinity:b,phase:"activate",metadata:e?.metadata??y,declaredStream:c})})}const R=N.token;if(!R){const e=new Error("No T7a token received from stream setup handshake");x(e),Ke(w,n.taskId);try{await fe.release(w,n.taskId)}catch{}throw e}const j=new e({subscribeKey:se,publishKey:ne,token:R,agentName:u,streamId:w,channel:v,format:g,direction:k,affinity:b,gating:m,bundleSizeBytes:a?.bundleSizeBytes,maxLatencyMs:a?.maxLatencyMs});S.streamClient=j;const P=$(w,j,n.taskId,{releaseStream:Ee});if(A(P),E(),S.setupPromise=null,"outbound"===k||"bidirectional"===k){const e=a?.subscribeGraceMs??1e3;e>0&&await new Promise(t=>setTimeout(t,e))}return I&&(S.activated=!0,S.activatePromise=K(w,P,I,xe)),P}};let b;try{b=t.handler?await t.handler(n,I):void 0}finally{Te.delete(n.taskId),h&&(clearTimeout(h),h=null),y()}const w=z?{baseUrl:z,authProvider:l?new S(l):void 0,agentAuth:O}:void 0;if(b?.artifacts&&b.artifacts.length>0)for(const e of b.artifacts){const t="string"==typeof e.data?Buffer.from(e.data,"utf-8"):e.data;await B(r,n.taskId,d,u,t,e.mimeType,e.fileName,e.outputId,w)}const v=c.signal.aborted;if(m){const e=be.has(n.taskId),t=we.has(n.taskId);if(v||e||t){await Ae(n.taskId);const a=v&&!e?"canceled":"completed";await U(r,n.taskId,d,u,{type:"terminal",taskId:n.taskId,state:a,...e?{completionReason:"duration_expired"}:{},...t?{reason:"terminated"}:{}}),pe.remove(n.taskId),q("info",`Task ${n.taskId} ${a}`,{taskId:n.taskId,agentName:u,owner:i})}else q("info",`Task ${n.taskId} handler returned (pipe, no auto-terminal)`,{taskId:n.taskId,agentName:u,owner:i})}else{const e=we.has(n.taskId);await Ae(n.taskId);const t=v?"canceled":"completed";await U(r,n.taskId,d,u,{type:"terminal",taskId:n.taskId,state:t,...e?{reason:"terminated"}:{}}),pe.remove(n.taskId),q("info",`Task ${n.taskId} ${t}`,{taskId:n.taskId,agentName:u,owner:i})}},Re=(()=>{let e=!1;return(t,a)=>{const s=h(t),n="number"==typeof t.statusCode?t.statusCode:null;if(q("debug","access-denied handler invoked",{event:"access_denied_handler_invoked",category:s,operation:y(a),statusCode:n,alreadyHandled:e,instanceId:c}),I(t)&&!e){e=!0,q("error","access denied — destroying control client (agent will go silent)",{event:"access_denied_destroy",category:s,operation:y(a),statusCode:n,instanceId:c,controlChannel:$e}),q("error","access token expired or revoked — agent is no longer receiving tasks. Re-register the agent to resume.",{event:"access_denied_user_message",instanceId:c});try{te.destroy()}catch{}}}})();let je=Q();const Pe={status:e=>{const t=e,a=String(t.category??""),s=String(t.operation??"");"PNConnectedCategory"===a&&q("debug","control client connected",{event:"control_connected",controlChannel:$e}),Re(t,s)},message:e=>{const s=e.message;if(!s)return;if("SwitchEnvironment"===s.type){const e=s,t=e.environment;return void(t&&t!==V&&t in f&&((e,t)=>{if(q("info",`SwitchEnvironment requested: ${V} -> ${e}`,{pamToken:t?"present":"absent"}),!t)return void q("error","SwitchEnvironment rejected: pamToken is required but was absent. The backend must include a pamToken in SwitchEnvironment messages.");const a=te,s=ae;try{a.removeListener(Pe),a.removeListener(je),$e&&a.unsubscribe({channels:[$e]})}catch{}ee(a);const r=f[e];if(te=n({publishKey:r.publishKey,subscribeKey:r.subscribeKey,userId:c,presenceTimeout:20,announceSuccessfulHeartbeats:k()>=g.debug,subscribeRetryUnbounded:!0}),ae=!0,Ce=void 0,te.setToken(t),0!==de&&"function"==typeof te.setFilterExpression&&te.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),je=Q(),te.addListener(Pe),te.addListener(je),X("control",te),$e&&te.subscribe({channels:[$e]}),s)try{a.destroy()}catch{}V=e,se=r.subscribeKey,ne=r.publishKey,oe.updateKeys(r.subscribeKey,r.publishKey),q("info",`Switched to ${e} environment`),_e().catch(()=>{})})(t,e.pamToken))}(async(e,s)=>{if("StartTask"===e.type){const r=V,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(Se.has(e.taskId))return void q("warn",`Ignoring duplicate StartTask for in-flight task ${e.taskId}`,{taskId:e.taskId});const d=Boolean(s?.broadcast);if(e.protocolVersion&&!P(e.protocolVersion))return d?void q("debug",`Ignoring broadcast StartTask ${e.taskId} with unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId}):(await U(te,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"unsupported_protocol_version"},e.protocolVersion),void q("warn",`Task ${e.taskId} rejected: unsupported protocolVersion ${e.protocolVersion}`,{taskId:e.taskId,instanceId:c}));if("pipe"===(e.taskKind||"request")){const t=e.duration,s=e.durationExpiresAtMs;if(null==t||!Number.isInteger(t)||t<1||t>43200||"number"!=typeof s||s<=0)return q("error",`Task ${e.taskId} rejected: pipe StartTask with invalid duration (duration=${t}, durationExpiresAtMs=${s})`,{taskId:e.taskId,instanceId:c}),void await U(te,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"invalid_start_task"},e.protocolVersion)}const l=me+fe.activeStreamCount;if(ie>0&&l>=ie)return d?void q("debug",`Task ${e.taskId} skipped (broadcast, at capacity ${l}/${ie})`,{taskId:e.taskId,instanceId:c}):(await U(te,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:"agent_at_capacity"}),void q("warn",`Task ${e.taskId} rejected: agent at capacity`,{taskId:e.taskId,instanceId:c,activeTasks:me}));ge.set(e.taskId,o),he.set(e.taskId,i),Se.add(e.taskId),me++,e.controlToken&&(Ce=e.controlToken);const u=new AbortController;Ie.set(e.taskId,u);let m=null;try{const s=f[r];if(!s.publishKey||!s.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=n({publishKey:s.publishKey,subscribeKey:s.subscribeKey,userId:c,subscribeRetryUnbounded:!1}),e.writeToken&&m.setToken(e.writeToken),X(`task-sub:${e.taskId}`,m),pe.set(e.taskId,{ownerId:o,orgId:i,writeToken:e.writeToken??"",agentName:a,environment:r}),await _e();const{writeToken:d,controlToken:l,...p}=e;t.onStartTask?await t.onStartTask(e,m):await Ne(p,m,o,i,u)}catch(s){const n=s?.message??"Agent instance error",r=m??te;try{await Ae(e.taskId)}catch{}await U(r,e.taskId,i,a,{type:"terminal",taskId:e.taskId,state:"failed",error:n}),t.onError&&await t.onError(e.taskId,s),q("error",n,{taskId:e.taskId}),pe.remove(e.taskId)}finally{const t=ve.get(e.taskId);if(t&&(clearTimeout(t),ve.delete(e.taskId)),Se.delete(e.taskId),ge.delete(e.taskId),he.delete(e.taskId),ye.delete(e.taskId),Ie.delete(e.taskId),be.delete(e.taskId),we.delete(e.taskId),m){ee(m);try{m.destroy()}catch{}}me--,0===me&&Ce&&te.setToken(Ce),await _e()}}else if("CancelTask"===e.type){const t=Ie.get(e.taskId);t?(t.abort(),q("info",`Task ${e.taskId} cancel requested (cooperative)`,{taskId:e.taskId,instanceId:c})):pe.get(e.taskId)&&await Me(e.taskId,{state:"canceled"})}else if("ExpireTask"===e.type)Ie.has(e.taskId)?(be.add(e.taskId),Ie.get(e.taskId)?.abort(),q("info",`Task ${e.taskId} expired`,{taskId:e.taskId,instanceId:c})):pe.get(e.taskId)&&await Me(e.taskId,{state:"completed",completionReason:"duration_expired"});else if("TerminateTask"===e.type)Ie.has(e.taskId)?(we.add(e.taskId),Ie.get(e.taskId)?.abort()):pe.get(e.taskId)&&await Me(e.taskId,{state:"canceled",reason:"terminated"});else if("PauseTask"===e.type){if(Se.has(e.taskId)){const t=he.get(e.taskId)??"anonymous";await U(te,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"paused"})}}else if("ResumeTask"===e.type){if(Se.has(e.taskId)){const t=he.get(e.taskId)??"anonymous";await U(te,e.taskId,t,a,{type:"system",taskId:e.taskId,status:"resumed"})}}else"RetryTask"===e.type&&q("info","RetryTask received (no-op)",{taskId:e.taskId})})(s,e.userMetadata&&"object"==typeof e.userMetadata?e.userMetadata:void 0).catch(e=>{q("error","unhandled error in message handler",{event:"message_handler_error",error:e instanceof Error?e.message:String(e),stack:e instanceof Error?e.stack:void 0})})}};te.addListener(Pe),te.addListener(je),X("control",te),J&&null===Z&&(Z=setInterval(()=>{const e=Date.now();let t=!1;const a=W.map(a=>{const s=null!==a.lastStatusAt?e-a.lastStatusAt:null,n=_isDiagEntryStale({lastStatusAt:a.lastStatusAt,lastCategory:a.lastCategory,now:e,thresholdMs:6e4});n&&(t=!0);const r="function"==typeof a.pn.getSubscribedChannels?a.pn.getSubscribedChannels().length:null;return{client:a.label,lastCategory:a.lastCategory,lastOperation:a.lastOperation,lastStatusCode:a.lastStatusCode,msSinceStatus:s,msSinceConnected:null!==a.lastConnectedAt?e-a.lastConnectedAt:null,msSinceMessage:null!==a.lastMessageAt?e-a.lastMessageAt:null,subscribedChannels:r,stale:n}}),s={event:"transport_alive_snapshot",instanceId:c,clients:a};t?q("info","transport alive snapshot — STALE clients present",s):q("debug","transport alive snapshot",s)},1e4),"function"==typeof Z.unref&&Z.unref()),J&&q("info","transport diagnostics armed",{event:"transport_diagnostics_armed",snapshotIntervalMs:1e4,staleThresholdMs:6e4,instanceId:c}),i(a,{instanceId:c,billingMode:Y,description:t.description,scaling:{expectedInstances:de,concurrency:ie,maxPendingBacklog:ce,maxRunningTimeSec:le},card:t.card,cardRef:t.cardRef,cardSummary:t.cardSummary,listing:G??t.listing,actor:`agent-instance:${c}`,baseUrl:t.baseUrl??v?.api.baseUrl,agentAuth:O}).then(e=>{if(q("info",`registered agent: ${a} (instance: ${c})`,{event:"agent_registered",agentName:a,instanceId:c}),!e.controlChannel)throw new Error("Connect response missing controlChannel — server may be outdated");$e=e.controlChannel,e.pamToken&&te.setToken(e.pamToken),0!==de&&"function"==typeof te.setFilterExpression&&te.setFilterExpression(`meta.instance == '${c}' || meta.broadcast == "true"`),te.subscribe({channels:[$e]}),_e().catch(()=>{})}).catch(e=>{const t=e instanceof Error?e.message:String(e);q("error",`failed to register agent: ${a} — ${t}`,{event:"agent_registration_failed",agentName:a,instanceId:c,error:t}),e instanceof N&&process.exit(1)}),q("info",`Agent instance ${c} started (agent name: ${a})`,{agentName:a,event:"agent_instance_started",instanceId:c});return{stop:()=>{try{null!==Z&&(clearInterval(Z),Z=null);for(const e of W)try{e.pn.removeListener(e.listener)}catch{}W.length=0,J&&q("info","transport diagnostics disarmed",{event:"transport_diagnostics_disarmed",instanceId:c}),re?.destroy(),oe.destroy(),te.removeListener(Pe),te.removeListener(je),$e&&te.unsubscribe({channels:[$e]}),ae&&te.destroy();for(const e of fe.streamIds()){const t=fe.get(e);if(t?.streamClient)try{t.streamClient.end()}catch{}}fe.clear(),ke.clear(),pe.clear()}catch{}},agentName:a,instanceId:c,get controlChannel(){return $e},get pubnub(){return te},get subscribeKey(){return f[V].subscribeKey},taskClient:oe,publishTerminal:Me,failStream:xe,cdmConfig:v}};export const artifactUtils={buildArtifactRef:t,shouldInlineArtifact:a};
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Agent Registry - REST-based agent card storage and discovery.
|
|
3
3
|
*
|
|
4
4
|
* All registry operations (register, fetch, get, remove) use the server-side
|
|
5
|
-
* REST endpoints at /api/v1/registry/agents which handle
|
|
5
|
+
* REST endpoints at /api/v1/registry/agents which handle registry metadata operations,
|
|
6
6
|
* membership indexing, and audit events server-side.
|
|
7
7
|
*/
|
|
8
|
-
import { registryAllChannel,
|
|
8
|
+
import { registryAllChannel, registryTagChannel, registryVisibilityChannel, registryLogChannel, normalizeTagSlug } from './channel-manager.js';
|
|
9
9
|
import type { AgentAuth } from './agent-auth.js';
|
|
10
10
|
/**
|
|
11
11
|
* Scaling configuration for multi-instance support.
|
|
@@ -17,9 +17,9 @@ export interface AgentScaling {
|
|
|
17
17
|
maxRunningTimeSec?: number;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Tag object within an AgentCard.
|
|
21
21
|
*/
|
|
22
|
-
export interface
|
|
22
|
+
export interface AgentTag {
|
|
23
23
|
id: string;
|
|
24
24
|
name: string;
|
|
25
25
|
description?: string;
|
|
@@ -29,7 +29,7 @@ export interface AgentSkill {
|
|
|
29
29
|
* Agent card payload (input schema) — authored by developers in agent-card.json.
|
|
30
30
|
*
|
|
31
31
|
* Uses the 9-section structure: identity, capabilities, io, streams,
|
|
32
|
-
* security, services,
|
|
32
|
+
* security, services, tags, extensions, runtime.
|
|
33
33
|
*/
|
|
34
34
|
export interface AgentCard {
|
|
35
35
|
identity: {
|
|
@@ -89,7 +89,7 @@ export interface AgentCard {
|
|
|
89
89
|
services?: {
|
|
90
90
|
webhooks?: boolean;
|
|
91
91
|
};
|
|
92
|
-
|
|
92
|
+
tags: AgentTag[];
|
|
93
93
|
extensions?: Record<string, unknown>;
|
|
94
94
|
runtime?: {
|
|
95
95
|
handler?: string;
|
|
@@ -125,7 +125,7 @@ export interface OutputAgentCard {
|
|
|
125
125
|
streams?: AgentCard['streams'];
|
|
126
126
|
security?: AgentCard['security'];
|
|
127
127
|
services?: AgentCard['services'];
|
|
128
|
-
|
|
128
|
+
tags: AgentTag[];
|
|
129
129
|
extensions?: Record<string, unknown>;
|
|
130
130
|
}
|
|
131
131
|
/**
|
|
@@ -138,8 +138,8 @@ export interface AgentEntry {
|
|
|
138
138
|
displayName: string;
|
|
139
139
|
/** Human-readable description */
|
|
140
140
|
description?: string;
|
|
141
|
-
/**
|
|
142
|
-
|
|
141
|
+
/** Tag objects from agent card */
|
|
142
|
+
tags?: AgentTag[];
|
|
143
143
|
/** Multi-instance scaling configuration */
|
|
144
144
|
scaling?: AgentScaling;
|
|
145
145
|
/** Full agent card payload */
|
|
@@ -171,8 +171,8 @@ export interface ConnectAgentOptions {
|
|
|
171
171
|
billingMode: 'free' | 'paid';
|
|
172
172
|
/** Human-readable description */
|
|
173
173
|
description?: string;
|
|
174
|
-
/**
|
|
175
|
-
|
|
174
|
+
/** Tag objects advertised by this agent */
|
|
175
|
+
tags?: AgentTag[];
|
|
176
176
|
/** Multi-instance scaling configuration */
|
|
177
177
|
scaling?: AgentScaling;
|
|
178
178
|
/** Full agent card payload */
|
|
@@ -208,7 +208,7 @@ export interface RegistryAuditEvent {
|
|
|
208
208
|
source: string;
|
|
209
209
|
agentName?: string;
|
|
210
210
|
visibility?: 'public' | 'private';
|
|
211
|
-
|
|
211
|
+
tags?: string[];
|
|
212
212
|
cardHash?: string;
|
|
213
213
|
cardRef?: string;
|
|
214
214
|
summaryHash?: string;
|
|
@@ -232,7 +232,7 @@ declare function detectDeviceOs(): string;
|
|
|
232
232
|
* Register or update an agent in the registry via REST.
|
|
233
233
|
*
|
|
234
234
|
* Sends a POST request to the dedicated registry endpoint which handles
|
|
235
|
-
* all
|
|
235
|
+
* all registry metadata operations (UUID metadata, memberships, audit events)
|
|
236
236
|
* server-side.
|
|
237
237
|
*/
|
|
238
238
|
export declare function connectAgent(agentName: string, options: ConnectAgentOptions): Promise<{
|
|
@@ -261,9 +261,9 @@ export declare function fetchAgentRegistry(options?: {
|
|
|
261
261
|
baseUrl?: string;
|
|
262
262
|
}): Promise<AgentRegistryResult>;
|
|
263
263
|
/**
|
|
264
|
-
* Fetch agents filtered by
|
|
264
|
+
* Fetch agents filtered by tag.
|
|
265
265
|
*/
|
|
266
|
-
export declare function
|
|
266
|
+
export declare function fetchAgentsByTag(tag: string, options?: {
|
|
267
267
|
limit?: number;
|
|
268
268
|
cursor?: string;
|
|
269
269
|
baseUrl?: string;
|
|
@@ -283,4 +283,4 @@ export declare function removeAgent(agentName: string, options?: {
|
|
|
283
283
|
baseUrl?: string;
|
|
284
284
|
agentAuth?: AgentAuth;
|
|
285
285
|
}): Promise<boolean>;
|
|
286
|
-
export { detectDeviceOs, registryAllChannel,
|
|
286
|
+
export { detectDeviceOs, registryAllChannel, registryTagChannel, registryVisibilityChannel, registryLogChannel, normalizeTagSlug, };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"node:os";import{registryAllChannel as n,
|
|
1
|
+
import t from"node:os";import{registryAllChannel as n,registryTagChannel as e,registryVisibilityChannel as a,registryLogChannel as r,normalizeTagSlug as o}from"./channel-manager.js";import{getEnv as i}from"../env.js";import{CURRENT_PROTOCOL_VERSION as s,SUPPORTED_PROTOCOL_VERSIONS as c,SDK_VERSION as l,PROTOCOL_VERSION_HEADER as u}from"./protocol-version.js";import{captureAffinity as g,injectAffinity as d}from"./write-affinity.js";function m(){try{return t.platform()}catch{return"unknown"}}async function f(t,n=3,e=500){let a;for(let r=0;r<n;r++)try{return await t()}catch(t){a=t;const o=String(t);if(!(o.includes("ENOTFOUND")||o.includes("ETIMEDOUT")||o.includes("ECONNRESET")||o.includes("NetworkIssues"))||r===n-1)throw t;const i=e*Math.pow(2,r)+100*Math.random();await new Promise(t=>setTimeout(t,i))}throw a}function h(t,n){let e=`${function(t){if(t)return t.replace(/\/+$/,"");throw new Error("[AgentRegistry] baseUrl is required: provide it via options or CDM config")}(n)}/api/v1/registry/agents`;if(t){const n=new URLSearchParams;for(const[e,a]of Object.entries(t))void 0!==a&&""!==a&&n.set(e,a);const a=n.toString();a&&(e+="?"+a)}return e}async function p(t,n){const e=new Headers(n?.headers);e.has(u)||e.set(u,s);const a={};d(a);for(const[t,n]of Object.entries(a))e.set(t,n);const r=await fetch(t,{...n,headers:e});if(g(r.headers),404===r.status)return null;if(!r.ok){const t=(await r.json().catch(()=>({}))).message??`HTTP ${r.status}`;throw new Error(`[AgentRegistry] Request failed: ${t}`)}return{data:await r.json(),status:r.status}}function y(t){return{agentName:t.agentName,displayName:t.name??t.agentName,description:t.description,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,n){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.")}(n.card);h(void 0,n.baseUrl);const e=i("BLOCKS_CLI_VERSION"),a={agentName:t,instanceId:n.instanceId,billingMode:n.billingMode,listing:n.listing,expectedInstances:n.scaling?.expectedInstances,concurrency:n.scaling?.concurrency,maxPendingBacklog:n.scaling?.maxPendingBacklog,maxRunningTimeSec:n.scaling?.maxRunningTimeSec,deviceOs:m(),sdkLanguage:"Node",sdkVersion:l,protocolVersions:[...c],preferredProtocolVersion:s};let r;if(e&&(a.cliVersion=e),!n.agentAuth)throw new Error("[AgentRegistry] agentAuth is required. Set BLOCKS_API_KEY and pass agentAuth to connectAgent.");const o=await f(async()=>n.agentAuth.init(a));return r=o.pamToken??void 0,{pamToken:r,agentId:o.agentId,controlChannel:o.controlChannel}}export async function getAgent(t,n){const e=h({agentName:t},n?.baseUrl),a=n?.apiKey?{headers:{Authorization:`Bearer ${n.apiKey}`}}:void 0,r=await f(async()=>p(e,a));return r?y(r.data.agent):null}export async function fetchAgentRegistry(t){const n={include:"full"};t?.limit&&(n.limit=String(t.limit)),t?.cursor&&(n.cursor=t.cursor);const e=h(n,t?.baseUrl),a=await f(async()=>p(e));return a?{agents:a.data.agents.map(y),next:a.data.next??void 0,totalCount:a.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsByTag(t,n){const e={include:"full",tag:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function fetchAgentsByListing(t,n){const e={include:"full",listing:t};n?.limit&&(e.limit=String(n.limit)),n?.cursor&&(e.cursor=n.cursor);const a=h(e,n?.baseUrl),r=await f(async()=>p(a));return r?{agents:r.data.agents.map(y),next:r.data.next??void 0,totalCount:r.data.totalCount}:{agents:[],totalCount:0}}export async function removeAgent(t,n){const e=h({agentName:t},n?.baseUrl);if(n?.agentAuth){return 404!==(await f(()=>n.agentAuth.authenticatedFetch(e,{method:"DELETE"}))).status}const a=i("BLOCKS_API_KEY");return null!==await f(async()=>p(e,{method:"DELETE",headers:{...a?{Authorization:`Bearer ${a}`}:{}}}))}export{m as detectDeviceOs,n as registryAllChannel,e as registryTagChannel,a as registryVisibilityChannel,r as registryLogChannel,o as normalizeTagSlug};
|
|
@@ -33,7 +33,37 @@ export interface AuthProvider {
|
|
|
33
33
|
* calls are no-ops after the first success.
|
|
34
34
|
*/
|
|
35
35
|
ensureReady?(): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Returns the recorded permanent-refresh error when the provider is in
|
|
38
|
+
* a known-broken state (e.g. proactive refresh exhausted its retries),
|
|
39
|
+
* or null otherwise. The transport calls this before issuing any
|
|
40
|
+
* authenticated request and throws the error directly so consumers see
|
|
41
|
+
* the typed `AuthRefreshFailedError` instead of an opaque downstream
|
|
42
|
+
* 401. Implementations without proactive refresh return null / omit.
|
|
43
|
+
*/
|
|
44
|
+
getLastAuthError?(): Error | null;
|
|
36
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Pre-flight gate used by authenticated SDK paths (RPC, file upload,
|
|
48
|
+
* TaskClient methods) to surface a recorded permanent-refresh error
|
|
49
|
+
* before issuing the request.
|
|
50
|
+
*
|
|
51
|
+
* Behavior:
|
|
52
|
+
* - No `getLastAuthError` / no recorded error -> returns immediately.
|
|
53
|
+
* - Error recorded -> attempts one reactive refresh via
|
|
54
|
+
* `onAuthFailure()`. On success the error clears atomically with the
|
|
55
|
+
* new token apply and this returns silently (caller proceeds with the
|
|
56
|
+
* refreshed token). On failure the error stays recorded and this
|
|
57
|
+
* throws it so the caller sees `AuthRefreshFailedError` instead of an
|
|
58
|
+
* opaque downstream 401.
|
|
59
|
+
*
|
|
60
|
+
* The two-phase shape (record-then-retry-then-throw) preserves the
|
|
61
|
+
* documented reactive recovery path: a transient outage that exhausts
|
|
62
|
+
* proactive retries and then resolves still lets the next authenticated
|
|
63
|
+
* call recover, instead of permanently wedging the client until it is
|
|
64
|
+
* rebuilt.
|
|
65
|
+
*/
|
|
66
|
+
export declare function preflightAuthOrThrow(provider: AuthProvider | undefined): Promise<void>;
|
|
37
67
|
/**
|
|
38
68
|
* Wraps a static JWT string. No refresh capability -- onAuthFailure()
|
|
39
69
|
* always returns false.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export class StaticAuthProvider{constructor(t){this.token=t}getAuthHeader(){return`Bearer ${this.token}`}async onAuthFailure(){return!1}}
|
|
1
|
+
export async function preflightAuthOrThrow(t){if(!t?.getLastAuthError)return;if(!t.getLastAuthError())return;await t.onAuthFailure();const r=t.getLastAuthError();if(r)throw r}export class StaticAuthProvider{constructor(t){this.token=t}getAuthHeader(){return`Bearer ${this.token}`}async onAuthFailure(){return!1}}
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* - agent.{agentId}.control - Control plane per agent ID
|
|
7
7
|
* - obs.{agentName}.log - Agent-level observability
|
|
8
8
|
*
|
|
9
|
-
* Registry channels (
|
|
9
|
+
* Registry channels (membership indexes):
|
|
10
10
|
* - registry.all - All agents (membership index)
|
|
11
11
|
* - registry.public - Public agents (membership index)
|
|
12
12
|
* - registry.private - Private agents (membership index)
|
|
13
|
-
* - registry.
|
|
13
|
+
* - registry.tag.{tag} - Agents with specific tag (membership index)
|
|
14
14
|
* - registry.log - Audit log channel (pub/sub with history)
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* Channel metadata (not pub/sub):
|
|
17
17
|
* - uuid:{agentName} - Agent card stored as User Object
|
|
18
18
|
*
|
|
19
19
|
* All channels respect PubNub's 3-level maximum depth constraint.
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
*/
|
|
26
26
|
export declare const registryAllChannel: () => string;
|
|
27
27
|
/**
|
|
28
|
-
* Registry
|
|
29
|
-
* Format: registry.
|
|
28
|
+
* Registry tag channel - membership index for agents with a tag.
|
|
29
|
+
* Format: registry.tag.{tag}
|
|
30
30
|
*
|
|
31
|
-
*
|
|
31
|
+
* Tag slugs are normalized: lowercase, alphanumeric + `.` or `_`.
|
|
32
32
|
*/
|
|
33
|
-
export declare const
|
|
33
|
+
export declare const registryTagChannel: (tag: string) => string;
|
|
34
34
|
/**
|
|
35
35
|
* Registry visibility channel - membership index for public/private agents.
|
|
36
36
|
* Format: registry.public or registry.private
|
|
@@ -42,7 +42,7 @@ export declare const registryVisibilityChannel: (isPublic: boolean) => string;
|
|
|
42
42
|
*/
|
|
43
43
|
export declare const registryLogChannel: () => string;
|
|
44
44
|
/**
|
|
45
|
-
* Normalize a
|
|
45
|
+
* Normalize a tag string to a stable slug.
|
|
46
46
|
* - Lowercase
|
|
47
47
|
* - Replace non-alphanumeric characters (except `.` and `_`) with `_`
|
|
48
48
|
* - Collapse multiple underscores
|
|
@@ -52,7 +52,7 @@ export declare const registryLogChannel: () => string;
|
|
|
52
52
|
* - "text.embeddings" -> "text.embeddings"
|
|
53
53
|
* - "Image Generation" -> "image_generation"
|
|
54
54
|
*/
|
|
55
|
-
export declare const
|
|
55
|
+
export declare const normalizeTagSlug: (tag: string) => string;
|
|
56
56
|
/**
|
|
57
57
|
* Validates that an owner ID is not a reserved prefix.
|
|
58
58
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const registryAllChannel=()=>"registry.all";export const
|
|
1
|
+
export const registryAllChannel=()=>"registry.all";export const registryTagChannel=r=>`registry.tag.${normalizeTagSlug(r)}`;export const registryVisibilityChannel=r=>r?"registry.public":"registry.private";export const registryLogChannel=()=>"registry.log";export const normalizeTagSlug=r=>r.toLowerCase().replace(/[^a-z0-9._]/g,"_").replace(/_+/g,"_").replace(/^_|_$/g,"");const r=["agent","obs","sys","u","anonymous","system","stream"];export function validateOwnerId(e){return!(!e||"string"!=typeof e)&&!r.includes(e.toLowerCase())}export class ChannelManager{constructor(r={}){if(!r.agentName)throw new Error("agentName is required for ChannelManager");this.agentName=r.agentName}getAgentName(){return this.agentName}taskChannel(r,e){if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`}controlChannel(r){return`agent.${r}.control`}obsChannel(r){return`obs.${r??this.agentName}.log`}userTaskPattern(r){if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`}agentWildcard(r){return`agent.${r??this.agentName}.*`}streamChannel(r,e){if(!r)throw new Error("streamId required for stream channel");return`stream.${e??this.agentName}.${r}`}streamWildcard(r){return`stream.${r??this.agentName}.*`}}export const createChannelManager=r=>new ChannelManager({agentName:r});export const taskChannel=(r,e)=>{if(!e)throw new Error("orgId required for task channel");if(!r)throw new Error("taskId required for task channel");return`u.${e}.${r}`};export const userTaskPattern=r=>{if(!r)throw new Error("orgId required for user task pattern");return`u.${r}.*`};export const streamChannel=(r,e)=>{if(!r)throw new Error("agentName required for stream channel");if(!e)throw new Error("streamId required for stream channel");return`stream.${r}.${e}`};
|