@roll-agent/relay-client 0.1.3 → 0.2.1

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/client.d.ts CHANGED
@@ -1,17 +1,19 @@
1
- import { relayRequestMethodSchemasV11, type RelayInteractionRequestV11, type RelayRequestParamsForVersion, type RelayRequestResultForVersion } from "@roll-agent/relay-protocol";
1
+ import { relayRequestMethodSchemasV12, type RelayInteractionRequestV11, type RelayRequestParamsForVersion, type RelayRequestResultForVersion } from "@roll-agent/relay-protocol";
2
2
  import { z } from "zod/v4";
3
3
  import { type RelayClientErrorDetails, type RelayConnectionState, type RelayThreadView } from "./schemas.ts";
4
4
  import { type RelayClientRuntimeDependencies } from "./transport.ts";
5
- type ThreadListWireInput = z.input<(typeof relayRequestMethodSchemasV11)["thread.list"]["params"]>;
6
- type ThreadCreateWireInput = z.input<(typeof relayRequestMethodSchemasV11)["thread.create"]["params"]>;
7
- type ThreadListWireResult = RelayRequestResultForVersion<"1.1", "thread.list">;
8
- type ThreadOpenWireResult = RelayRequestResultForVersion<"1.1", "thread.open">;
9
- type ThreadSnapshotWireResult = RelayRequestResultForVersion<"1.1", "thread.snapshot">;
10
- type TurnStartWireResult = RelayRequestResultForVersion<"1.1", "turn.start">;
11
- type TurnCancelWireResult = RelayRequestResultForVersion<"1.1", "turn.cancel">;
12
- type InteractionCandidateWireResult = RelayRequestResultForVersion<"1.1", "interaction.candidate">;
5
+ type ThreadListWireInput = z.input<(typeof relayRequestMethodSchemasV12)["thread.list"]["params"]>;
6
+ type ThreadCreateWireInput = z.input<(typeof relayRequestMethodSchemasV12)["thread.create"]["params"]>;
7
+ type ThreadListWireResult = RelayRequestResultForVersion<"1.2", "thread.list">;
8
+ type ThreadOpenWireResult = RelayRequestResultForVersion<"1.2", "thread.open">;
9
+ type ThreadSnapshotWireResult = RelayRequestResultForVersion<"1.2", "thread.snapshot">;
10
+ type TurnStartWireResult = RelayRequestResultForVersion<"1.2", "turn.start">;
11
+ type TurnCancelWireResult = RelayRequestResultForVersion<"1.2", "turn.cancel">;
12
+ type InteractionCandidateWireResult = RelayRequestResultForVersion<"1.2", "interaction.candidate">;
13
+ export type RelayOperationResult = RelayRequestResultForVersion<"1.2", "operation.result.get">;
14
+ export type RelayThreadCapabilities = RelayRequestResultForVersion<"1.2", "thread.capabilities">;
13
15
  export type RelayThreadId = ThreadOpenWireResult["thread"]["id"];
14
- export type RelayTurnId = RelayRequestParamsForVersion<"1.1", "turn.cancel">["turnId"];
16
+ export type RelayTurnId = RelayRequestParamsForVersion<"1.2", "turn.cancel">["turnId"];
15
17
  export type RelayInteractionId = RelayInteractionRequestV11["interactionId"];
16
18
  export type RelayThreadSummary = ThreadListWireResult["items"][number];
