@finalai/agents 0.8.0
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.
Potentially problematic release.
This version of @finalai/agents might be problematic. Click here for more details.
- package/README.md +30 -0
- package/dist/modules/agent-client/acp-frames.d.mts +19 -0
- package/dist/modules/agent-client/acp-frames.mjs +49 -0
- package/dist/modules/agent-client/acp-frames.mjs.map +7 -0
- package/dist/modules/agent-client/acp.d.mts +43 -0
- package/dist/modules/agent-client/acp.mjs +76 -0
- package/dist/modules/agent-client/acp.mjs.map +7 -0
- package/dist/modules/agent-client/async-queue.d.mts +10 -0
- package/dist/modules/agent-client/async-queue.mjs +66 -0
- package/dist/modules/agent-client/async-queue.mjs.map +7 -0
- package/dist/modules/agent-client/build.d.mts +107 -0
- package/dist/modules/agent-client/build.mjs +222 -0
- package/dist/modules/agent-client/build.mjs.map +7 -0
- package/dist/modules/agent-client/client.d.mts +111 -0
- package/dist/modules/agent-client/client.mjs +198 -0
- package/dist/modules/agent-client/client.mjs.map +7 -0
- package/dist/modules/agent-client/collection-events.d.mts +35 -0
- package/dist/modules/agent-client/collection-events.mjs +142 -0
- package/dist/modules/agent-client/collection-events.mjs.map +7 -0
- package/dist/modules/agent-client/config-options.d.mts +24 -0
- package/dist/modules/agent-client/config-options.mjs +49 -0
- package/dist/modules/agent-client/config-options.mjs.map +7 -0
- package/dist/modules/agent-client/context.d.mts +31 -0
- package/dist/modules/agent-client/context.mjs +11 -0
- package/dist/modules/agent-client/context.mjs.map +7 -0
- package/dist/modules/agent-client/conversations.d.mts +385 -0
- package/dist/modules/agent-client/conversations.mjs +1156 -0
- package/dist/modules/agent-client/conversations.mjs.map +7 -0
- package/dist/modules/agent-client/errors.d.mts +85 -0
- package/dist/modules/agent-client/errors.mjs +94 -0
- package/dist/modules/agent-client/errors.mjs.map +7 -0
- package/dist/modules/agent-client/event-stream.d.mts +14 -0
- package/dist/modules/agent-client/event-stream.mjs +89 -0
- package/dist/modules/agent-client/event-stream.mjs.map +7 -0
- package/dist/modules/agent-client/frame-stream.d.mts +51 -0
- package/dist/modules/agent-client/frame-stream.mjs +333 -0
- package/dist/modules/agent-client/frame-stream.mjs.map +7 -0
- package/dist/modules/agent-client/http.d.mts +68 -0
- package/dist/modules/agent-client/http.mjs +168 -0
- package/dist/modules/agent-client/http.mjs.map +7 -0
- package/dist/modules/agent-client/index.d.mts +17 -0
- package/dist/modules/agent-client/index.mjs +86 -0
- package/dist/modules/agent-client/index.mjs.map +7 -0
- package/dist/modules/agent-client/live-stream.d.mts +18 -0
- package/dist/modules/agent-client/live-stream.mjs +94 -0
- package/dist/modules/agent-client/live-stream.mjs.map +7 -0
- package/dist/modules/agent-client/realtime.d.mts +72 -0
- package/dist/modules/agent-client/realtime.mjs +186 -0
- package/dist/modules/agent-client/realtime.mjs.map +7 -0
- package/dist/modules/agent-client/sandbox-uri.d.mts +19 -0
- package/dist/modules/agent-client/sandbox-uri.mjs +43 -0
- package/dist/modules/agent-client/sandbox-uri.mjs.map +7 -0
- package/dist/modules/agent-client/sessions.d.mts +124 -0
- package/dist/modules/agent-client/sessions.mjs +350 -0
- package/dist/modules/agent-client/sessions.mjs.map +7 -0
- package/dist/modules/agent-client/test-fakes.d.mts +40 -0
- package/dist/modules/agent-client/test-fakes.mjs +94 -0
- package/dist/modules/agent-client/test-fakes.mjs.map +7 -0
- package/dist/modules/authoring/approval.d.mts +1 -0
- package/dist/modules/authoring/approval.mjs +2 -0
- package/dist/modules/authoring/approval.mjs.map +7 -0
- package/dist/modules/authoring/channels.d.mts +1 -0
- package/dist/modules/authoring/channels.mjs +2 -0
- package/dist/modules/authoring/channels.mjs.map +7 -0
- package/dist/modules/authoring/final-channel.d.mts +1 -0
- package/dist/modules/authoring/final-channel.mjs +2 -0
- package/dist/modules/authoring/final-channel.mjs.map +7 -0
- package/dist/modules/authoring/hooks.d.mts +1 -0
- package/dist/modules/authoring/hooks.mjs +2 -0
- package/dist/modules/authoring/hooks.mjs.map +7 -0
- package/dist/modules/authoring/index.d.mts +1 -0
- package/dist/modules/authoring/index.mjs +2 -0
- package/dist/modules/authoring/index.mjs.map +7 -0
- package/dist/modules/authoring/instructions.d.mts +1 -0
- package/dist/modules/authoring/instructions.mjs +2 -0
- package/dist/modules/authoring/instructions.mjs.map +7 -0
- package/dist/modules/authoring/providers.d.mts +2 -0
- package/dist/modules/authoring/providers.mjs +1 -0
- package/dist/modules/authoring/providers.mjs.map +7 -0
- package/dist/modules/authoring/tools.d.mts +1 -0
- package/dist/modules/authoring/tools.mjs +2 -0
- package/dist/modules/authoring/tools.mjs.map +7 -0
- package/dist/modules/cli/build.d.mts +22 -0
- package/dist/modules/cli/build.mjs +77 -0
- package/dist/modules/cli/build.mjs.map +7 -0
- package/dist/modules/cli/channel-socket.d.mts +9 -0
- package/dist/modules/cli/channel-socket.mjs +114 -0
- package/dist/modules/cli/channel-socket.mjs.map +7 -0
- package/dist/modules/cli/cli.d.mts +16 -0
- package/dist/modules/cli/cli.mjs +151 -0
- package/dist/modules/cli/cli.mjs.map +7 -0
- package/dist/modules/cli/conformance.d.mts +36 -0
- package/dist/modules/cli/conformance.mjs +220 -0
- package/dist/modules/cli/conformance.mjs.map +7 -0
- package/dist/modules/cli/dev.d.mts +24 -0
- package/dist/modules/cli/dev.mjs +263 -0
- package/dist/modules/cli/dev.mjs.map +7 -0
- package/dist/modules/cli/files.d.mts +18 -0
- package/dist/modules/cli/files.mjs +66 -0
- package/dist/modules/cli/files.mjs.map +7 -0
- package/dist/modules/cli/harness-descriptions.d.mts +20 -0
- package/dist/modules/cli/harness-descriptions.mjs +70 -0
- package/dist/modules/cli/harness-descriptions.mjs.map +7 -0
- package/dist/modules/cli/main.d.mts +2 -0
- package/dist/modules/cli/main.mjs +20 -0
- package/dist/modules/cli/main.mjs.map +7 -0
- package/dist/modules/cli/output.d.mts +14 -0
- package/dist/modules/cli/output.mjs +94 -0
- package/dist/modules/cli/output.mjs.map +7 -0
- package/dist/modules/cli/publish.d.mts +14 -0
- package/dist/modules/cli/publish.mjs +89 -0
- package/dist/modules/cli/publish.mjs.map +7 -0
- package/dist/modules/compile.d.mts +3 -0
- package/dist/modules/compile.mjs +62 -0
- package/dist/modules/compile.mjs.map +7 -0
- package/dist/modules/harnesses.d.mts +1 -0
- package/dist/modules/harnesses.mjs +2 -0
- package/dist/modules/harnesses.mjs.map +7 -0
- package/dist/modules/index.browser.d.mts +4 -0
- package/dist/modules/index.browser.mjs +8 -0
- package/dist/modules/index.browser.mjs.map +7 -0
- package/dist/modules/index.d.mts +3 -0
- package/dist/modules/index.mjs +6 -0
- package/dist/modules/index.mjs.map +7 -0
- package/dist/modules/models.d.mts +1 -0
- package/dist/modules/models.mjs +2 -0
- package/dist/modules/models.mjs.map +7 -0
- package/dist/modules/public.d.mts +6 -0
- package/dist/modules/public.mjs +68 -0
- package/dist/modules/public.mjs.map +7 -0
- package/dist/modules/sandboxes.d.mts +1 -0
- package/dist/modules/sandboxes.mjs +2 -0
- package/dist/modules/sandboxes.mjs.map +7 -0
- package/examples/custom-harness/README.md +16 -0
- package/examples/custom-harness/agent.ts +154 -0
- package/examples/custom-sandbox/README.md +12 -0
- package/examples/custom-sandbox/agent.ts +14 -0
- package/examples/custom-sandbox/sandbox.ts +22 -0
- package/examples/release-scout/README.md +11 -0
- package/examples/release-scout/agent/agent.ts +22 -0
- package/examples/release-scout/agent/channels/github-signature.ts +27 -0
- package/examples/release-scout/agent/channels/github.ts +38 -0
- package/examples/release-scout/agent/connections/linear.ts +9 -0
- package/examples/release-scout/agent/hooks/audit.ts +24 -0
- package/examples/release-scout/agent/instructions/org-context.ts +26 -0
- package/examples/release-scout/agent/instructions.md +2 -0
- package/examples/release-scout/agent/sandbox.ts +20 -0
- package/examples/release-scout/agent/schedules/daily-digest.md +7 -0
- package/examples/release-scout/agent/skills/triage/SKILL.md +8 -0
- package/examples/release-scout/agent/skills/triage/checklist.md +3 -0
- package/examples/release-scout/agent/tools/lookup_release_notes.ts +24 -0
- package/examples/release-scout/client.ts +123 -0
- package/package.json +126 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export * from '@finalai/agent-protocol/agent-platform';
|
|
2
|
+
export { AcpAuthMode, type AcpApiIface, type AcpConnectArgsIface } from "./acp.mjs";
|
|
3
|
+
export { createTextCollector, isUserMessageFor, sessionUpdateOf, textOf, turnEndOf, type AcpSessionUpdateIface, type TurnEndIface, } from "./acp-frames.mjs";
|
|
4
|
+
export type { ComputerSetupGuidesApiIface, DefinitionRefIface, DefinitionsApiIface, ProvidersApiIface, ScheduleRef, SchedulesApiIface, } from "./build.mjs";
|
|
5
|
+
export { createClient, type AgentClientIface, type AgentHandleIface, type AgentsApiIface, type CreateClientOptionsIface, type PendingApiIface, type PeopleApiIface, type RealtimeApiIface, } from "./client.mjs";
|
|
6
|
+
export { CollectionEventKind, CollectionResyncReason, type CollectionEvent, type CollectionEventsOptionsIface, type CollectionResyncEventIface, } from "./collection-events.mjs";
|
|
7
|
+
export { ConfigOptionCategory, configSelectFor, configSelectsOf, type ConfigSelectChoiceIface, type ConfigSelectIface, type SessionConfigOptionIface, } from "./config-options.mjs";
|
|
8
|
+
export type { EnumInput, SignalOptionsIface } from "./context.mjs";
|
|
9
|
+
export { TURN_OPEN_SESSION_STATES, isNewerMessageVersion, messageVersionOf, type MessageVersionIface, } from "./conversations.mjs";
|
|
10
|
+
export type { ConversationAgentTurnIface, ConversationEvent, ConversationEventMetaIface, ConversationEventsOptionsIface, ConversationHandleIface, ConversationsApiIface, CreateConversationArgsIface, ListMessagesOptionsIface, OpenedConversationIface, ParticipantCreateInput, SandboxFileContentArgsIface, SandboxFileContentRequestIface, SendMessageOptionsIface, SentMessageIface, UploadAttachmentArgsIface, } from "./conversations.mjs";
|
|
11
|
+
export { AgentClientErrorName, ClientErrorCode, PendingRequestErrorReason, clientError, isClientError, isPendingRequestError, isRealtimeSubscriptionError, isTerminalRealtimeError, isTurnRejectedError, pendingRequestError, realtimeSubscriptionError, turnRejectedError, type AgentClientError, type ClientErrorIface, type PendingRequestErrorIface, type RealtimeSubscriptionErrorIface, type TurnRejectedErrorIface, } from "./errors.mjs";
|
|
12
|
+
export { defaultStreamTiming, type StreamTimingIface } from "./frame-stream.mjs";
|
|
13
|
+
export type { AgentClientAuth, FetchIface, HeadersOption } from "./http.mjs";
|
|
14
|
+
export { SessionLiveEventKind, type SessionLiveEvent } from "./live-stream.mjs";
|
|
15
|
+
export { createGatewayRealtimeTransport, type GatewayConnect, type GatewayConnectionIface, type GatewayRealtimeTransportOptionsIface, type RealtimeSubscribeOptionsIface, type RealtimeTransportFrameIface, type RealtimeTransportIface, } from "./realtime.mjs";
|
|
16
|
+
export { SANDBOX_FILE_URI_SCHEME, createSandboxFileUri, parseSandboxFileUri, type SandboxFileTargetIface, } from "./sandbox-uri.mjs";
|
|
17
|
+
export type { CreateSessionArgsIface, CreatedSessionIface, ForkSessionOptionsIface, RespondArgs, SendOptionsIface, SessionHandleIface, SessionsApiIface, StreamOptionsIface, TurnHandleIface, TurnResultIface, } from "./sessions.mjs";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export * from "@finalai/agent-protocol/agent-platform";
|
|
2
|
+
import { AcpAuthMode } from "./acp.mjs";
|
|
3
|
+
import {
|
|
4
|
+
createTextCollector,
|
|
5
|
+
isUserMessageFor,
|
|
6
|
+
sessionUpdateOf,
|
|
7
|
+
textOf,
|
|
8
|
+
turnEndOf
|
|
9
|
+
} from "./acp-frames.mjs";
|
|
10
|
+
import {
|
|
11
|
+
createClient
|
|
12
|
+
} from "./client.mjs";
|
|
13
|
+
import {
|
|
14
|
+
CollectionEventKind,
|
|
15
|
+
CollectionResyncReason
|
|
16
|
+
} from "./collection-events.mjs";
|
|
17
|
+
import {
|
|
18
|
+
ConfigOptionCategory,
|
|
19
|
+
configSelectFor,
|
|
20
|
+
configSelectsOf
|
|
21
|
+
} from "./config-options.mjs";
|
|
22
|
+
import {
|
|
23
|
+
TURN_OPEN_SESSION_STATES,
|
|
24
|
+
isNewerMessageVersion,
|
|
25
|
+
messageVersionOf
|
|
26
|
+
} from "./conversations.mjs";
|
|
27
|
+
import {
|
|
28
|
+
AgentClientErrorName,
|
|
29
|
+
ClientErrorCode,
|
|
30
|
+
PendingRequestErrorReason,
|
|
31
|
+
clientError,
|
|
32
|
+
isClientError,
|
|
33
|
+
isPendingRequestError,
|
|
34
|
+
isRealtimeSubscriptionError,
|
|
35
|
+
isTerminalRealtimeError,
|
|
36
|
+
isTurnRejectedError,
|
|
37
|
+
pendingRequestError,
|
|
38
|
+
realtimeSubscriptionError,
|
|
39
|
+
turnRejectedError
|
|
40
|
+
} from "./errors.mjs";
|
|
41
|
+
import { defaultStreamTiming } from "./frame-stream.mjs";
|
|
42
|
+
import { SessionLiveEventKind } from "./live-stream.mjs";
|
|
43
|
+
import {
|
|
44
|
+
createGatewayRealtimeTransport
|
|
45
|
+
} from "./realtime.mjs";
|
|
46
|
+
import {
|
|
47
|
+
SANDBOX_FILE_URI_SCHEME,
|
|
48
|
+
createSandboxFileUri,
|
|
49
|
+
parseSandboxFileUri
|
|
50
|
+
} from "./sandbox-uri.mjs";
|
|
51
|
+
export {
|
|
52
|
+
AcpAuthMode,
|
|
53
|
+
AgentClientErrorName,
|
|
54
|
+
ClientErrorCode,
|
|
55
|
+
CollectionEventKind,
|
|
56
|
+
CollectionResyncReason,
|
|
57
|
+
ConfigOptionCategory,
|
|
58
|
+
PendingRequestErrorReason,
|
|
59
|
+
SANDBOX_FILE_URI_SCHEME,
|
|
60
|
+
SessionLiveEventKind,
|
|
61
|
+
TURN_OPEN_SESSION_STATES,
|
|
62
|
+
clientError,
|
|
63
|
+
configSelectFor,
|
|
64
|
+
configSelectsOf,
|
|
65
|
+
createClient,
|
|
66
|
+
createGatewayRealtimeTransport,
|
|
67
|
+
createSandboxFileUri,
|
|
68
|
+
createTextCollector,
|
|
69
|
+
defaultStreamTiming,
|
|
70
|
+
isClientError,
|
|
71
|
+
isNewerMessageVersion,
|
|
72
|
+
isPendingRequestError,
|
|
73
|
+
isRealtimeSubscriptionError,
|
|
74
|
+
isTerminalRealtimeError,
|
|
75
|
+
isTurnRejectedError,
|
|
76
|
+
isUserMessageFor,
|
|
77
|
+
messageVersionOf,
|
|
78
|
+
parseSandboxFileUri,
|
|
79
|
+
pendingRequestError,
|
|
80
|
+
realtimeSubscriptionError,
|
|
81
|
+
sessionUpdateOf,
|
|
82
|
+
textOf,
|
|
83
|
+
turnEndOf,
|
|
84
|
+
turnRejectedError
|
|
85
|
+
};
|
|
86
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/agent-client/index.ts"],
|
|
4
|
+
"sourcesContent": ["// `@finalai/agents/client`: the agent platform client SDK.\n// Wire types come from `@finalai/agent-protocol/agent-platform`, re-exported\n// here so consumers need one import.\n\nexport * from '@finalai/agent-protocol/agent-platform';\nexport { AcpAuthMode, type AcpApiIface, type AcpConnectArgsIface } from \"./acp.mjs\";\nexport {\n createTextCollector,\n isUserMessageFor,\n sessionUpdateOf,\n textOf,\n turnEndOf,\n type AcpSessionUpdateIface,\n type TurnEndIface,\n} from \"./acp-frames.mjs\";\nexport type {\n ComputerSetupGuidesApiIface,\n DefinitionRefIface,\n DefinitionsApiIface,\n ProvidersApiIface,\n ScheduleRef,\n SchedulesApiIface,\n} from \"./build.mjs\";\nexport {\n createClient,\n type AgentClientIface,\n type AgentHandleIface,\n type AgentsApiIface,\n type CreateClientOptionsIface,\n type PendingApiIface,\n type PeopleApiIface,\n type RealtimeApiIface,\n} from \"./client.mjs\";\nexport {\n CollectionEventKind,\n CollectionResyncReason,\n type CollectionEvent,\n type CollectionEventsOptionsIface,\n type CollectionResyncEventIface,\n} from \"./collection-events.mjs\";\nexport {\n ConfigOptionCategory,\n configSelectFor,\n configSelectsOf,\n type ConfigSelectChoiceIface,\n type ConfigSelectIface,\n type SessionConfigOptionIface,\n} from \"./config-options.mjs\";\nexport type { EnumInput, SignalOptionsIface } from \"./context.mjs\";\nexport {\n TURN_OPEN_SESSION_STATES,\n isNewerMessageVersion,\n messageVersionOf,\n type MessageVersionIface,\n} from \"./conversations.mjs\";\nexport type {\n ConversationAgentTurnIface,\n ConversationEvent,\n ConversationEventMetaIface,\n ConversationEventsOptionsIface,\n ConversationHandleIface,\n ConversationsApiIface,\n CreateConversationArgsIface,\n ListMessagesOptionsIface,\n OpenedConversationIface,\n ParticipantCreateInput,\n SandboxFileContentArgsIface,\n SandboxFileContentRequestIface,\n SendMessageOptionsIface,\n SentMessageIface,\n UploadAttachmentArgsIface,\n} from \"./conversations.mjs\";\nexport {\n AgentClientErrorName,\n ClientErrorCode,\n PendingRequestErrorReason,\n clientError,\n isClientError,\n isPendingRequestError,\n isRealtimeSubscriptionError,\n isTerminalRealtimeError,\n isTurnRejectedError,\n pendingRequestError,\n realtimeSubscriptionError,\n turnRejectedError,\n type AgentClientError,\n type ClientErrorIface,\n type PendingRequestErrorIface,\n type RealtimeSubscriptionErrorIface,\n type TurnRejectedErrorIface,\n} from \"./errors.mjs\";\nexport { defaultStreamTiming, type StreamTimingIface } from \"./frame-stream.mjs\";\nexport type { AgentClientAuth, FetchIface, HeadersOption } from \"./http.mjs\";\nexport { SessionLiveEventKind, type SessionLiveEvent } from \"./live-stream.mjs\";\nexport {\n createGatewayRealtimeTransport,\n type GatewayConnect,\n type GatewayConnectionIface,\n type GatewayRealtimeTransportOptionsIface,\n type RealtimeSubscribeOptionsIface,\n type RealtimeTransportFrameIface,\n type RealtimeTransportIface,\n} from \"./realtime.mjs\";\nexport {\n SANDBOX_FILE_URI_SCHEME,\n createSandboxFileUri,\n parseSandboxFileUri,\n type SandboxFileTargetIface,\n} from \"./sandbox-uri.mjs\";\nexport type {\n CreateSessionArgsIface,\n CreatedSessionIface,\n ForkSessionOptionsIface,\n RespondArgs,\n SendOptionsIface,\n SessionHandleIface,\n SessionsApiIface,\n StreamOptionsIface,\n TurnHandleIface,\n TurnResultIface,\n} from \"./sessions.mjs\";\n"],
|
|
5
|
+
"mappings": "AAIA,cAAc;AACd,SAAS,mBAA+D;AACxE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AASP;AAAA,EACE;AAAA,OAQK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAkBP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAMK;AACP,SAAS,2BAAmD;AAE5D,SAAS,4BAAmD;AAC5D;AAAA,EACE;AAAA,OAOK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type AcpLiveFrameEventIface, type AcpLiveResetEventIface } from '@finalai/agent-protocol/agent-platform';
|
|
2
|
+
import type { ClientContextIface } from "./context.mjs";
|
|
3
|
+
export declare enum SessionLiveEventKind {
|
|
4
|
+
Frame = "frame",
|
|
5
|
+
Reset = "reset"
|
|
6
|
+
}
|
|
7
|
+
export type SessionLiveEvent = {
|
|
8
|
+
kind: SessionLiveEventKind.Frame;
|
|
9
|
+
frame: AcpLiveFrameEventIface;
|
|
10
|
+
} | {
|
|
11
|
+
kind: SessionLiveEventKind.Reset;
|
|
12
|
+
reset: AcpLiveResetEventIface;
|
|
13
|
+
};
|
|
14
|
+
export declare const streamSessionLiveEvents: (args: {
|
|
15
|
+
context: ClientContextIface;
|
|
16
|
+
sessionId: string;
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
}) => AsyncIterableIterator<SessionLiveEvent>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AgentRealtimeFrameType,
|
|
3
|
+
AgentRealtimeKind,
|
|
4
|
+
acpLiveFrameEventSchema,
|
|
5
|
+
acpLiveResetEventSchema,
|
|
6
|
+
agentRealtimeFrameSchema,
|
|
7
|
+
agentRealtimeTopics
|
|
8
|
+
} from "@finalai/agent-protocol/agent-platform";
|
|
9
|
+
import { createAsyncQueue, failOnAbort } from "./async-queue.mjs";
|
|
10
|
+
import { isTerminalRealtimeError, realtimeSubscriptionError } from "./errors.mjs";
|
|
11
|
+
var SessionLiveEventKind = /* @__PURE__ */ ((SessionLiveEventKind2) => {
|
|
12
|
+
SessionLiveEventKind2["Frame"] = "frame";
|
|
13
|
+
SessionLiveEventKind2["Reset"] = "reset";
|
|
14
|
+
return SessionLiveEventKind2;
|
|
15
|
+
})(SessionLiveEventKind || {});
|
|
16
|
+
const streamSessionLiveEvents = (args) => {
|
|
17
|
+
const topic = agentRealtimeTopics.session(args.sessionId);
|
|
18
|
+
let unsubscribe = null;
|
|
19
|
+
let detachAbort = () => void 0;
|
|
20
|
+
let detachClose = () => void 0;
|
|
21
|
+
let bufferedBytes = 0;
|
|
22
|
+
let bufferedFrames = 0;
|
|
23
|
+
const queue = createAsyncQueue(() => {
|
|
24
|
+
unsubscribe?.();
|
|
25
|
+
detachAbort();
|
|
26
|
+
detachClose();
|
|
27
|
+
});
|
|
28
|
+
const push = (event) => {
|
|
29
|
+
const bytes = JSON.stringify(event).length * 3;
|
|
30
|
+
if (bufferedFrames >= 256 || bufferedBytes + bytes > 8 * 1024 * 1024) {
|
|
31
|
+
queue.fail(new Error("Live frame capacity exceeded; reconnect and replay durable frames"));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
bufferedBytes += bytes;
|
|
35
|
+
bufferedFrames += 1;
|
|
36
|
+
queue.push({ event, bytes });
|
|
37
|
+
};
|
|
38
|
+
const onFrame = (raw) => {
|
|
39
|
+
const frame = agentRealtimeFrameSchema.safeParse(raw);
|
|
40
|
+
if (!frame.success || queue.closed()) return;
|
|
41
|
+
if (frame.data.type === AgentRealtimeFrameType.Error && isTerminalRealtimeError(frame.data)) {
|
|
42
|
+
queue.fail(
|
|
43
|
+
realtimeSubscriptionError({
|
|
44
|
+
topic: frame.data.topic ?? topic,
|
|
45
|
+
code: frame.data.code ?? "unknown",
|
|
46
|
+
message: frame.data.message ?? "Realtime subscription was refused"
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (frame.data.type === AgentRealtimeFrameType.Subscribed || frame.data.type === AgentRealtimeFrameType.ResyncRequired || frame.data.type === AgentRealtimeFrameType.Error) {
|
|
52
|
+
push({
|
|
53
|
+
kind: "reset" /* Reset */,
|
|
54
|
+
reset: { streamId: null, throughSeq: null, reason: "reconnect" }
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (frame.data.type !== AgentRealtimeFrameType.Event) return;
|
|
59
|
+
if (frame.data.kind === AgentRealtimeKind.AcpLiveFrame) {
|
|
60
|
+
const payload = acpLiveFrameEventSchema.safeParse(frame.data.payload);
|
|
61
|
+
if (payload.success) push({ kind: "frame" /* Frame */, frame: payload.data });
|
|
62
|
+
} else if (frame.data.kind === AgentRealtimeKind.AcpLiveReset) {
|
|
63
|
+
const payload = acpLiveResetEventSchema.safeParse(frame.data.payload);
|
|
64
|
+
if (payload.success) push({ kind: "reset" /* Reset */, reset: payload.data });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
detachAbort = failOnAbort(queue, args.signal);
|
|
68
|
+
detachClose = failOnAbort(queue, args.context.closeSignal);
|
|
69
|
+
if (!queue.closed()) {
|
|
70
|
+
const stop = args.context.realtime().subscribe(topic, onFrame);
|
|
71
|
+
if (queue.closed()) stop();
|
|
72
|
+
else unsubscribe = stop;
|
|
73
|
+
}
|
|
74
|
+
const iterator = {
|
|
75
|
+
next: async () => {
|
|
76
|
+
const value = await queue.iterator.next();
|
|
77
|
+
if (value.done === true) return value;
|
|
78
|
+
bufferedBytes -= value.value.bytes;
|
|
79
|
+
bufferedFrames -= 1;
|
|
80
|
+
return { done: false, value: value.value.event };
|
|
81
|
+
},
|
|
82
|
+
return: async () => {
|
|
83
|
+
await queue.iterator.return?.();
|
|
84
|
+
return { done: true, value: void 0 };
|
|
85
|
+
},
|
|
86
|
+
[Symbol.asyncIterator]: () => iterator
|
|
87
|
+
};
|
|
88
|
+
return iterator;
|
|
89
|
+
};
|
|
90
|
+
export {
|
|
91
|
+
SessionLiveEventKind,
|
|
92
|
+
streamSessionLiveEvents
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=live-stream.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/agent-client/live-stream.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n AgentRealtimeFrameType,\n AgentRealtimeKind,\n acpLiveFrameEventSchema,\n acpLiveResetEventSchema,\n agentRealtimeFrameSchema,\n agentRealtimeTopics,\n type AcpLiveFrameEventIface,\n type AcpLiveResetEventIface,\n} from '@finalai/agent-protocol/agent-platform';\nimport { createAsyncQueue, failOnAbort } from \"./async-queue.mjs\";\nimport type { ClientContextIface } from \"./context.mjs\";\nimport { isTerminalRealtimeError, realtimeSubscriptionError } from \"./errors.mjs\";\nimport type { RealtimeTransportFrameIface } from \"./realtime.mjs\";\n\nexport enum SessionLiveEventKind {\n Frame = 'frame',\n Reset = 'reset',\n}\n\nexport type SessionLiveEvent =\n | { kind: SessionLiveEventKind.Frame; frame: AcpLiveFrameEventIface }\n | { kind: SessionLiveEventKind.Reset; reset: AcpLiveResetEventIface };\n\n// Provisional rendering only. Replay positions and completed operations come\n// from the ordinary durable stream, never from this iterator's seq values.\nexport const streamSessionLiveEvents = (args: {\n context: ClientContextIface;\n sessionId: string;\n signal?: AbortSignal;\n}): AsyncIterableIterator<SessionLiveEvent> => {\n const topic = agentRealtimeTopics.session(args.sessionId);\n let unsubscribe: (() => void) | null = null;\n let detachAbort: () => void = () => undefined;\n let detachClose: () => void = () => undefined;\n let bufferedBytes = 0;\n let bufferedFrames = 0;\n const queue = createAsyncQueue<{ event: SessionLiveEvent; bytes: number }>(() => {\n unsubscribe?.();\n detachAbort();\n detachClose();\n });\n const push = (event: SessionLiveEvent): void => {\n const bytes = JSON.stringify(event).length * 3;\n if (bufferedFrames >= 256 || bufferedBytes + bytes > 8 * 1024 * 1024) {\n queue.fail(new Error('Live frame capacity exceeded; reconnect and replay durable frames'));\n return;\n }\n bufferedBytes += bytes;\n bufferedFrames += 1;\n queue.push({ event, bytes });\n };\n const onFrame = (raw: RealtimeTransportFrameIface): void => {\n const frame = agentRealtimeFrameSchema.safeParse(raw);\n if (!frame.success || queue.closed()) return;\n if (frame.data.type === AgentRealtimeFrameType.Error && isTerminalRealtimeError(frame.data)) {\n queue.fail(\n realtimeSubscriptionError({\n topic: frame.data.topic ?? topic,\n code: frame.data.code ?? 'unknown',\n message: frame.data.message ?? 'Realtime subscription was refused',\n }),\n );\n return;\n }\n if (\n frame.data.type === AgentRealtimeFrameType.Subscribed ||\n frame.data.type === AgentRealtimeFrameType.ResyncRequired ||\n frame.data.type === AgentRealtimeFrameType.Error\n ) {\n push({\n kind: SessionLiveEventKind.Reset,\n reset: { streamId: null, throughSeq: null, reason: 'reconnect' },\n });\n return;\n }\n if (frame.data.type !== AgentRealtimeFrameType.Event) return;\n if (frame.data.kind === AgentRealtimeKind.AcpLiveFrame) {\n const payload = acpLiveFrameEventSchema.safeParse(frame.data.payload);\n if (payload.success) push({ kind: SessionLiveEventKind.Frame, frame: payload.data });\n } else if (frame.data.kind === AgentRealtimeKind.AcpLiveReset) {\n const payload = acpLiveResetEventSchema.safeParse(frame.data.payload);\n if (payload.success) push({ kind: SessionLiveEventKind.Reset, reset: payload.data });\n }\n };\n detachAbort = failOnAbort(queue, args.signal);\n detachClose = failOnAbort(queue, args.context.closeSignal);\n if (!queue.closed()) {\n const stop = args.context.realtime().subscribe(topic, onFrame);\n if (queue.closed()) stop();\n else unsubscribe = stop;\n }\n const iterator: AsyncIterableIterator<SessionLiveEvent> = {\n next: async () => {\n const value = await queue.iterator.next();\n if (value.done === true) return value;\n bufferedBytes -= value.value.bytes;\n bufferedFrames -= 1;\n return { done: false, value: value.value.event };\n },\n return: async () => {\n await queue.iterator.return?.();\n return { done: true, value: undefined };\n },\n [Symbol.asyncIterator]: () => iterator,\n };\n return iterator;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["SessionLiveEventKind"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { RealtimeSessionResponseIface } from '@finalai/agent-protocol/agent-platform';
|
|
2
|
+
/**
|
|
3
|
+
* One frame a topic subscriber receives: a realtime event envelope
|
|
4
|
+
* (`type: 'event'`), `subscribed` (after every (re)subscription), `resync_required`
|
|
5
|
+
* or `error`. The SDK parses it with the contract's `agentRealtimeFrameSchema`,
|
|
6
|
+
* so console clients can forward their gateway frames unchanged.
|
|
7
|
+
*/
|
|
8
|
+
export interface RealtimeTransportFrameIface {
|
|
9
|
+
type: string;
|
|
10
|
+
topic?: string;
|
|
11
|
+
cursor?: string | null;
|
|
12
|
+
eventKey?: string;
|
|
13
|
+
kind?: string;
|
|
14
|
+
payload?: unknown;
|
|
15
|
+
createdAt?: string;
|
|
16
|
+
reason?: string;
|
|
17
|
+
code?: string;
|
|
18
|
+
message?: string;
|
|
19
|
+
retryable?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface RealtimeSubscribeOptionsIface {
|
|
22
|
+
/** Gateway cursor to resume after; omitted means live only. */
|
|
23
|
+
afterCursor?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface RealtimeTransportIface {
|
|
26
|
+
subscribe: (topic: string, onFrame: (frame: RealtimeTransportFrameIface) => void, options?: RealtimeSubscribeOptionsIface) => () => void;
|
|
27
|
+
}
|
|
28
|
+
interface GatewayMessageIface {
|
|
29
|
+
data: unknown;
|
|
30
|
+
cursor: string | null;
|
|
31
|
+
}
|
|
32
|
+
interface GatewayTopicIface {
|
|
33
|
+
subscribe: (handler: (message: GatewayMessageIface) => void, options?: {
|
|
34
|
+
afterCursor?: string;
|
|
35
|
+
onSubscribed?: () => void;
|
|
36
|
+
onResyncRequired?: () => void;
|
|
37
|
+
onError?: (error: {
|
|
38
|
+
code: string;
|
|
39
|
+
message: string;
|
|
40
|
+
retryable: boolean;
|
|
41
|
+
}) => void;
|
|
42
|
+
}) => () => void;
|
|
43
|
+
}
|
|
44
|
+
export interface GatewayConnectionIface {
|
|
45
|
+
realtime: (topic: string) => GatewayTopicIface;
|
|
46
|
+
close: (code?: number, reason?: string) => void;
|
|
47
|
+
}
|
|
48
|
+
export interface GatewayConnectOptionsIface {
|
|
49
|
+
url: string;
|
|
50
|
+
auth: () => Promise<string>;
|
|
51
|
+
reconnect: boolean;
|
|
52
|
+
}
|
|
53
|
+
export type GatewayConnect = (options: GatewayConnectOptionsIface) => Promise<GatewayConnectionIface>;
|
|
54
|
+
export interface GatewayRealtimeTransportOptionsIface {
|
|
55
|
+
/** Mints a gateway token (`POST /api/v1/realtime/session`). */
|
|
56
|
+
createSession: () => Promise<RealtimeSessionResponseIface>;
|
|
57
|
+
/** Seam for tests; defaults to `@finalai/websockets`'s `client.connect`. */
|
|
58
|
+
connect?: GatewayConnect;
|
|
59
|
+
/** Refresh the gateway token this long before it expires. */
|
|
60
|
+
refreshMarginMs?: number;
|
|
61
|
+
/** First retry delay after a failed connect or a retryable subscription error; doubles per attempt. */
|
|
62
|
+
retryBaseMs?: number;
|
|
63
|
+
retryMaxMs?: number;
|
|
64
|
+
/** Close the gateway connection once no topic has been subscribed for this long. */
|
|
65
|
+
idleCloseMs?: number;
|
|
66
|
+
}
|
|
67
|
+
/** The default transport; `close` tears down its connection and cancels every retry. */
|
|
68
|
+
export interface GatewayRealtimeTransportIface extends RealtimeTransportIface {
|
|
69
|
+
close: () => void;
|
|
70
|
+
}
|
|
71
|
+
export declare const createGatewayRealtimeTransport: (options: GatewayRealtimeTransportOptionsIface) => GatewayRealtimeTransportIface;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { client as gatewayClient } from "@finalai/websockets";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
const DEFAULT_REFRESH_MARGIN_MS = 6e4;
|
|
4
|
+
const DEFAULT_RETRY_BASE_MS = 500;
|
|
5
|
+
const DEFAULT_RETRY_MAX_MS = 15e3;
|
|
6
|
+
const DEFAULT_IDLE_CLOSE_MS = 1e4;
|
|
7
|
+
const eventDataSchema = z.object({
|
|
8
|
+
type: z.string(),
|
|
9
|
+
topic: z.string().optional(),
|
|
10
|
+
eventKey: z.string().optional(),
|
|
11
|
+
kind: z.string().optional(),
|
|
12
|
+
payload: z.unknown(),
|
|
13
|
+
createdAt: z.string().optional(),
|
|
14
|
+
reason: z.string().optional()
|
|
15
|
+
});
|
|
16
|
+
const errorMessageSchema = z.object({ message: z.string() });
|
|
17
|
+
const createGatewayRealtimeTransport = (options) => {
|
|
18
|
+
const connect = options.connect ?? gatewayClient.connect;
|
|
19
|
+
const refreshMarginMs = options.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS;
|
|
20
|
+
let session = null;
|
|
21
|
+
let connection = null;
|
|
22
|
+
const topics = /* @__PURE__ */ new Map();
|
|
23
|
+
const idleCloseMs = options.idleCloseMs ?? DEFAULT_IDLE_CLOSE_MS;
|
|
24
|
+
let idleTimer = null;
|
|
25
|
+
let transportClosed = false;
|
|
26
|
+
const closeConnection = (reason) => {
|
|
27
|
+
const pending = connection;
|
|
28
|
+
connection = null;
|
|
29
|
+
void pending?.then(
|
|
30
|
+
(live) => live.close(1e3, reason),
|
|
31
|
+
() => {
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
const cancelIdleClose = () => {
|
|
36
|
+
if (idleTimer != null) clearTimeout(idleTimer);
|
|
37
|
+
idleTimer = null;
|
|
38
|
+
};
|
|
39
|
+
const scheduleIdleClose = () => {
|
|
40
|
+
if (transportClosed || topics.size > 0 || idleTimer != null || connection == null) return;
|
|
41
|
+
idleTimer = setTimeout(() => {
|
|
42
|
+
idleTimer = null;
|
|
43
|
+
if (topics.size === 0) closeConnection("agent realtime idle");
|
|
44
|
+
}, idleCloseMs);
|
|
45
|
+
idleTimer.unref?.();
|
|
46
|
+
};
|
|
47
|
+
const freshSession = async () => {
|
|
48
|
+
if (session != null && Date.parse(session.expiresAt) - Date.now() > refreshMarginMs) {
|
|
49
|
+
return session;
|
|
50
|
+
}
|
|
51
|
+
session = await options.createSession();
|
|
52
|
+
return session;
|
|
53
|
+
};
|
|
54
|
+
const open = () => {
|
|
55
|
+
if (connection != null) return connection;
|
|
56
|
+
const pending = freshSession().then(
|
|
57
|
+
(initial) => connect({
|
|
58
|
+
url: initial.url,
|
|
59
|
+
auth: async () => (await freshSession()).token,
|
|
60
|
+
reconnect: true
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
connection = pending;
|
|
64
|
+
pending.catch(() => {
|
|
65
|
+
if (connection === pending) connection = null;
|
|
66
|
+
});
|
|
67
|
+
return pending;
|
|
68
|
+
};
|
|
69
|
+
const emit = (entry, frame) => {
|
|
70
|
+
for (const listener of [...entry.listeners]) listener(frame);
|
|
71
|
+
};
|
|
72
|
+
const retryBaseMs = options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS;
|
|
73
|
+
const retryMaxMs = options.retryMaxMs ?? DEFAULT_RETRY_MAX_MS;
|
|
74
|
+
const detach = (entry) => {
|
|
75
|
+
entry.ready = false;
|
|
76
|
+
entry.unsubscribe?.();
|
|
77
|
+
entry.unsubscribe = null;
|
|
78
|
+
};
|
|
79
|
+
const close = (entry) => {
|
|
80
|
+
entry.closed = true;
|
|
81
|
+
if (entry.retryTimer != null) clearTimeout(entry.retryTimer);
|
|
82
|
+
entry.retryTimer = null;
|
|
83
|
+
detach(entry);
|
|
84
|
+
if (topics.get(entry.topic) === entry) topics.delete(entry.topic);
|
|
85
|
+
scheduleIdleClose();
|
|
86
|
+
};
|
|
87
|
+
const scheduleRetry = (entry) => {
|
|
88
|
+
if (entry.closed || entry.retryTimer != null) return;
|
|
89
|
+
const delay = Math.min(retryMaxMs, retryBaseMs * 2 ** entry.attempts);
|
|
90
|
+
entry.attempts += 1;
|
|
91
|
+
entry.retryTimer = setTimeout(() => {
|
|
92
|
+
entry.retryTimer = null;
|
|
93
|
+
void attach(entry);
|
|
94
|
+
}, delay);
|
|
95
|
+
};
|
|
96
|
+
const attach = async (entry) => {
|
|
97
|
+
const { topic } = entry;
|
|
98
|
+
try {
|
|
99
|
+
const live = await open();
|
|
100
|
+
if (entry.closed) return;
|
|
101
|
+
detach(entry);
|
|
102
|
+
entry.unsubscribe = live.realtime(topic).subscribe(
|
|
103
|
+
(message) => {
|
|
104
|
+
const data = eventDataSchema.safeParse(message.data);
|
|
105
|
+
if (!data.success) return;
|
|
106
|
+
if (message.cursor != null) entry.cursor = message.cursor;
|
|
107
|
+
emit(entry, { ...data.data, cursor: message.cursor });
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
afterCursor: entry.cursor,
|
|
111
|
+
onSubscribed: () => {
|
|
112
|
+
entry.attempts = 0;
|
|
113
|
+
entry.ready = true;
|
|
114
|
+
emit(entry, { type: "subscribed", topic });
|
|
115
|
+
},
|
|
116
|
+
onResyncRequired: () => {
|
|
117
|
+
entry.cursor = void 0;
|
|
118
|
+
emit(entry, { type: "resync_required", topic });
|
|
119
|
+
},
|
|
120
|
+
onError: (error) => {
|
|
121
|
+
emit(entry, { type: "error", topic, ...error });
|
|
122
|
+
if (error.retryable) {
|
|
123
|
+
detach(entry);
|
|
124
|
+
scheduleRetry(entry);
|
|
125
|
+
} else {
|
|
126
|
+
close(entry);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
} catch (cause) {
|
|
132
|
+
if (entry.closed) return;
|
|
133
|
+
emit(entry, {
|
|
134
|
+
type: "error",
|
|
135
|
+
topic,
|
|
136
|
+
code: "connect_failed",
|
|
137
|
+
message: errorMessageSchema.safeParse(cause).data?.message ?? "realtime connection failed",
|
|
138
|
+
retryable: true
|
|
139
|
+
});
|
|
140
|
+
scheduleRetry(entry);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const subscribe = (topic, onFrame, subscribeOptions) => {
|
|
144
|
+
if (transportClosed) return () => {
|
|
145
|
+
};
|
|
146
|
+
cancelIdleClose();
|
|
147
|
+
let entry = topics.get(topic);
|
|
148
|
+
if (entry == null) {
|
|
149
|
+
entry = {
|
|
150
|
+
topic,
|
|
151
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
152
|
+
unsubscribe: null,
|
|
153
|
+
closed: false,
|
|
154
|
+
cursor: subscribeOptions?.afterCursor,
|
|
155
|
+
attempts: 0,
|
|
156
|
+
retryTimer: null,
|
|
157
|
+
ready: false
|
|
158
|
+
};
|
|
159
|
+
topics.set(topic, entry);
|
|
160
|
+
void attach(entry);
|
|
161
|
+
}
|
|
162
|
+
const current = entry;
|
|
163
|
+
current.listeners.add(onFrame);
|
|
164
|
+
if (current.ready) {
|
|
165
|
+
queueMicrotask(() => {
|
|
166
|
+
if (current.ready && current.listeners.has(onFrame)) onFrame({ type: "subscribed", topic });
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return () => {
|
|
170
|
+
current.listeners.delete(onFrame);
|
|
171
|
+
if (current.listeners.size === 0) close(current);
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
const closeTransport = () => {
|
|
175
|
+
if (transportClosed) return;
|
|
176
|
+
transportClosed = true;
|
|
177
|
+
cancelIdleClose();
|
|
178
|
+
for (const entry of [...topics.values()]) close(entry);
|
|
179
|
+
closeConnection("agent client closed");
|
|
180
|
+
};
|
|
181
|
+
return { subscribe, close: closeTransport };
|
|
182
|
+
};
|
|
183
|
+
export {
|
|
184
|
+
createGatewayRealtimeTransport
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=realtime.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/agent-client/realtime.ts"],
|
|
4
|
+
"sourcesContent": ["import type { RealtimeSessionResponseIface } from '@finalai/agent-protocol/agent-platform';\nimport { client as gatewayClient } from '@finalai/websockets';\nimport * as z from 'zod';\n\n// Streams ride websocket-gateway topic subscriptions. console-web and\n// console-mobile pass their existing realtime clients as a\n// RealtimeTransportIface; everyone else gets the default gateway transport,\n// which mints a gateway token at `POST /api/v1/realtime/session`.\n\n/**\n * One frame a topic subscriber receives: a realtime event envelope\n * (`type: 'event'`), `subscribed` (after every (re)subscription), `resync_required`\n * or `error`. The SDK parses it with the contract's `agentRealtimeFrameSchema`,\n * so console clients can forward their gateway frames unchanged.\n */\nexport interface RealtimeTransportFrameIface {\n type: string;\n topic?: string;\n cursor?: string | null;\n eventKey?: string;\n kind?: string;\n payload?: unknown;\n createdAt?: string;\n reason?: string;\n code?: string;\n message?: string;\n retryable?: boolean;\n}\n\nexport interface RealtimeSubscribeOptionsIface {\n /** Gateway cursor to resume after; omitted means live only. */\n afterCursor?: string;\n}\n\nexport interface RealtimeTransportIface {\n subscribe: (\n topic: string,\n onFrame: (frame: RealtimeTransportFrameIface) => void,\n options?: RealtimeSubscribeOptionsIface,\n ) => () => void;\n}\n\ninterface GatewayMessageIface {\n data: unknown;\n cursor: string | null;\n}\n\ninterface GatewayTopicIface {\n subscribe: (\n handler: (message: GatewayMessageIface) => void,\n options?: {\n afterCursor?: string;\n onSubscribed?: () => void;\n onResyncRequired?: () => void;\n onError?: (error: { code: string; message: string; retryable: boolean }) => void;\n },\n ) => () => void;\n}\n\nexport interface GatewayConnectionIface {\n realtime: (topic: string) => GatewayTopicIface;\n close: (code?: number, reason?: string) => void;\n}\n\nexport interface GatewayConnectOptionsIface {\n url: string;\n auth: () => Promise<string>;\n reconnect: boolean;\n}\n\nexport type GatewayConnect = (\n options: GatewayConnectOptionsIface,\n) => Promise<GatewayConnectionIface>;\n\nexport interface GatewayRealtimeTransportOptionsIface {\n /** Mints a gateway token (`POST /api/v1/realtime/session`). */\n createSession: () => Promise<RealtimeSessionResponseIface>;\n /** Seam for tests; defaults to `@finalai/websockets`'s `client.connect`. */\n connect?: GatewayConnect;\n /** Refresh the gateway token this long before it expires. */\n refreshMarginMs?: number;\n /** First retry delay after a failed connect or a retryable subscription error; doubles per attempt. */\n retryBaseMs?: number;\n retryMaxMs?: number;\n /** Close the gateway connection once no topic has been subscribed for this long. */\n idleCloseMs?: number;\n}\n\n/** The default transport; `close` tears down its connection and cancels every retry. */\nexport interface GatewayRealtimeTransportIface extends RealtimeTransportIface {\n close: () => void;\n}\n\nconst DEFAULT_REFRESH_MARGIN_MS = 60_000;\nconst DEFAULT_RETRY_BASE_MS = 500;\nconst DEFAULT_RETRY_MAX_MS = 15_000;\n// Long enough that a view swapping one stream for another reuses the socket.\nconst DEFAULT_IDLE_CLOSE_MS = 10_000;\n\nconst eventDataSchema = z.object({\n type: z.string(),\n topic: z.string().optional(),\n eventKey: z.string().optional(),\n kind: z.string().optional(),\n payload: z.unknown(),\n createdAt: z.string().optional(),\n reason: z.string().optional(),\n});\n\nconst errorMessageSchema = z.object({ message: z.string() });\n\ninterface TopicEntryIface {\n topic: string;\n listeners: Set<(frame: RealtimeTransportFrameIface) => void>;\n unsubscribe: (() => void) | null;\n /** The last listener left, or the gateway refused the topic for good. */\n closed: boolean;\n /** Newest retained cursor delivered, so a retry resumes after it. */\n cursor: string | undefined;\n attempts: number;\n retryTimer: ReturnType<typeof setTimeout> | null;\n /** The gateway acknowledged the current subscription; late listeners get their own `subscribed`. */\n ready: boolean;\n}\n\nexport const createGatewayRealtimeTransport = (\n options: GatewayRealtimeTransportOptionsIface,\n): GatewayRealtimeTransportIface => {\n const connect = options.connect ?? gatewayClient.connect;\n const refreshMarginMs = options.refreshMarginMs ?? DEFAULT_REFRESH_MARGIN_MS;\n let session: RealtimeSessionResponseIface | null = null;\n let connection: Promise<GatewayConnectionIface> | null = null;\n const topics = new Map<string, TopicEntryIface>();\n const idleCloseMs = options.idleCloseMs ?? DEFAULT_IDLE_CLOSE_MS;\n let idleTimer: ReturnType<typeof setTimeout> | null = null;\n let transportClosed = false;\n\n const closeConnection = (reason: string): void => {\n const pending = connection;\n connection = null;\n void pending?.then(\n (live) => live.close(1000, reason),\n () => {},\n );\n };\n\n const cancelIdleClose = (): void => {\n if (idleTimer != null) clearTimeout(idleTimer);\n idleTimer = null;\n };\n\n // With no topics left the socket only keeps the process (and a server slot) busy;\n // the next subscribe reconnects.\n const scheduleIdleClose = (): void => {\n if (transportClosed || topics.size > 0 || idleTimer != null || connection == null) return;\n idleTimer = setTimeout(() => {\n idleTimer = null;\n if (topics.size === 0) closeConnection('agent realtime idle');\n }, idleCloseMs);\n // An idle socket waiting to close must not keep a Node process alive.\n idleTimer.unref?.();\n };\n\n const freshSession = async (): Promise<RealtimeSessionResponseIface> => {\n if (session != null && Date.parse(session.expiresAt) - Date.now() > refreshMarginMs) {\n return session;\n }\n session = await options.createSession();\n return session;\n };\n\n const open = (): Promise<GatewayConnectionIface> => {\n if (connection != null) return connection;\n const pending = freshSession().then((initial) =>\n connect({\n url: initial.url,\n auth: async () => (await freshSession()).token,\n reconnect: true,\n }),\n );\n connection = pending;\n pending.catch(() => {\n if (connection === pending) connection = null;\n });\n return pending;\n };\n\n const emit = (entry: TopicEntryIface, frame: RealtimeTransportFrameIface): void => {\n for (const listener of [...entry.listeners]) listener(frame);\n };\n\n const retryBaseMs = options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS;\n const retryMaxMs = options.retryMaxMs ?? DEFAULT_RETRY_MAX_MS;\n\n const detach = (entry: TopicEntryIface): void => {\n entry.ready = false;\n entry.unsubscribe?.();\n entry.unsubscribe = null;\n };\n\n const close = (entry: TopicEntryIface): void => {\n entry.closed = true;\n if (entry.retryTimer != null) clearTimeout(entry.retryTimer);\n entry.retryTimer = null;\n detach(entry);\n if (topics.get(entry.topic) === entry) topics.delete(entry.topic);\n scheduleIdleClose();\n };\n\n // Retries run only while the entry has listeners; the last unsubscribe cancels them.\n const scheduleRetry = (entry: TopicEntryIface): void => {\n if (entry.closed || entry.retryTimer != null) return;\n const delay = Math.min(retryMaxMs, retryBaseMs * 2 ** entry.attempts);\n entry.attempts += 1;\n entry.retryTimer = setTimeout(() => {\n entry.retryTimer = null;\n void attach(entry);\n }, delay);\n };\n\n const attach = async (entry: TopicEntryIface): Promise<void> => {\n const { topic } = entry;\n try {\n const live = await open();\n if (entry.closed) return;\n detach(entry);\n entry.unsubscribe = live.realtime(topic).subscribe(\n (message) => {\n const data = eventDataSchema.safeParse(message.data);\n if (!data.success) return;\n if (message.cursor != null) entry.cursor = message.cursor;\n // The gateway's cursor wins: non-retained messages carry null.\n emit(entry, { ...data.data, cursor: message.cursor });\n },\n {\n afterCursor: entry.cursor,\n onSubscribed: () => {\n entry.attempts = 0;\n entry.ready = true;\n emit(entry, { type: 'subscribed', topic });\n },\n onResyncRequired: () => {\n entry.cursor = undefined;\n emit(entry, { type: 'resync_required', topic });\n },\n onError: (error) => {\n emit(entry, { type: 'error', topic, ...error });\n if (error.retryable) {\n detach(entry);\n scheduleRetry(entry);\n } else {\n // Refused for good: a later subscribe to this topic starts a fresh attempt.\n close(entry);\n }\n },\n },\n );\n } catch (cause) {\n if (entry.closed) return;\n // `open` dropped the failed connection, so the retry reconnects (and re-mints the token).\n emit(entry, {\n type: 'error',\n topic,\n code: 'connect_failed',\n message: errorMessageSchema.safeParse(cause).data?.message ?? 'realtime connection failed',\n retryable: true,\n });\n scheduleRetry(entry);\n }\n };\n\n const subscribe: RealtimeTransportIface['subscribe'] = (topic, onFrame, subscribeOptions) => {\n if (transportClosed) return () => {};\n cancelIdleClose();\n let entry = topics.get(topic);\n if (entry == null) {\n entry = {\n topic,\n listeners: new Set(),\n unsubscribe: null,\n closed: false,\n cursor: subscribeOptions?.afterCursor,\n attempts: 0,\n retryTimer: null,\n ready: false,\n };\n topics.set(topic, entry);\n void attach(entry);\n }\n const current = entry;\n current.listeners.add(onFrame);\n if (current.ready) {\n // Streams start recovery (and non-following streams finish) on `subscribed`, so a\n // listener joining a live topic gets its own, after `subscribe` has returned.\n queueMicrotask(() => {\n if (current.ready && current.listeners.has(onFrame)) onFrame({ type: 'subscribed', topic });\n });\n }\n return () => {\n current.listeners.delete(onFrame);\n if (current.listeners.size === 0) close(current);\n };\n };\n\n const closeTransport = (): void => {\n if (transportClosed) return;\n transportClosed = true;\n cancelIdleClose();\n for (const entry of [...topics.values()]) close(entry);\n closeConnection('agent client closed');\n };\n\n return { subscribe, close: closeTransport };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type ResultIface<T> = {
|
|
2
|
+
ok: true;
|
|
3
|
+
data: T;
|
|
4
|
+
} | {
|
|
5
|
+
ok: false;
|
|
6
|
+
error: string;
|
|
7
|
+
};
|
|
8
|
+
export interface SandboxFileTargetIface {
|
|
9
|
+
conversationId: string;
|
|
10
|
+
bindingId: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SandboxFileUriIface {
|
|
13
|
+
target: SandboxFileTargetIface;
|
|
14
|
+
path: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const SANDBOX_FILE_URI_SCHEME = "final-sandbox:";
|
|
17
|
+
export declare const createSandboxFileUri: (location: SandboxFileUriIface) => string;
|
|
18
|
+
export declare const parseSandboxFileUri: (value: string) => ResultIface<SandboxFileUriIface>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { base58Decode } from "@finalai/agent-protocol/base58";
|
|
2
|
+
const err = (error) => ({ ok: false, error });
|
|
3
|
+
const ok = (data) => ({ ok: true, data });
|
|
4
|
+
const SANDBOX_FILE_URI_SCHEME = "final-sandbox:";
|
|
5
|
+
const wireIdValid = (prefix, value) => {
|
|
6
|
+
if (!value.startsWith(prefix + "_")) return false;
|
|
7
|
+
const body = value.slice(prefix.length + 1);
|
|
8
|
+
if (body.length === 0 || body.length > 22) return false;
|
|
9
|
+
try {
|
|
10
|
+
return base58Decode(body).length === 16;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const targetValid = (target) => wireIdValid("conv", target.conversationId) && wireIdValid("sbnd", target.bindingId);
|
|
16
|
+
const pathValid = (path) => path.length > 0 && path.length <= 4096 && !path.includes("\0");
|
|
17
|
+
const createSandboxFileUri = (location) => {
|
|
18
|
+
if (!targetValid(location.target) || !pathValid(location.path))
|
|
19
|
+
throw new Error("Invalid sandbox file location");
|
|
20
|
+
const query = new URLSearchParams({ path: location.path });
|
|
21
|
+
return `final-sandbox://${location.target.conversationId}/${location.target.bindingId}?${query}`;
|
|
22
|
+
};
|
|
23
|
+
const parseSandboxFileUri = (value) => {
|
|
24
|
+
try {
|
|
25
|
+
const uri = new URL(value);
|
|
26
|
+
const bindingId = uri.pathname.slice(1);
|
|
27
|
+
const target = { conversationId: uri.hostname, bindingId };
|
|
28
|
+
const paths = uri.searchParams.getAll("path");
|
|
29
|
+
if (uri.protocol !== SANDBOX_FILE_URI_SCHEME || uri.username.length > 0 || uri.password.length > 0 || uri.port.length > 0 || uri.hash.length > 0 || !targetValid(target) || paths.length !== 1 || [...uri.searchParams.keys()].some((key) => key !== "path"))
|
|
30
|
+
return err("Invalid sandbox file URI");
|
|
31
|
+
const path = paths[0];
|
|
32
|
+
if (path == null || !pathValid(path)) return err("Invalid sandbox file path");
|
|
33
|
+
return ok({ target, path });
|
|
34
|
+
} catch {
|
|
35
|
+
return err("Invalid sandbox file URI");
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
export {
|
|
39
|
+
SANDBOX_FILE_URI_SCHEME,
|
|
40
|
+
createSandboxFileUri,
|
|
41
|
+
parseSandboxFileUri
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=sandbox-uri.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/agent-client/sandbox-uri.ts"],
|
|
4
|
+
"sourcesContent": ["import { base58Decode } from '@finalai/agent-protocol/base58';\ntype ResultIface<T> = { ok: true; data: T } | { ok: false; error: string };\nconst err = (error: string): { ok: false; error: string } => ({ ok: false, error });\nconst ok = <T>(data: T): { ok: true; data: T } => ({ ok: true, data });\n\nexport interface SandboxFileTargetIface {\n conversationId: string;\n bindingId: string;\n}\n\nexport interface SandboxFileUriIface {\n target: SandboxFileTargetIface;\n path: string;\n}\n\nexport const SANDBOX_FILE_URI_SCHEME = 'final-sandbox:';\n\nconst wireIdValid = (prefix: string, value: string): boolean => {\n if (!value.startsWith(prefix + '_')) return false;\n const body = value.slice(prefix.length + 1);\n if (body.length === 0 || body.length > 22) return false;\n try {\n return base58Decode(body).length === 16;\n } catch {\n return false;\n }\n};\n\nconst targetValid = (target: SandboxFileTargetIface): boolean =>\n wireIdValid('conv', target.conversationId) && wireIdValid('sbnd', target.bindingId);\n\nconst pathValid = (path: string): boolean =>\n path.length > 0 && path.length <= 4096 && !path.includes('\\0');\n\nexport const createSandboxFileUri = (location: SandboxFileUriIface): string => {\n if (!targetValid(location.target) || !pathValid(location.path))\n throw new Error('Invalid sandbox file location');\n const query = new URLSearchParams({ path: location.path });\n return `final-sandbox://${location.target.conversationId}/${location.target.bindingId}?${query}`;\n};\n\nexport const parseSandboxFileUri = (value: string): ResultIface<SandboxFileUriIface> => {\n try {\n const uri = new URL(value);\n const bindingId = uri.pathname.slice(1);\n const target = { conversationId: uri.hostname, bindingId };\n const paths = uri.searchParams.getAll('path');\n if (\n uri.protocol !== SANDBOX_FILE_URI_SCHEME ||\n uri.username.length > 0 ||\n uri.password.length > 0 ||\n uri.port.length > 0 ||\n uri.hash.length > 0 ||\n !targetValid(target) ||\n paths.length !== 1 ||\n [...uri.searchParams.keys()].some((key) => key !== 'path')\n )\n return err('Invalid sandbox file URI');\n const path = paths[0];\n if (path == null || !pathValid(path)) return err('Invalid sandbox file path');\n return ok({ target, path });\n } catch {\n return err('Invalid sandbox file URI');\n }\n};\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,MAAM,MAAM,CAAC,WAAiD,EAAE,IAAI,OAAO,MAAM;AACjF,MAAM,KAAK,CAAI,UAAoC,EAAE,IAAI,MAAM,KAAK;AAY7D,MAAM,0BAA0B;AAEvC,MAAM,cAAc,CAAC,QAAgB,UAA2B;AAC9D,MAAI,CAAC,MAAM,WAAW,SAAS,GAAG,EAAG,QAAO;AAC5C,QAAM,OAAO,MAAM,MAAM,OAAO,SAAS,CAAC;AAC1C,MAAI,KAAK,WAAW,KAAK,KAAK,SAAS,GAAI,QAAO;AAClD,MAAI;AACF,WAAO,aAAa,IAAI,EAAE,WAAW;AAAA,EACvC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,MAAM,cAAc,CAAC,WACnB,YAAY,QAAQ,OAAO,cAAc,KAAK,YAAY,QAAQ,OAAO,SAAS;AAEpF,MAAM,YAAY,CAAC,SACjB,KAAK,SAAS,KAAK,KAAK,UAAU,QAAQ,CAAC,KAAK,SAAS,IAAI;AAExD,MAAM,uBAAuB,CAAC,aAA0C;AAC7E,MAAI,CAAC,YAAY,SAAS,MAAM,KAAK,CAAC,UAAU,SAAS,IAAI;AAC3D,UAAM,IAAI,MAAM,+BAA+B;AACjD,QAAM,QAAQ,IAAI,gBAAgB,EAAE,MAAM,SAAS,KAAK,CAAC;AACzD,SAAO,mBAAmB,SAAS,OAAO,cAAc,IAAI,SAAS,OAAO,SAAS,IAAI,KAAK;AAChG;AAEO,MAAM,sBAAsB,CAAC,UAAoD;AACtF,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AACzB,UAAM,YAAY,IAAI,SAAS,MAAM,CAAC;AACtC,UAAM,SAAS,EAAE,gBAAgB,IAAI,UAAU,UAAU;AACzD,UAAM,QAAQ,IAAI,aAAa,OAAO,MAAM;AAC5C,QACE,IAAI,aAAa,2BACjB,IAAI,SAAS,SAAS,KACtB,IAAI,SAAS,SAAS,KACtB,IAAI,KAAK,SAAS,KAClB,IAAI,KAAK,SAAS,KAClB,CAAC,YAAY,MAAM,KACnB,MAAM,WAAW,KACjB,CAAC,GAAG,IAAI,aAAa,KAAK,CAAC,EAAE,KAAK,CAAC,QAAQ,QAAQ,MAAM;AAEzD,aAAO,IAAI,0BAA0B;AACvC,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,QAAQ,QAAQ,CAAC,UAAU,IAAI,EAAG,QAAO,IAAI,2BAA2B;AAC5E,WAAO,GAAG,EAAE,QAAQ,KAAK,CAAC;AAAA,EAC5B,QAAQ;AACN,WAAO,IAAI,0BAA0B;AAAA,EACvC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|