17
19
  export type RelayThreadListInput = Omit<ThreadListWireInput, "limit"> & {
@@ -25,6 +27,7 @@ export type RelayTurnCancelResult = TurnCancelWireResult;
25
27
  export type RelayInteractionCandidateResult = InteractionCandidateWireResult;
26
28
  export interface RelaySessionProviderInput {
27
29
  readonly signal: AbortSignal;
30
+ readonly supportedRelayProtocolVersions: readonly ["1.2", "1.1"];
28
31
  }
29
32
  export type RelaySessionProvider = (input: RelaySessionProviderInput) => Promise<unknown>;
30
33
  export interface RelayRequestOptions {
@@ -46,6 +49,8 @@ export interface RelayThread {
46
49
  cancel(turnId: RelayTurnId, options?: RelayRequestOptions): Promise<RelayTurnCancelResult>;
47
50
  respond(interactionId: RelayInteractionId, candidate: unknown, options?: RelayRequestOptions): Promise<RelayInteractionCandidateResult>;
48
51
  refresh(options?: RelayRequestOptions): Promise<RelayThreadSnapshot>;
52
+ getResult(operationId: string, options?: RelayRequestOptions): Promise<RelayOperationResult>;
53
+ capabilities(options?: RelayRequestOptions): Promise<RelayThreadCapabilities>;
49
54
  }
50
55
  export interface RelayClient {
51
56
  connect(options?: RelayRequestOptions): Promise<void>;
@@ -73,6 +78,8 @@ export declare class RelayClientImpl implements RelayClient {
73
78
  startTurn(threadId: RelayThreadId, text: string, options?: RelayRequestOptions): Promise<RelayTurnStartResult>;
74
79
  cancelTurn(threadId: RelayThreadId, turnId: RelayTurnId, options?: RelayRequestOptions): Promise<RelayTurnCancelResult>;
75
80
  respondToInteraction(request: RelayInteractionRequestV11, candidate: unknown, options?: RelayRequestOptions): Promise<RelayInteractionCandidateResult>;
81
+ getThreadCapabilities(threadId: RelayThreadId, options?: RelayRequestOptions): Promise<RelayThreadCapabilities>;
82
+ getOperationResult(threadId: RelayThreadId, operationId: string, options?: RelayRequestOptions): Promise<RelayOperationResult>;
76
83
  getThreadSnapshot(threadId: RelayThreadId, options?: RelayRequestOptions): Promise<RelayThreadSnapshot>;
77
84
  }
78
85
  export declare function createRelayClientWithRuntime(options: CreateRelayClientOptions, runtime: RelayClientRuntimeDependencies): RelayClient;
package/dist/client.js CHANGED
@@ -1 +1 @@
1
- import{RELAY_ERROR_CODES_V11 as e,RELAY_MESSAGE_TYPES_V11 as t,RELAY_REQUEST_METHODS_V11 as s,RELAY_REQUEST_METHOD_DISPOSITIONS_V11 as n,parseRelayInteractionCandidateForRequestV11 as i,parseRelayRequestParamsForVersion as r,parseRelayRequestResultForVersion as o,relayAckSchemaV11 as a,relayMessageSchemaV11 as c,relayRequestIdSchema as l,relayRequestMethodSchemasV11 as d,relayRuntimeRequestSchemaV11 as h}from"@roll-agent/relay-protocol";import{parseRelayBrowserFirstControlFrame as u,relayBrowserControlMessageSchema as p,relaySessionDescriptorSchema as y}from"@roll-agent/relay-protocol/control";import{z as m}from"zod/v4";import{RELAY_CLIENT_TRANSPORT_ERROR_CODES as f,getPendingInteractionRequest as g,relayClientErrorDetailsSchema as v,relayConnectionStateSchema as w,relayThreadViewSchema as S}from"./schemas.js";import{browserRelayRuntimeDependencies as R}from"./transport.js";export class RelayClientError extends Error{details;constructor(e){super(e.message),this.name="RelayClientError",this.details=v.parse(e)}}const b=3e4,q=1,I=-1,k={normal:1e3,invalidFrame:4002,sessionFatal:4008,sessionRetryable:4012},C=new Set(["completed","cancelled","failed"]);function T(e,t,s){return new RelayClientError({kind:"transport",code:e,message:t,retryable:s})}function E(e){return e instanceof RelayClientError?e:T(f.transportError,e instanceof Error?e.message:"Relay transport failed",!0)}function M(e,t){try{e(t)}catch(e){if("function"==typeof globalThis.reportError)try{globalThis.reportError(e)}catch{}}}function P(e){if("string"!=typeof e)throw T(f.protocolError,"Relay sent a non-text frame",!1);try{return JSON.parse(e)}catch{throw T(f.protocolError,"Relay sent invalid JSON",!1)}}function A(e){try{return u(e)}catch{throw T(f.protocolError,"Relay first frame must be Control 1.0 session.ready for Wire 1.1",!1)}}function B(e){const t=e.activeTurn;return void 0===t?{status:"idle"}:"cancelling"===t.status?{status:"cancelling",turnId:t.id}:{status:"running",turnId:t.id}}function x(e,t){const s=e.findIndex(e=>e.streamId===t.streamId);return-1===s?[...e,t]:e.map((e,n)=>n===s?t:e)}function O(e,t){return{status:e,interactionId:t.interactionId,threadId:t.threadId,turnId:t.turnId,method:t.method}}class RelayThreadImpl{id;#e;#t=new Set;#s;constructor(e,t){this.#e=e,this.id=t,this.#s=S.parse({status:"loading",threadId:t,snapshot:null,liveAssistantMessages:[],interactions:[],turn:{status:"idle"}})}getSnapshot(){return this.#s}subscribe(e){return this.#t.add(e),M(e,this.#s),()=>{this.#t.delete(e)}}async send(e,t){const s=await this.#e.startTurn(this.id,e,t);return this.#n({status:"running",turnId:s.turnId}),s}async cancel(e,t){const s=await this.#e.cancelTurn(this.id,e,t);return s.cancelling&&this.#n({status:"cancelling",turnId:e}),s}async respond(e,t,s){const n=this.#s.interactions.find(t=>{const s=g(t);return s?.interactionId===e}),i=void 0===n?void 0:g(n);if(void 0===i)throw T(f.connectionUnavailable,"Interaction is no longer pending in this Browser session",!1);this.#i(e,"responding");try{return await this.#e.respondToInteraction(i,t,s)}catch(t){throw this.#i(e,"pending"),t}}async refresh(e){try{const t=await this.#e.getThreadSnapshot(this.id,e);return this.applySnapshot(t),t}catch(e){throw this.applyError(E(e).details),e}}applySnapshot(e){this.#r({status:"ready",threadId:this.id,snapshot:e,liveAssistantMessages:[],interactions:this.#s.interactions,turn:B(e)})}applyError(e){this.#r({status:"error",threadId:this.id,snapshot:this.#s.snapshot,liveAssistantMessages:this.#s.liveAssistantMessages,interactions:this.#s.interactions,turn:this.#s.turn,error:e})}applyRuntimeEvent(e){const t=e.event,s=t.event,n=t.turnId;let i=this.#s.liveAssistantMessages,r=this.#s.turn;if("message.started"===s.type)i=x(i,{status:"streaming",streamId:s.streamId,text:""});else if("message.delta"===s.type){const e=i.find(e=>e.streamId===s.streamId);i=x(i,{status:"streaming",streamId:s.streamId,text:`${e?.text??""}${s.delta}`})}else"message.completed"===s.type?i=x(i,{status:"completed",streamId:s.streamId,text:s.text}):"turn.started"===s.type&&void 0!==n?r={status:"running",turnId:n}:"turn.completed"===s.type&&void 0!==n?r={status:"completed",turnId:n}:"turn.cancelled"===s.type&&void 0!==n?r={status:"cancelled",turnId:n}:"turn.failed"===s.type&&void 0!==n&&(r={status:"failed",turnId:n,stage:s.stage});this.#o({liveAssistantMessages:i,turn:r}),"turn.completed"!==s.type&&"turn.cancelled"!==s.type&&"turn.failed"!==s.type||this.refresh().catch(()=>{})}applyInteractionRequest(e){const t=this.#s.interactions.findIndex(t=>{const s=g(t);return s?.interactionId===e.interactionId}),s={status:"pending",request:e},n=-1===t?[...this.#s.interactions,s]:this.#s.interactions.map((e,n)=>n===t?s:e);this.#o({interactions:n})}applyInteractionTerminal(e){const s=O(e.type===t.interactionResolved?"resolved":"cancelled",e),n=this.#s.interactions.findIndex(t=>{const s=g(t);return s?.interactionId===e.interactionId}),i=-1===n?[...this.#s.interactions,s]:this.#s.interactions.map((e,t)=>t===n?s:e);this.#o({interactions:i})}#n(e){const t=this.#s.turn;"idle"!==t.status&&t.turnId===e.turnId&&C.has(t.status)||this.#o({turn:e})}#i(e,t){const s=this.#s.interactions.map(s=>{const n=g(s);return n?.interactionId===e?{status:t,request:n}:s});this.#o({interactions:s})}#o(e){const t={threadId:this.id,snapshot:this.#s.snapshot,liveAssistantMessages:e.liveAssistantMessages??this.#s.liveAssistantMessages,interactions:e.interactions??this.#s.interactions,turn:e.turn??this.#s.turn};this.#r(null===this.#s.snapshot?{status:"loading",...t,snapshot:null}:{status:"ready",...t,snapshot:this.#s.snapshot})}#r(e){this.#s=S.parse(e);for(const e of this.#t)M(e,this.#s)}}export class RelayClientImpl{#a;#c;#l=new Set;#d=new Map;#h=new Map;#u=new Map;#p={status:"idle"};#y;#m;#f;#g;#v;#w=0;#S=!1;#R=!1;#b=I;#q=!1;#I=0;#k=!1;#C;constructor(e,t){if(this.#a=e,this.#c=t,this.#C=e.requestTimeoutMs??b,!Number.isFinite(this.#C)||this.#C<=0)throw new TypeError("requestTimeoutMs must be a positive finite number")}async connect(e){if("connected"===this.#p.status)return;if(this.#S)throw T(f.closed,"Relay client is closed",!1);this.#T();const t=this.#f;return void 0!==t?t:(this.#R=!1,this.#w=0,this.#E({status:"connecting",attempt:1}),this.#M(e?.signal,e?.timeoutMs).catch(e=>{const t=E(e);throw this.#S||this.#R||this.#E({status:"closed",reason:"session"===t.details.kind?"session":"transport"}),t}))}close(){this.#S||(this.#S=!0,this.#R=!0,this.#g?.abort(),this.#T(),this.#P("client closed"),this.#A(!0),this.#E({status:"closed",reason:"client"}))}getConnectionState(){return this.#p}subscribeConnection(e){return this.#l.add(e),M(e,this.#p),()=>{this.#l.delete(e)}}async listThreads(e={},t){return this.#B(s.threadList,e,t)}async createThread(e={},t){const n=await this.#B(s.threadCreate,{...e,requestId:this.#c.createUuid()},t),i=this.#x(n.thread.id);return await i.refresh(t).catch(()=>{}),i}async openThread(e,t){const n=await this.#B(s.threadOpen,{threadId:e},t),i=this.#x(e);return i.applySnapshot(n),i}async startTurn(e,t,n){return this.#B(s.turnStart,{requestId:this.#c.createUuid(),threadId:e,turnId:this.#c.createUuid(),input:{text:t}},n)}async cancelTurn(e,t,n){return this.#B(s.turnCancel,{requestId:this.#c.createUuid(),threadId:e,turnId:t},n)}async respondToInteraction(e,t,n){const r=i(e,t);return this.#B(s.interactionCandidate,r,n)}async getThreadSnapshot(e,t){return this.#B(s.threadSnapshot,{threadId:e,limit:100},t)}async#O(e,t){const s=t??this.#C;if(!Number.isFinite(s)||s<=0)throw new TypeError("timeoutMs must be a positive finite number");const n=new AbortController;this.#g=n;let i=!1;const r=()=>{n.abort()};e?.addEventListener("abort",r,{once:!0}),!0===e?.aborted&&n.abort();const o=this.#c.scheduler.setTimer(()=>{i=!0,n.abort()},s),a=new Promise((e,t)=>{const s=()=>{t(T(i?f.requestTimeout:f.aborted,i?"Relay session connection timed out":"Relay session request was aborted",i)),this.#y?.close(k.normal,"session attempt cancelled")};n.signal.aborted?s():n.signal.addEventListener("abort",s,{once:!0})});try{await Promise.race([this.#W(n.signal),a])}finally{this.#c.scheduler.clearTimer(o),e?.removeEventListener("abort",r),this.#g===n&&(this.#g=void 0)}}async#W(e){try{let t;try{t=await this.#a.getSession({signal:e})}catch(t){if(e.aborted)throw T(f.aborted,"Relay session request was aborted",!1);throw E(t)}if(e.aborted)throw T(f.aborted,"Relay session request was aborted",!1);const s=y.safeParse(t);if(!s.success)throw T(f.invalidSession,"Session endpoint returned an invalid Relay session",!1);if(Date.parse(s.data.expiresAt)<=this.#c.scheduler.now())throw T(f.invalidSession,"Session endpoint returned an expired Relay session",!1);this.#P("superseded relay session");const n=this.#c.createWebSocket(s.data.connectUrl);this.#y=n,await this.#F(n)}catch(e){throw E(e)}}#F(e){return new Promise((t,s)=>{const n={ready:!1,settled:!1},i=e=>{n.settled||(n.settled=!0,s(e))};e.setHandlers({onOpen:()=>{},onMessage:s=>{if(!n.ready||this.#y===e)try{const e=P(s);if(!n.ready){const s=A(e);if(void 0!==this.#m&&this.#m!==s.workspaceId)throw T(f.protocolError,"Relay session changed the bound Workspace",!1);return this.#m=s.workspaceId,n.ready=!0,n.settled=!0,this.#w=0,this.#k=!0,this.#E({status:"connected",workspaceStatus:s.workspaceStatus}),this.#j(),"online"===s.workspaceStatus&&this.#L(),void t()}this.#U(e)}catch(t){const s=E(t);n.ready?this.#z(s):i(s),e.close(k.invalidFrame,"invalid relay frame")}},onClose:()=>{const t=this.#y===e;t&&(this.#y=void 0),n.ready?t&&this.#N():i(T(f.transportError,"Relay connection closed before session.ready",!0))},onError:()=>{n.ready||i(T(f.transportError,"Relay WebSocket failed before session.ready",!0))}})})}#U(e){const t=p.safeParse(e);if(t.success)return void this.#G(t.data);const s=c.safeParse(e);if(!s.success)throw T(f.protocolError,"Relay sent an invalid Control 1.0 or Wire 1.1 frame",!1);this.#H(s.data)}#G(e){if("session.ready"===e.type)throw T(f.protocolError,"session.ready may only be the first frame",!1);if("workspace.status"===e.type){this.#$(e.workspaceId);const t=this.#p,s="connected"===t.status&&"offline"===t.workspaceStatus&&"online"===e.status;return this.#E({status:"connected",workspaceStatus:e.status}),s&&(this.#k=!0,this.#D()),void("online"===e.status&&this.#L())}const t=new RelayClientError({kind:"session",code:e.code,message:`Relay session failed: ${e.code}`,retryable:e.retryable});if(e.retryable)this.#y?.close(k.sessionRetryable,"retryable relay session error");else{this.#R=!0,this.#A(!0);for(const e of this.#d.values())e.applyError(t.details);this.#E({status:"closed",reason:"session"}),this.#y?.close(k.sessionFatal,"non-retryable relay session error")}}#H(e){if(e.type!==t.runtimeResponse){if(e.type===t.runtimeGap)return this.#$(e.workspaceId),this.#J(e.throughRelaySequence),void this.#Q(e.throughRelaySequence);if(e.type!==t.runtimeEvent&&e.type!==t.interactionRequest&&e.type!==t.interactionResolved&&e.type!==t.interactionCancelled)throw T(f.protocolError,`Relay sent disallowed Browser inbound frame: ${e.type}`,!1);this.#V({relaySequence:e.relaySequence,message:e})}else this.#K(e)}#K(t){this.#$(t.workspaceId);const s=this.#h.get(t.requestId);if(void 0!==s){if(void 0!==t.error){const n=t.error.code===e.remoteRequestDenied?"Remote request denied":t.error.message;return void this.#X(s,new RelayClientError({kind:"remote",code:t.error.code,message:n,retryable:t.error.retryable}))}if(void 0!==t.result)try{const e=o("1.1",s.method,t.result);this.#X(s,void 0,e)}catch{this.#X(s,T(f.protocolError,`Relay returned an invalid ${s.method} result`,!1))}else this.#X(s,T(f.protocolError,"runtime.response contains neither result nor error",!1))}}#V(e){if(this.#$(e.message.workspaceId),this.#J(e.relaySequence),e.relaySequence<=this.#b)this.#j();else if(this.#q)this.#u.set(e.relaySequence,e);else{if(e.relaySequence!==this.#b+1)return this.#u.set(e.relaySequence,e),void this.#Q(e.relaySequence-1);this.#Y(e.message),this.#b=e.relaySequence,this.#j()}}#Y(e){const s=e.type===t.runtimeEvent?e.event.threadId:e.threadId,n=this.#x(s);e.type===t.runtimeEvent?n.applyRuntimeEvent(e):e.type===t.interactionRequest?n.applyInteractionRequest(e):n.applyInteractionTerminal(e)}#Q(e){if(e<=this.#b)return void this.#j();if(this.#q)return;this.#q=!0;const t=e,s=this.#I,n=[...this.#d.values()].map(async e=>e.refresh());Promise.all(n).then(()=>{if(this.#I===s){this.#b=t;for(const e of[...this.#u.keys()])e<=t&&this.#u.delete(e);this.#q=!1,this.#j(),this.#Z()}}).catch(()=>{this.#I===s&&(this.#q=!1,this.#y?.close(k.sessionRetryable,"snapshot recovery failed"))})}#J(e){this.#k&&(this.#k=!1,e>this.#b||(this.#I+=1,this.#b=I,this.#u.clear(),this.#q=!1,this.#D()))}#D(){for(const e of this.#d.values())e.refresh().catch(()=>{})}#Z(){for(;!this.#q;){const e=this.#b+1,t=this.#u.get(e);if(void 0===t){const t=[...this.#u.keys()].sort((e,t)=>e-t)[0];return void(void 0!==t&&t>e&&this.#Q(t-1))}this.#u.delete(e),this.#Y(t.message),this.#b=e,this.#j()}}#j(){const e=this.#m,s=this.#y;if(void 0===e||1!==s?.readyState)return;const n=a.parse({type:t.runtimeAck,workspaceId:e,throughRelaySequence:this.#b});s.send(JSON.stringify(n))}#N(){this.#S||this.#R||(this.#_(),this.#ee())}#ee(){if(void 0!==this.#v||this.#S||this.#R)return;this.#w+=1;const e=this.#c.reconnectDelayMs(this.#w),t=new Date(this.#c.scheduler.now()+e).toISOString();this.#E({status:"reconnecting",attempt:this.#w,retryAt:t}),this.#v=this.#c.scheduler.setTimer(()=>{this.#v=void 0,this.#S||this.#R||void 0!==this.#f||"connected"===this.#p.status||this.#M().catch(e=>{if(this.#S)return;if(!E(e).details.retryable)return this.#R=!0,this.#A(!0),void this.#E({status:"closed",reason:"session"});this.#ee()})},e)}#M(e,t){const s=this.#O(e,t).finally(()=>{this.#f===s&&(this.#f=void 0)});return this.#f=s,s}#P(e){const t=this.#y;void 0!==t&&(this.#y=void 0,t.close(k.normal,e))}#T(){void 0!==this.#v&&(this.#c.scheduler.clearTimer(this.#v),this.#v=void 0)}#L(){for(const e of this.#h.values())e.mutation&&this.#te(e)}#B(e,s,i){if("connected"!==this.#p.status||"online"!==this.#p.workspaceStatus||void 0===this.#m){const e="connected"===this.#p.status&&"offline"===this.#p.workspaceStatus;return Promise.reject(T(e?f.workspaceOffline:f.connectionUnavailable,e?"The local Workspace is offline":"Relay is not connected",!0))}if(!0===i?.signal?.aborted)return Promise.reject(T(f.aborted,"Relay request was aborted",!1));const o=r("1.1",e,s),a=l.parse(this.#c.createUuid()),c=h.parse({type:t.runtimeRequest,requestId:a,workspaceId:this.#m,method:e,params:o}),d=i?.timeoutMs??this.#C;if(!Number.isFinite(d)||d<=0)return Promise.reject(new TypeError("timeoutMs must be a positive finite number"));const u="mutation"===n[e];return new Promise((t,s)=>{const n=i?.signal?()=>{const e=this.#h.get(a);if(void 0===e)return;const t=e.mutation&&e.sent;this.#X(e,T(t?f.outcomeUnknown:f.aborted,t?"Mutation outcome is unknown after Browser cancellation":"Relay request was aborted",!1))}:void 0,r={requestId:a,method:e,frame:JSON.stringify(c),mutation:u,sent:!1,resolve:e=>{t(e)},reject:s,signal:i?.signal,abortListener:n,timeoutHandle:void 0};r.timeoutHandle=this.#c.scheduler.setTimer(()=>{const e=this.#h.get(a);if(void 0===e)return;const t=e.mutation&&e.sent;this.#X(e,T(t?f.outcomeUnknown:f.requestTimeout,t?"Mutation outcome is unknown after Relay timeout":"Relay request timed out",!t))},d),i?.signal?.addEventListener("abort",n??(()=>{}),{once:!0}),this.#h.set(a,r),this.#te(r)})}#te(e){const t=this.#y;1===t?.readyState&&"connected"===this.#p.status&&"online"===this.#p.workspaceStatus&&(t.send(e.frame),e.sent=!0)}#X(e,t,s){this.#h.delete(e.requestId)&&(void 0!==e.timeoutHandle&&this.#c.scheduler.clearTimer(e.timeoutHandle),void 0!==e.signal&&void 0!==e.abortListener&&e.signal.removeEventListener("abort",e.abortListener),void 0!==t?e.reject(t):e.resolve(s))}#_(){for(const e of[...this.#h.values()])e.mutation||this.#X(e,T(f.transportError,"Relay disconnected before the query completed",!0))}#A(e){for(const t of[...this.#h.values()]){const s=e&&t.mutation&&t.sent;this.#X(t,T(s?f.outcomeUnknown:f.closed,s?"Mutation outcome is unknown after Relay closed":"Relay client is closed",!1))}}#$(e){if(this.#m!==e)throw T(f.protocolError,"Relay frame does not match the bound Workspace",!1)}#x(e){const t=this.#d.get(e);if(void 0!==t)return t;const s=new RelayThreadImpl(this,e);return this.#d.set(e,s),s}#E(e){this.#p=w.parse(e);for(const e of this.#l)M(e,this.#p)}#z(e){this.#R=!0,this.#A(!0);for(const t of this.#d.values())t.applyError(e.details);this.#E({status:"closed",reason:"protocol"})}}export function createRelayClientWithRuntime(e,t){return new RelayClientImpl(e,t)}export function createRelayClient(e){return createRelayClientWithRuntime(e,R)}
1
+ import{RELAY_ERROR_CODES_V11 as e,RELAY_MESSAGE_TYPES_V11 as t,RELAY_REQUEST_METHODS_V11 as s,RELAY_REQUEST_METHOD_DISPOSITIONS_V12 as n,parseRelayInteractionCandidateForRequestV11 as i,parseRelayRequestParamsForVersion as r,parseRelayRequestResultForVersion as o,relayAckSchemaV11 as a,relayMessageSchemaV12 as c,relayRequestIdSchema as l,relayRequestMethodSchemasV12 as d,relayRuntimeRequestSchemaV12 as h}from"@roll-agent/relay-protocol";import{parseRelayBrowserFirstControlFrameAny as u,relayBrowserControlMessageSchemaAny as p,relaySessionDescriptorSchema as y}from"@roll-agent/relay-protocol/control";import{z as m}from"zod/v4";import{RELAY_CLIENT_TRANSPORT_ERROR_CODES as f,getPendingInteractionRequest as g,relayClientErrorDetailsSchema as v,relayConnectionStateSchema as w,relayThreadViewSchema as R}from"./schemas.js";import{browserRelayRuntimeDependencies as S}from"./transport.js";export class RelayClientError extends Error{details;constructor(e){super(e.message),this.name="RelayClientError",this.details=v.parse(e)}}const b=3e4,I=1,q=-1,k={normal:1e3,invalidFrame:4002,sessionFatal:4008,sessionRetryable:4012},C=new Set(["completed","cancelled","failed"]);function T(e,t,s){return new RelayClientError({kind:"transport",code:e,message:t,retryable:s})}function E(e){return e instanceof RelayClientError?e:T(f.transportError,e instanceof Error?e.message:"Relay transport failed",!0)}function P(e,t){try{e(t)}catch(e){if("function"==typeof globalThis.reportError)try{globalThis.reportError(e)}catch{}}}function M(e){if("string"!=typeof e)throw T(f.protocolError,"Relay sent a non-text frame",!1);try{return JSON.parse(e)}catch{throw T(f.protocolError,"Relay sent invalid JSON",!1)}}function A(e){try{return u(e)}catch{throw T(f.protocolError,"Relay first frame must be Control 1.0 session.ready for Wire 1.1",!1)}}function B(e){const t=e.activeTurn;return void 0===t?{status:"idle"}:"cancelling"===t.status?{status:"cancelling",turnId:t.id}:{status:"running",turnId:t.id}}function O(e,t){const s=e.findIndex(e=>e.streamId===t.streamId);return-1===s?[...e,t]:e.map((e,n)=>n===s?t:e)}function x(e,t){return{status:e,interactionId:t.interactionId,threadId:t.threadId,turnId:t.turnId,method:t.method}}class RelayThreadImpl{id;#e;#t=new Set;#s;constructor(e,t){this.#e=e,this.id=t,this.#s=R.parse({status:"loading",threadId:t,snapshot:null,liveAssistantMessages:[],interactions:[],turn:{status:"idle"}})}getSnapshot(){return this.#s}subscribe(e){return this.#t.add(e),P(e,this.#s),()=>{this.#t.delete(e)}}async send(e,t){const s=await this.#e.startTurn(this.id,e,t);return this.#n({status:"running",turnId:s.turnId}),s}async cancel(e,t){const s=await this.#e.cancelTurn(this.id,e,t);return s.cancelling&&this.#n({status:"cancelling",turnId:e}),s}async respond(e,t,s){const n=this.#s.interactions.find(t=>{const s=g(t);return s?.interactionId===e}),i=void 0===n?void 0:g(n);if(void 0===i)throw T(f.connectionUnavailable,"Interaction is no longer pending in this Browser session",!1);this.#i(e,"responding");try{return await this.#e.respondToInteraction(i,t,s)}catch(t){throw this.#i(e,"pending"),t}}getResult(e,t){return this.#e.getOperationResult(this.id,e,t)}capabilities(e){return this.#e.getThreadCapabilities(this.id,e)}async refresh(e){try{const t=await this.#e.getThreadSnapshot(this.id,e);return this.applySnapshot(t),t}catch(e){throw this.applyError(E(e).details),e}}applySnapshot(e){this.#r({status:"ready",threadId:this.id,snapshot:e,liveAssistantMessages:[],interactions:this.#s.interactions,turn:B(e)})}applyError(e){this.#r({status:"error",threadId:this.id,snapshot:this.#s.snapshot,liveAssistantMessages:this.#s.liveAssistantMessages,interactions:this.#s.interactions,turn:this.#s.turn,error:e})}applyRuntimeEvent(e){const t=e.event,s=t.event,n=t.turnId;let i=this.#s.liveAssistantMessages,r=this.#s.turn;if("message.started"===s.type)i=O(i,{status:"streaming",streamId:s.streamId,text:""});else if("message.delta"===s.type){const e=i.find(e=>e.streamId===s.streamId);i=O(i,{status:"streaming",streamId:s.streamId,text:`${e?.text??""}${s.delta}`})}else"message.completed"===s.type?i=O(i,{status:"completed",streamId:s.streamId,text:s.text}):"turn.started"===s.type&&void 0!==n?r={status:"running",turnId:n}:"turn.completed"===s.type&&void 0!==n?r={status:"completed",turnId:n}:"turn.cancelled"===s.type&&void 0!==n?r={status:"cancelled",turnId:n}:"turn.failed"===s.type&&void 0!==n&&(r={status:"failed",turnId:n,stage:s.stage});this.#o({liveAssistantMessages:i,turn:r}),"turn.completed"!==s.type&&"turn.cancelled"!==s.type&&"turn.failed"!==s.type||this.refresh().catch(()=>{})}applyInteractionRequest(e){const t=this.#s.interactions.findIndex(t=>{const s=g(t);return s?.interactionId===e.interactionId}),s={status:"pending",request:e},n=-1===t?[...this.#s.interactions,s]:this.#s.interactions.map((e,n)=>n===t?s:e);this.#o({interactions:n})}applyInteractionTerminal(e){const s=x(e.type===t.interactionResolved?"resolved":"cancelled",e),n=this.#s.interactions.findIndex(t=>{const s=g(t);return s?.interactionId===e.interactionId}),i=-1===n?[...this.#s.interactions,s]:this.#s.interactions.map((e,t)=>t===n?s:e);this.#o({interactions:i})}#n(e){const t=this.#s.turn;"idle"!==t.status&&t.turnId===e.turnId&&C.has(t.status)||this.#o({turn:e})}#i(e,t){const s=this.#s.interactions.map(s=>{const n=g(s);return n?.interactionId===e?{status:t,request:n}:s});this.#o({interactions:s})}#o(e){const t={threadId:this.id,snapshot:this.#s.snapshot,liveAssistantMessages:e.liveAssistantMessages??this.#s.liveAssistantMessages,interactions:e.interactions??this.#s.interactions,turn:e.turn??this.#s.turn};this.#r(null===this.#s.snapshot?{status:"loading",...t,snapshot:null}:{status:"ready",...t,snapshot:this.#s.snapshot})}#r(e){this.#s=R.parse(e);for(const e of this.#t)P(e,this.#s)}}export class RelayClientImpl{#a;#c;#l=new Set;#d=new Map;#h=new Map;#u=new Map;#p={status:"idle"};#y;#m;#f="1.1";#g=new Map;#v;#w;#R;#S=0;#b=!1;#I=!1;#q=q;#k=!1;#C=0;#T=!1;#E;constructor(e,t){if(this.#a=e,this.#c=t,this.#E=e.requestTimeoutMs??b,!Number.isFinite(this.#E)||this.#E<=0)throw new TypeError("requestTimeoutMs must be a positive finite number")}async connect(e){if("connected"===this.#p.status)return;if(this.#b)throw T(f.closed,"Relay client is closed",!1);this.#P();const t=this.#v;return void 0!==t?t:(this.#I=!1,this.#S=0,this.#M({status:"connecting",attempt:1}),this.#A(e?.signal,e?.timeoutMs).catch(e=>{const t=E(e);throw this.#b||this.#I||this.#M({status:"closed",reason:"session"===t.details.kind?"session":"transport"}),t}))}close(){this.#b||(this.#b=!0,this.#I=!0,this.#w?.abort(),this.#P(),this.#B("client closed"),this.#O(!0),this.#M({status:"closed",reason:"client"}))}getConnectionState(){return this.#p}subscribeConnection(e){return this.#l.add(e),P(e,this.#p),()=>{this.#l.delete(e)}}async listThreads(e={},t){return this.#x(s.threadList,e,t)}async createThread(e={},t){const n=await this.#x(s.threadCreate,{...e,requestId:this.#c.createUuid()},t),i=this.#F(n.thread.id);return await i.refresh(t).catch(()=>{}),i}async openThread(e,t){const n=await this.#x(s.threadOpen,{threadId:e},t),i=this.#F(e);return i.applySnapshot(n),i}async startTurn(e,t,n){return this.#x(s.turnStart,{requestId:this.#c.createUuid(),threadId:e,turnId:this.#c.createUuid(),input:{text:t}},n)}async cancelTurn(e,t,n){return this.#x(s.turnCancel,{requestId:this.#c.createUuid(),threadId:e,turnId:t},n)}async respondToInteraction(e,t,n){const r=i(e,t);return this.#x(s.interactionCandidate,r,n)}async getThreadCapabilities(e,t){return this.#x("thread.capabilities",{threadId:e},t)}getOperationResult(e,t,s){if("1.2"!==this.#f)return Promise.reject(T(f.protocolError,"App results require Relay Wire 1.2",!1));const n=`${this.#C}:${this.#m}:${e}:${t}`;if(void 0===s){const e=this.#g.get(n);if(e)return e}const i=this.#x("operation.result.get",{threadId:e,operationId:t},s);return void 0===s&&(this.#g.set(n,i),i.finally(()=>{this.#g.get(n)===i&&this.#g.delete(n)}).catch(()=>{})),i}async getThreadSnapshot(e,t){return this.#x(s.threadSnapshot,{threadId:e,limit:100},t)}async#W(e,t){const s=t??this.#E;if(!Number.isFinite(s)||s<=0)throw new TypeError("timeoutMs must be a positive finite number");const n=new AbortController;this.#w=n;let i=!1;const r=()=>{n.abort()};e?.addEventListener("abort",r,{once:!0}),!0===e?.aborted&&n.abort();const o=this.#c.scheduler.setTimer(()=>{i=!0,n.abort()},s),a=new Promise((e,t)=>{const s=()=>{t(T(i?f.requestTimeout:f.aborted,i?"Relay session connection timed out":"Relay session request was aborted",i)),this.#y?.close(k.normal,"session attempt cancelled")};n.signal.aborted?s():n.signal.addEventListener("abort",s,{once:!0})});try{await Promise.race([this.#j(n.signal),a])}finally{this.#c.scheduler.clearTimer(o),e?.removeEventListener("abort",r),this.#w===n&&(this.#w=void 0)}}async#j(e){try{let t;try{t=await this.#a.getSession({signal:e,supportedRelayProtocolVersions:["1.2","1.1"]})}catch(t){if(e.aborted)throw T(f.aborted,"Relay session request was aborted",!1);throw E(t)}if(e.aborted)throw T(f.aborted,"Relay session request was aborted",!1);const s=y.safeParse(t);if(!s.success)throw T(f.invalidSession,"Session endpoint returned an invalid Relay session",!1);if(Date.parse(s.data.expiresAt)<=this.#c.scheduler.now())throw T(f.invalidSession,"Session endpoint returned an expired Relay session",!1);this.#B("superseded relay session");const n=this.#c.createWebSocket(s.data.connectUrl);this.#y=n,await this.#L(n)}catch(e){throw E(e)}}#L(e){return new Promise((t,s)=>{const n={ready:!1,settled:!1},i=e=>{n.settled||(n.settled=!0,s(e))};e.setHandlers({onOpen:()=>{},onMessage:s=>{if(!n.ready||this.#y===e)try{const e=M(s);if(!n.ready){const s=A(e);if(void 0!==this.#m&&this.#m!==s.workspaceId)throw T(f.protocolError,"Relay session changed the bound Workspace",!1);return this.#m=s.workspaceId,this.#f=s.relayProtocolVersion,this.#g.clear(),n.ready=!0,n.settled=!0,this.#S=0,this.#T=!0,this.#M({status:"connected",workspaceStatus:s.workspaceStatus}),this.#U(),"online"===s.workspaceStatus&&this.#z(),void t()}this.#$(e)}catch(t){const s=E(t);n.ready?this.#V(s):i(s),e.close(k.invalidFrame,"invalid relay frame")}},onClose:()=>{const t=this.#y===e;t&&(this.#y=void 0),n.ready?t&&this.#N():i(T(f.transportError,"Relay connection closed before session.ready",!0))},onError:()=>{n.ready||i(T(f.transportError,"Relay WebSocket failed before session.ready",!0))}})})}#$(e){const t=p.safeParse(e);if(t.success)return void this.#G(t.data);const s=c.safeParse(e);if(!s.success)throw T(f.protocolError,"Relay sent an invalid Control 1.0 or Wire 1.1 frame",!1);this.#H(s.data)}#G(e){if("session.ready"===e.type)throw T(f.protocolError,"session.ready may only be the first frame",!1);if("workspace.status"===e.type){this.#D(e.workspaceId);const t=this.#p,s="connected"===t.status&&"offline"===t.workspaceStatus&&"online"===e.status;return this.#M({status:"connected",workspaceStatus:e.status}),s&&(this.#T=!0,this.#J()),void("online"===e.status&&this.#z())}const t=new RelayClientError({kind:"session",code:e.code,message:`Relay session failed: ${e.code}`,retryable:e.retryable});if(e.retryable)this.#y?.close(k.sessionRetryable,"retryable relay session error");else{this.#I=!0,this.#O(!0);for(const e of this.#d.values())e.applyError(t.details);this.#M({status:"closed",reason:"session"}),this.#y?.close(k.sessionFatal,"non-retryable relay session error")}}#H(e){if(e.type!==t.runtimeResponse){if(e.type===t.runtimeGap)return this.#D(e.workspaceId),this.#Q(e.throughRelaySequence),void this.#K(e.throughRelaySequence);if(e.type!==t.runtimeEvent&&e.type!==t.interactionRequest&&e.type!==t.interactionResolved&&e.type!==t.interactionCancelled)throw T(f.protocolError,`Relay sent disallowed Browser inbound frame: ${e.type}`,!1);this.#X({relaySequence:e.relaySequence,message:e})}else this.#Y(e)}#Y(t){this.#D(t.workspaceId);const s=this.#h.get(t.requestId);if(void 0!==s){if(void 0!==t.error){const n=t.error.code===e.remoteRequestDenied?"Remote request denied":t.error.message;return void this.#Z(s,new RelayClientError({kind:"remote",code:t.error.code,message:n,retryable:t.error.retryable}))}if(void 0!==t.result)try{const e=o(this.#f,s.method,t.result);this.#Z(s,void 0,e)}catch{this.#Z(s,T(f.protocolError,`Relay returned an invalid ${s.method} result`,!1))}else this.#Z(s,T(f.protocolError,"runtime.response contains neither result nor error",!1))}}#X(e){if(this.#D(e.message.workspaceId),this.#Q(e.relaySequence),e.relaySequence<=this.#q)this.#U();else if(this.#k)this.#u.set(e.relaySequence,e);else{if(e.relaySequence!==this.#q+1)return this.#u.set(e.relaySequence,e),void this.#K(e.relaySequence-1);this.#_(e.message),this.#q=e.relaySequence,this.#U()}}#_(e){const s=e.type===t.runtimeEvent?e.event.threadId:e.threadId,n=this.#F(s);e.type===t.runtimeEvent?n.applyRuntimeEvent(e):e.type===t.interactionRequest?n.applyInteractionRequest(e):n.applyInteractionTerminal(e)}#K(e){if(e<=this.#q)return void this.#U();if(this.#k)return;this.#k=!0;const t=e,s=this.#C,n=[...this.#d.values()].map(async e=>e.refresh());Promise.all(n).then(()=>{if(this.#C===s){this.#q=t;for(const e of[...this.#u.keys()])e<=t&&this.#u.delete(e);this.#k=!1,this.#U(),this.#ee()}}).catch(()=>{this.#C===s&&(this.#k=!1,this.#y?.close(k.sessionRetryable,"snapshot recovery failed"))})}#Q(e){this.#T&&(this.#T=!1,e>this.#q||(this.#C+=1,this.#q=q,this.#u.clear(),this.#k=!1,this.#J()))}#J(){for(const e of this.#d.values())e.refresh().catch(()=>{})}#ee(){for(;!this.#k;){const e=this.#q+1,t=this.#u.get(e);if(void 0===t){const t=[...this.#u.keys()].sort((e,t)=>e-t)[0];return void(void 0!==t&&t>e&&this.#K(t-1))}this.#u.delete(e),this.#_(t.message),this.#q=e,this.#U()}}#U(){const e=this.#m,s=this.#y;if(void 0===e||1!==s?.readyState)return;const n=a.parse({type:t.runtimeAck,workspaceId:e,throughRelaySequence:this.#q});s.send(JSON.stringify(n))}#N(){this.#b||this.#I||(this.#te(),this.#se())}#se(){if(void 0!==this.#R||this.#b||this.#I)return;this.#S+=1;const e=this.#c.reconnectDelayMs(this.#S),t=new Date(this.#c.scheduler.now()+e).toISOString();this.#M({status:"reconnecting",attempt:this.#S,retryAt:t}),this.#R=this.#c.scheduler.setTimer(()=>{this.#R=void 0,this.#b||this.#I||void 0!==this.#v||"connected"===this.#p.status||this.#A().catch(e=>{if(this.#b)return;if(!E(e).details.retryable)return this.#I=!0,this.#O(!0),void this.#M({status:"closed",reason:"session"});this.#se()})},e)}#A(e,t){const s=this.#W(e,t).finally(()=>{this.#v===s&&(this.#v=void 0)});return this.#v=s,s}#B(e){const t=this.#y;void 0!==t&&(this.#y=void 0,t.close(k.normal,e))}#P(){void 0!==this.#R&&(this.#c.scheduler.clearTimer(this.#R),this.#R=void 0)}#z(){for(const e of this.#h.values())e.mutation&&this.#ne(e)}#x(e,s,i){if("connected"!==this.#p.status||"online"!==this.#p.workspaceStatus||void 0===this.#m){const e="connected"===this.#p.status&&"offline"===this.#p.workspaceStatus;return Promise.reject(T(e?f.workspaceOffline:f.connectionUnavailable,e?"The local Workspace is offline":"Relay is not connected",!0))}if(!0===i?.signal?.aborted)return Promise.reject(T(f.aborted,"Relay request was aborted",!1));const o=r("1.2",e,s),a=l.parse(this.#c.createUuid()),c=h.parse({type:t.runtimeRequest,requestId:a,workspaceId:this.#m,method:e,params:o}),d=i?.timeoutMs??this.#E;if(!Number.isFinite(d)||d<=0)return Promise.reject(new TypeError("timeoutMs must be a positive finite number"));const u="mutation"===n[e];return new Promise((t,s)=>{const n=i?.signal?()=>{const e=this.#h.get(a);if(void 0===e)return;const t=e.mutation&&e.sent;this.#Z(e,T(t?f.outcomeUnknown:f.aborted,t?"Mutation outcome is unknown after Browser cancellation":"Relay request was aborted",!1))}:void 0,r={requestId:a,method:e,frame:JSON.stringify(c),mutation:u,sent:!1,resolve:e=>{t(e)},reject:s,signal:i?.signal,abortListener:n,timeoutHandle:void 0};r.timeoutHandle=this.#c.scheduler.setTimer(()=>{const e=this.#h.get(a);if(void 0===e)return;const t=e.mutation&&e.sent;this.#Z(e,T(t?f.outcomeUnknown:f.requestTimeout,t?"Mutation outcome is unknown after Relay timeout":"Relay request timed out",!t))},d),i?.signal?.addEventListener("abort",n??(()=>{}),{once:!0}),this.#h.set(a,r),this.#ne(r)})}#ne(e){const t=this.#y;1===t?.readyState&&"connected"===this.#p.status&&"online"===this.#p.workspaceStatus&&(t.send(e.frame),e.sent=!0)}#Z(e,t,s){this.#h.delete(e.requestId)&&(void 0!==e.timeoutHandle&&this.#c.scheduler.clearTimer(e.timeoutHandle),void 0!==e.signal&&void 0!==e.abortListener&&e.signal.removeEventListener("abort",e.abortListener),void 0!==t?e.reject(t):e.resolve(s))}#te(){for(const e of[...this.#h.values()])e.mutation||this.#Z(e,T(f.transportError,"Relay disconnected before the query completed",!0))}#O(e){for(const t of[...this.#h.values()]){const s=e&&t.mutation&&t.sent;this.#Z(t,T(s?f.outcomeUnknown:f.closed,s?"Mutation outcome is unknown after Relay closed":"Relay client is closed",!1))}}#D(e){if(this.#m!==e)throw T(f.protocolError,"Relay frame does not match the bound Workspace",!1)}#F(e){const t=this.#d.get(e);if(void 0!==t)return t;const s=new RelayThreadImpl(this,e);return this.#d.set(e,s),s}#M(e){this.#p=w.parse(e);for(const e of this.#l)P(e,this.#p)}#V(e){this.#I=!0,this.#O(!0);for(const t of this.#d.values())t.applyError(e.details);this.#M({status:"closed",reason:"protocol"})}}export function createRelayClientWithRuntime(e,t){return new RelayClientImpl(e,t)}export function createRelayClient(e){return createRelayClientWithRuntime(e,S)}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { RelayClientError, createRelayClient, type CreateRelayClientOptions, type RelayClient, type RelayConnectionListener, type RelayInteractionCandidateResult, type RelayInteractionId, type RelayRequestOptions, type RelaySessionProvider, type RelaySessionProviderInput, type RelayThread, type RelayThreadCreateInput, type RelayThreadId, type RelayThreadListInput, type RelayThreadListResult, type RelayThreadListener, type RelayThreadSnapshot, type RelayThreadSummary, type RelayTurnCancelResult, type RelayTurnId, type RelayTurnStartResult, type RelayUnsubscribe, } from "./client.ts";
1
+ export { RelayClientError, createRelayClient, type CreateRelayClientOptions, type RelayClient, type RelayOperationResult, type RelayThreadCapabilities, type RelayConnectionListener, type RelayInteractionCandidateResult, type RelayInteractionId, type RelayRequestOptions, type RelaySessionProvider, type RelaySessionProviderInput, type RelayThread, type RelayThreadCreateInput, type RelayThreadId, type RelayThreadListInput, type RelayThreadListResult, type RelayThreadListener, type RelayThreadSnapshot, type RelayThreadSummary, type RelayTurnCancelResult, type RelayTurnId, type RelayTurnStartResult, type RelayUnsubscribe, } from "./client.ts";
2
2
  export { RELAY_CLIENT_TRANSPORT_ERROR_CODES, RELAY_CONNECTION_STATUSES, getPendingInteractionRequest, relayClientErrorDetailsSchema, relayClientTransportErrorCodeSchema, relayConnectionStateSchema, relayInteractionStateSchema, relayLiveAssistantMessageSchema, relayThreadViewSchema, relayTurnStateSchema, type RelayClientErrorDetails, type RelayClientTransportErrorCode, type RelayConnectionState, type RelayInteractionState, type RelayLiveAssistantMessage, type RelayThreadView, type RelayTurnState, } from "./schemas.ts";
package/dist/schemas.d.ts CHANGED
@@ -521,6 +521,26 @@ export declare const relayThreadViewSchema: z.ZodDiscriminatedUnion<[z.ZodReadon
521
521
  }, z.core.$strict>>>;
522
522
  nextBeforeSequence: z.ZodNullable<z.ZodNumber>;
523
523
  }, z.core.$strict>>;
524
+ activeTurn: z.ZodOptional<z.ZodReadonly<z.ZodObject<{
525
+ id: z.core.$ZodBranded<z.ZodString, "TurnId">;
526
+ status: z.ZodEnum<{
527
+ running: "running";
528
+ cancelling: "cancelling";
529
+ }>;
530
+ startedAt: z.ZodString;
531
+ }, z.core.$strict>>>;
532
+ pendingApprovals: z.ZodArray<z.ZodReadonly<z.ZodObject<{
533
+ id: z.core.$ZodBranded<z.ZodString, "ApprovalId">;
534
+ turnId: z.core.$ZodBranded<z.ZodString, "TurnId">;
535
+ agentName: z.ZodString;
536
+ toolName: z.ZodString;
537
+ preview: z.ZodJSONSchema;
538
+ reason: z.ZodOptional<z.ZodString>;
539
+ }, z.core.$strict>>>;
540
+ transcriptCompleteness: z.ZodEnum<{
541
+ complete: "complete";
542
+ legacy_snapshot: "legacy_snapshot";
543
+ }>;
524
544
  operations: z.ZodReadonly<z.ZodObject<{
525
545
  items: z.ZodArray<z.ZodReadonly<z.ZodObject<{
526
546
  id: z.core.$ZodBranded<z.ZodString, "OperationId">;
@@ -541,29 +561,29 @@ export declare const relayThreadViewSchema: z.ZodDiscriminatedUnion<[z.ZodReadon
541
561
  reason: z.ZodOptional<z.ZodString>;
542
562
  }, z.core.$strict>>;
543
563
  display: z.ZodJSONSchema;
564
+ appOutput: z.ZodOptional<z.ZodReadonly<z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodObject<{
565
+ status: z.ZodLiteral<"rejected">;
566
+ reason: z.ZodEnum<{
567
+ credential_field: "credential_field";
568
+ credential_value: "credential_value";
569
+ }>;
570
+ field: z.ZodOptional<z.ZodString>;
571
+ }, z.core.$strict>>, z.ZodObject<{
572
+ status: z.ZodLiteral<"available">;
573
+ schemaId: z.ZodString;
574
+ schemaVersion: z.ZodNumber;
575
+ }, z.core.$strict>, z.ZodObject<{
576
+ status: z.ZodEnum<{
577
+ not_provided: "not_provided";
578
+ invalid: "invalid";
579
+ too_large: "too_large";
580
+ expired: "expired";
581
+ denied: "denied";
582
+ }>;
583
+ }, z.core.$strict>]>>>;
544
584
  }, z.core.$strict>>>;
545
585
  nextBeforeSequence: z.ZodNullable<z.ZodNumber>;
546
586
  }, z.core.$strict>>;
547
- activeTurn: z.ZodOptional<z.ZodReadonly<z.ZodObject<{
548
- id: z.core.$ZodBranded<z.ZodString, "TurnId">;
549
- status: z.ZodEnum<{
550
- running: "running";
551
- cancelling: "cancelling";
552
- }>;
553
- startedAt: z.ZodString;
554
- }, z.core.$strict>>>;
555
- pendingApprovals: z.ZodArray<z.ZodReadonly<z.ZodObject<{
556
- id: z.core.$ZodBranded<z.ZodString, "ApprovalId">;
557
- turnId: z.core.$ZodBranded<z.ZodString, "TurnId">;
558
- agentName: z.ZodString;
559
- toolName: z.ZodString;
560
- preview: z.ZodJSONSchema;
561
- reason: z.ZodOptional<z.ZodString>;
562
- }, z.core.$strict>>>;
563
- transcriptCompleteness: z.ZodEnum<{
564
- complete: "complete";
565
- legacy_snapshot: "legacy_snapshot";
566
- }>;
567
587
  }, z.core.$strict>>;
568
588
  threadId: z.core.$ZodBranded<z.ZodString, "ThreadId">;
569
589
  liveAssistantMessages: z.ZodReadonly<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodObject<{
@@ -795,6 +815,26 @@ export declare const relayThreadViewSchema: z.ZodDiscriminatedUnion<[z.ZodReadon
795
815
  }, z.core.$strict>>>;
796
816
  nextBeforeSequence: z.ZodNullable<z.ZodNumber>;
797
817
  }, z.core.$strict>>;
818
+ activeTurn: z.ZodOptional<z.ZodReadonly<z.ZodObject<{
819
+ id: z.core.$ZodBranded<z.ZodString, "TurnId">;
820
+ status: z.ZodEnum<{
821
+ running: "running";
822
+ cancelling: "cancelling";
823
+ }>;
824
+ startedAt: z.ZodString;
825
+ }, z.core.$strict>>>;
826
+ pendingApprovals: z.ZodArray<z.ZodReadonly<z.ZodObject<{
827
+ id: z.core.$ZodBranded<z.ZodString, "ApprovalId">;
828
+ turnId: z.core.$ZodBranded<z.ZodString, "TurnId">;
829
+ agentName: z.ZodString;
830
+ toolName: z.ZodString;
831
+ preview: z.ZodJSONSchema;
832
+ reason: z.ZodOptional<z.ZodString>;
833
+ }, z.core.$strict>>>;
834
+ transcriptCompleteness: z.ZodEnum<{
835
+ complete: "complete";
836
+ legacy_snapshot: "legacy_snapshot";
837
+ }>;
798
838
  operations: z.ZodReadonly<z.ZodObject<{
799
839
  items: z.ZodArray<z.ZodReadonly<z.ZodObject<{
800
840
  id: z.core.$ZodBranded<z.ZodString, "OperationId">;
@@ -815,29 +855,29 @@ export declare const relayThreadViewSchema: z.ZodDiscriminatedUnion<[z.ZodReadon
815
855
  reason: z.ZodOptional<z.ZodString>;
816
856
  }, z.core.$strict>>;
817
857
  display: z.ZodJSONSchema;
858
+ appOutput: z.ZodOptional<z.ZodReadonly<z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodObject<{
859
+ status: z.ZodLiteral<"rejected">;
860
+ reason: z.ZodEnum<{
861
+ credential_field: "credential_field";
862
+ credential_value: "credential_value";
863
+ }>;
864
+ field: z.ZodOptional<z.ZodString>;
865
+ }, z.core.$strict>>, z.ZodObject<{
866
+ status: z.ZodLiteral<"available">;
867
+ schemaId: z.ZodString;
868
+ schemaVersion: z.ZodNumber;
869
+ }, z.core.$strict>, z.ZodObject<{
870
+ status: z.ZodEnum<{
871
+ not_provided: "not_provided";
872
+ invalid: "invalid";
873
+ too_large: "too_large";
874
+ expired: "expired";
875
+ denied: "denied";
876
+ }>;
877
+ }, z.core.$strict>]>>>;
818
878
  }, z.core.$strict>>>;
819
879
  nextBeforeSequence: z.ZodNullable<z.ZodNumber>;
820
880
  }, z.core.$strict>>;
821
- activeTurn: z.ZodOptional<z.ZodReadonly<z.ZodObject<{
822
- id: z.core.$ZodBranded<z.ZodString, "TurnId">;
823
- status: z.ZodEnum<{
824
- running: "running";
825
- cancelling: "cancelling";
826
- }>;
827
- startedAt: z.ZodString;
828
- }, z.core.$strict>>>;
829
- pendingApprovals: z.ZodArray<z.ZodReadonly<z.ZodObject<{
830
- id: z.core.$ZodBranded<z.ZodString, "ApprovalId">;
831
- turnId: z.core.$ZodBranded<z.ZodString, "TurnId">;
832
- agentName: z.ZodString;
833
- toolName: z.ZodString;
834
- preview: z.ZodJSONSchema;
835
- reason: z.ZodOptional<z.ZodString>;
836
- }, z.core.$strict>>>;
837
- transcriptCompleteness: z.ZodEnum<{
838
- complete: "complete";
839
- legacy_snapshot: "legacy_snapshot";
840
- }>;
841
881
  }, z.core.$strict>>>;
842
882
  error: z.ZodDiscriminatedUnion<[z.ZodReadonly<z.ZodObject<{
843
883
  code: z.ZodEnum<{
package/dist/schemas.js CHANGED
@@ -1 +1 @@
1
- import{relayInteractionRequestSchemaV11 as t,relayRequestMethodSchemasV11 as e}from"@roll-agent/relay-protocol";import{relayClientErrorCodeSchema as r}from"@roll-agent/relay-protocol/control";import{z as n}from"zod/v4";const a=e["thread.open"].params.def.innerType.shape.threadId,o=e["turn.cancel"].params.def.innerType.shape.turnId,s=e["thread.snapshot"].result;export const RELAY_CONNECTION_STATUSES={idle:"idle",connecting:"connecting",connected:"connected",reconnecting:"reconnecting",closed:"closed"};export const relayConnectionStateSchema=n.discriminatedUnion("status",[n.object({status:n.literal(RELAY_CONNECTION_STATUSES.idle)}).strict().readonly(),n.object({status:n.literal(RELAY_CONNECTION_STATUSES.connecting),attempt:n.number().int().positive()}).strict().readonly(),n.object({status:n.literal(RELAY_CONNECTION_STATUSES.connected),workspaceStatus:n.enum(["online","offline"])}).strict().readonly(),n.object({status:n.literal(RELAY_CONNECTION_STATUSES.reconnecting),attempt:n.number().int().positive(),retryAt:n.string().datetime({offset:!0})}).strict().readonly(),n.object({status:n.literal(RELAY_CONNECTION_STATUSES.closed),reason:n.enum(["client","session","transport","protocol"])}).strict().readonly()]);export const RELAY_CLIENT_TRANSPORT_ERROR_CODES={aborted:"ABORTED",closed:"CLIENT_CLOSED",connectionUnavailable:"CONNECTION_UNAVAILABLE",invalidSession:"INVALID_SESSION",outcomeUnknown:"OUTCOME_UNKNOWN",protocolError:"PROTOCOL_ERROR",requestTimeout:"REQUEST_TIMEOUT",transportError:"TRANSPORT_ERROR",workspaceOffline:"WORKSPACE_OFFLINE"};export const relayClientTransportErrorCodeSchema=n.enum(RELAY_CLIENT_TRANSPORT_ERROR_CODES);const i={message:n.string(),retryable:n.boolean()};export const relayClientErrorDetailsSchema=n.discriminatedUnion("kind",[n.object({kind:n.literal("transport"),...i,code:relayClientTransportErrorCodeSchema}).strict().readonly(),n.object({kind:n.literal("session"),...i,code:r}).strict().readonly(),n.object({kind:n.literal("remote"),...i,code:n.string().min(1)}).strict().readonly()]);export const relayLiveAssistantMessageSchema=n.discriminatedUnion("status",[n.object({status:n.literal("streaming"),streamId:n.string().uuid(),text:n.string()}).strict().readonly(),n.object({status:n.literal("completed"),streamId:n.string().uuid(),text:n.string()}).strict().readonly()]);const l={interactionId:t.options[0].def.innerType.shape.interactionId,threadId:a,turnId:o,method:t.options[0].def.innerType.shape.method.or(t.options[1].def.innerType.shape.method)};export const relayInteractionStateSchema=n.discriminatedUnion("status",[n.object({status:n.literal("pending"),request:t}).strict().readonly(),n.object({status:n.literal("responding"),request:t}).strict().readonly(),n.object({status:n.literal("resolved"),...l}).strict().readonly(),n.object({status:n.literal("cancelled"),...l}).strict().readonly()]);export const relayTurnStateSchema=n.discriminatedUnion("status",[n.object({status:n.literal("idle")}).strict().readonly(),n.object({status:n.literal("running"),turnId:o}).strict().readonly(),n.object({status:n.literal("cancelling"),turnId:o}).strict().readonly(),n.object({status:n.literal("completed"),turnId:o}).strict().readonly(),n.object({status:n.literal("cancelled"),turnId:o}).strict().readonly(),n.object({status:n.literal("failed"),turnId:o,stage:n.enum(["bootstrap","plan","execute"])}).strict().readonly()]);const c={threadId:a,liveAssistantMessages:n.array(relayLiveAssistantMessageSchema).readonly(),interactions:n.array(relayInteractionStateSchema).readonly(),turn:relayTurnStateSchema};export const relayThreadViewSchema=n.discriminatedUnion("status",[n.object({status:n.literal("loading"),...c,snapshot:n.null()}).strict().readonly(),n.object({status:n.literal("ready"),...c,snapshot:s}).strict().readonly(),n.object({status:n.literal("error"),...c,snapshot:s.nullable(),error:relayClientErrorDetailsSchema}).strict().readonly()]);export function getPendingInteractionRequest(t){return"pending"===t.status||"responding"===t.status?t.request:void 0}
1
+ import{relayInteractionRequestSchemaV11 as t,relayRequestMethodSchemasV12 as e}from"@roll-agent/relay-protocol";import{relayClientErrorCodeSchema as r}from"@roll-agent/relay-protocol/control";import{z as n}from"zod/v4";const a=e["thread.open"].params.def.innerType.shape.threadId,o=e["turn.cancel"].params.def.innerType.shape.turnId,s=e["thread.snapshot"].result;export const RELAY_CONNECTION_STATUSES={idle:"idle",connecting:"connecting",connected:"connected",reconnecting:"reconnecting",closed:"closed"};export const relayConnectionStateSchema=n.discriminatedUnion("status",[n.object({status:n.literal(RELAY_CONNECTION_STATUSES.idle)}).strict().readonly(),n.object({status:n.literal(RELAY_CONNECTION_STATUSES.connecting),attempt:n.number().int().positive()}).strict().readonly(),n.object({status:n.literal(RELAY_CONNECTION_STATUSES.connected),workspaceStatus:n.enum(["online","offline"])}).strict().readonly(),n.object({status:n.literal(RELAY_CONNECTION_STATUSES.reconnecting),attempt:n.number().int().positive(),retryAt:n.string().datetime({offset:!0})}).strict().readonly(),n.object({status:n.literal(RELAY_CONNECTION_STATUSES.closed),reason:n.enum(["client","session","transport","protocol"])}).strict().readonly()]);export const RELAY_CLIENT_TRANSPORT_ERROR_CODES={aborted:"ABORTED",closed:"CLIENT_CLOSED",connectionUnavailable:"CONNECTION_UNAVAILABLE",invalidSession:"INVALID_SESSION",outcomeUnknown:"OUTCOME_UNKNOWN",protocolError:"PROTOCOL_ERROR",requestTimeout:"REQUEST_TIMEOUT",transportError:"TRANSPORT_ERROR",workspaceOffline:"WORKSPACE_OFFLINE"};export const relayClientTransportErrorCodeSchema=n.enum(RELAY_CLIENT_TRANSPORT_ERROR_CODES);const i={message:n.string(),retryable:n.boolean()};export const relayClientErrorDetailsSchema=n.discriminatedUnion("kind",[n.object({kind:n.literal("transport"),...i,code:relayClientTransportErrorCodeSchema}).strict().readonly(),n.object({kind:n.literal("session"),...i,code:r}).strict().readonly(),n.object({kind:n.literal("remote"),...i,code:n.string().min(1)}).strict().readonly()]);export const relayLiveAssistantMessageSchema=n.discriminatedUnion("status",[n.object({status:n.literal("streaming"),streamId:n.string().uuid(),text:n.string()}).strict().readonly(),n.object({status:n.literal("completed"),streamId:n.string().uuid(),text:n.string()}).strict().readonly()]);const l={interactionId:t.options[0].def.innerType.shape.interactionId,threadId:a,turnId:o,method:t.options[0].def.innerType.shape.method.or(t.options[1].def.innerType.shape.method)};export const relayInteractionStateSchema=n.discriminatedUnion("status",[n.object({status:n.literal("pending"),request:t}).strict().readonly(),n.object({status:n.literal("responding"),request:t}).strict().readonly(),n.object({status:n.literal("resolved"),...l}).strict().readonly(),n.object({status:n.literal("cancelled"),...l}).strict().readonly()]);export const relayTurnStateSchema=n.discriminatedUnion("status",[n.object({status:n.literal("idle")}).strict().readonly(),n.object({status:n.literal("running"),turnId:o}).strict().readonly(),n.object({status:n.literal("cancelling"),turnId:o}).strict().readonly(),n.object({status:n.literal("completed"),turnId:o}).strict().readonly(),n.object({status:n.literal("cancelled"),turnId:o}).strict().readonly(),n.object({status:n.literal("failed"),turnId:o,stage:n.enum(["bootstrap","plan","execute"])}).strict().readonly()]);const c={threadId:a,liveAssistantMessages:n.array(relayLiveAssistantMessageSchema).readonly(),interactions:n.array(relayInteractionStateSchema).readonly(),turn:relayTurnStateSchema};export const relayThreadViewSchema=n.discriminatedUnion("status",[n.object({status:n.literal("loading"),...c,snapshot:n.null()}).strict().readonly(),n.object({status:n.literal("ready"),...c,snapshot:s}).strict().readonly(),n.object({status:n.literal("error"),...c,snapshot:s.nullable(),error:relayClientErrorDetailsSchema}).strict().readonly()]);export function getPendingInteractionRequest(t){return"pending"===t.status||"responding"===t.status?t.request:void 0}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roll-agent/relay-client",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "repository": {
@@ -31,8 +31,8 @@
31
31
  "node": ">=22.6.0"
32
32
  },
33
33
  "dependencies": {
34
- "zod": "3.25.76",
35
- "@roll-agent/relay-protocol": "0.3.3"
34
+ "@roll-agent/relay-protocol": "0.4.1",
35
+ "zod": "3.25.76"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^22.0.0",