@finalai/agents 0.4.3 → 0.5.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.
- package/dist/modules/agent-client/client.d.mts +1 -18
- package/dist/modules/agent-client/client.mjs +5 -39
- package/dist/modules/agent-client/client.mjs.map +2 -2
- package/dist/modules/agent-client/collection-events.d.mts +1 -1
- package/dist/modules/agent-client/collection-events.mjs.map +1 -1
- package/dist/modules/agent-client/context.d.mts +1 -2
- package/dist/modules/agent-client/context.mjs +0 -1
- package/dist/modules/agent-client/context.mjs.map +2 -2
- package/dist/modules/agent-client/conversations.d.mts +8 -7
- package/dist/modules/agent-client/conversations.mjs +41 -40
- package/dist/modules/agent-client/conversations.mjs.map +2 -2
- package/dist/modules/agent-client/errors.d.mts +14 -14
- package/dist/modules/agent-client/errors.mjs +15 -15
- package/dist/modules/agent-client/errors.mjs.map +3 -3
- package/dist/modules/agent-client/frame-stream.d.mts +8 -8
- package/dist/modules/agent-client/frame-stream.mjs +36 -36
- package/dist/modules/agent-client/frame-stream.mjs.map +2 -2
- package/dist/modules/agent-client/http.d.mts +2 -2
- package/dist/modules/agent-client/http.mjs +10 -10
- package/dist/modules/agent-client/http.mjs.map +2 -2
- package/dist/modules/agent-client/index.d.mts +2 -2
- package/dist/modules/agent-client/index.mjs +6 -6
- package/dist/modules/agent-client/index.mjs.map +2 -2
- package/dist/modules/agent-client/sessions.d.mts +9 -8
- package/dist/modules/agent-client/sessions.mjs +13 -13
- package/dist/modules/agent-client/sessions.mjs.map +2 -2
- package/examples/release-scout/README.md +1 -1
- package/examples/release-scout/client.ts +2 -2
- package/package.json +5 -5
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type AgentInfoIface, type AgentListIface, type HealthResponseIface, type
|
|
1
|
+
import { type AgentInfoIface, type AgentListIface, type HealthResponseIface, type PersonIface, type PersonListIface, type RealtimeSessionResponseIface } from '@finalai/agent-protocol/agent-platform';
|
|
2
2
|
import { type AcpApiIface } from "./acp.mjs";
|
|
3
3
|
import { type ComputerSetupGuidesApiIface, type DefinitionsApiIface, type ProvidersApiIface, type SchedulesApiIface } from "./build.mjs";
|
|
4
|
-
import { type CollectionEvent, type CollectionEventsOptionsIface } from "./collection-events.mjs";
|
|
5
4
|
import { type SignalOptionsIface } from "./context.mjs";
|
|
6
5
|
import { type ConversationsApiIface } from "./conversations.mjs";
|
|
7
6
|
import { type StreamTimingIface } from "./frame-stream.mjs";
|
|
@@ -50,21 +49,6 @@ export interface AgentsApiIface {
|
|
|
50
49
|
/** I/O-free handle by slug or id. */
|
|
51
50
|
get: (slugOrId: string) => AgentHandleIface;
|
|
52
51
|
}
|
|
53
|
-
export interface PendingApiIface {
|
|
54
|
-
/**
|
|
55
|
-
* The caller's pending topic (`user.collection:<userId>:pending`): `acp.pending`
|
|
56
|
-
* events for requests addressed to them, and a `collection.resync` whenever
|
|
57
|
-
* `mine()` must be re-read (on every subscription, resync and while realtime is down).
|
|
58
|
-
*/
|
|
59
|
-
events: (options?: CollectionEventsOptionsIface) => AsyncIterableIterator<CollectionEvent>;
|
|
60
|
-
/** Every open permission and elicitation request whose audience includes the caller, newest first (all pages). */
|
|
61
|
-
mine: (options?: SignalOptionsIface) => Promise<PendingRequestIface[]>;
|
|
62
|
-
/** One page, newest first; pass `nextCursor` as `cursor` for the next. */
|
|
63
|
-
page: (options?: SignalOptionsIface & {
|
|
64
|
-
cursor?: string;
|
|
65
|
-
limit?: number;
|
|
66
|
-
}) => Promise<PendingRequestListIface>;
|
|
67
|
-
}
|
|
68
52
|
export interface PeopleApiIface {
|
|
69
53
|
/** One page of the caller's organization members, by name; pass `nextCursor` as `cursor` for the next. */
|
|
70
54
|
list: (options?: SignalOptionsIface & {
|
|
@@ -93,7 +77,6 @@ export interface AgentClientIface {
|
|
|
93
77
|
providers: ProvidersApiIface;
|
|
94
78
|
computerSetupGuides: ComputerSetupGuidesApiIface;
|
|
95
79
|
schedules: SchedulesApiIface;
|
|
96
|
-
pending: PendingApiIface;
|
|
97
80
|
/**
|
|
98
81
|
* ACP-native access (`/acp/ws`): an ACP v2 ClientApp connection on which the
|
|
99
82
|
* session service is the agent. The ACP SDK loads on the first `connect`.
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
agentListSchema,
|
|
4
4
|
agentRealtimeTopics,
|
|
5
5
|
healthResponseSchema,
|
|
6
|
-
pendingRequestListSchema,
|
|
7
6
|
personListSchema,
|
|
8
7
|
realtimeSessionResponseSchema
|
|
9
8
|
} from "@finalai/agent-protocol/agent-platform";
|
|
@@ -14,13 +13,7 @@ import {
|
|
|
14
13
|
createProvidersApi,
|
|
15
14
|
createSchedulesApi
|
|
16
15
|
} from "./build.mjs";
|
|
17
|
-
import {
|
|
18
|
-
streamCollectionEvents
|
|
19
|
-
} from "./collection-events.mjs";
|
|
20
|
-
import {
|
|
21
|
-
ClientCollection,
|
|
22
|
-
encodePath
|
|
23
|
-
} from "./context.mjs";
|
|
16
|
+
import { encodePath } from "./context.mjs";
|
|
24
17
|
import { createConversationsApi } from "./conversations.mjs";
|
|
25
18
|
import { clientError, ClientErrorCode } from "./errors.mjs";
|
|
26
19
|
import { defaultStreamTiming } from "./frame-stream.mjs";
|
|
@@ -76,11 +69,12 @@ const createClient = (options) => {
|
|
|
76
69
|
http,
|
|
77
70
|
closeSignal: closing.signal,
|
|
78
71
|
realtimeUserId,
|
|
79
|
-
|
|
72
|
+
// Conversations is the only collection.
|
|
73
|
+
realtimeCollectionTopic: async () => {
|
|
80
74
|
const owner = await realtimeOwnerSession();
|
|
81
75
|
if (owner.userId == null)
|
|
82
76
|
throw new Error("Collection streams belong to users; API-key owners use session streams");
|
|
83
|
-
return
|
|
77
|
+
return agentRealtimeTopics.userConversations(owner.userId);
|
|
84
78
|
},
|
|
85
79
|
storageFetch: options.storageFetch ?? ((input, init) => globalThis.fetch(input, init)),
|
|
86
80
|
realtime: () => {
|
|
@@ -160,35 +154,7 @@ const createClient = (options) => {
|
|
|
160
154
|
providers: createProvidersApi(context),
|
|
161
155
|
computerSetupGuides: createComputerSetupGuidesApi(context),
|
|
162
156
|
schedules: createSchedulesApi(context),
|
|
163
|
-
acp: createAcpApi({ http })
|
|
164
|
-
pending: /* @__PURE__ */ (() => {
|
|
165
|
-
const page = (pageOptions) => http.request({
|
|
166
|
-
method: HttpMethod.Get,
|
|
167
|
-
path: "/v1/pending",
|
|
168
|
-
query: { cursor: pageOptions?.cursor, limit: pageOptions?.limit },
|
|
169
|
-
response: pendingRequestListSchema,
|
|
170
|
-
signal: pageOptions?.signal
|
|
171
|
-
});
|
|
172
|
-
return {
|
|
173
|
-
page,
|
|
174
|
-
events: (eventsOptions) => streamCollectionEvents({
|
|
175
|
-
context,
|
|
176
|
-
topicFor: agentRealtimeTopics.userPending,
|
|
177
|
-
collection: ClientCollection.Pending,
|
|
178
|
-
options: eventsOptions
|
|
179
|
-
}),
|
|
180
|
-
mine: async (mineOptions) => {
|
|
181
|
-
const all = [];
|
|
182
|
-
let cursor;
|
|
183
|
-
for (; ; ) {
|
|
184
|
-
const next = await page({ cursor, signal: mineOptions?.signal });
|
|
185
|
-
all.push(...next.pending);
|
|
186
|
-
if (next.nextCursor == null) return all;
|
|
187
|
-
cursor = next.nextCursor;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
})()
|
|
157
|
+
acp: createAcpApi({ http })
|
|
192
158
|
};
|
|
193
159
|
};
|
|
194
160
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/agent-client/client.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n agentInfoSchema,\n agentListSchema,\n agentRealtimeTopics,\n healthResponseSchema,\n pendingRequestListSchema,\n personListSchema,\n realtimeSessionResponseSchema,\n type AgentInfoIface,\n type AgentListIface,\n type HealthResponseIface,\n type PendingRequestIface,\n type PendingRequestListIface,\n type PersonIface,\n type PersonListIface,\n type RealtimeSessionResponseIface,\n} from '@finalai/agent-protocol/agent-platform';\nimport { createAcpApi, type AcpApiIface } from \"./acp.mjs\";\nimport {\n createComputerSetupGuidesApi,\n createDefinitionsApi,\n createProvidersApi,\n createSchedulesApi,\n type ComputerSetupGuidesApiIface,\n type DefinitionsApiIface,\n type ProvidersApiIface,\n type SchedulesApiIface,\n} from \"./build.mjs\";\nimport {\n streamCollectionEvents,\n type CollectionEvent,\n type CollectionEventsOptionsIface,\n} from \"./collection-events.mjs\";\nimport {\n ClientCollection,\n encodePath,\n type ClientContextIface,\n type SignalOptionsIface,\n} from \"./context.mjs\";\nimport { createConversationsApi, type ConversationsApiIface } from \"./conversations.mjs\";\nimport { clientError, ClientErrorCode } from \"./errors.mjs\";\nimport { defaultStreamTiming, type StreamTimingIface } from \"./frame-stream.mjs\";\nimport {\n createHttp,\n HttpMethod,\n type AgentClientAuth,\n type FetchIface,\n type HeadersOption,\n} from \"./http.mjs\";\nimport {\n createGatewayRealtimeTransport,\n type GatewayConnect,\n type GatewayRealtimeTransportIface,\n type RealtimeTransportIface,\n} from \"./realtime.mjs\";\nimport { createSessionsApi, type SessionsApiIface } from \"./sessions.mjs\";\n\nexport interface CreateClientOptionsIface {\n /**\n * The session service's origin (`https://agent-session.final.ai`; its API is\n * under `/api` and ACP at `/acp/ws`), or a base URL the API routes are mounted\n * under (console-server's `https://<console>/api/agent-platform` proxy).\n */\n host: string;\n auth: AgentClientAuth;\n /** Extra headers per request; console clients send their client identity header here. */\n headers?: HeadersOption;\n /** API requests (the host's routes); carries auth and caller headers. */\n fetch?: FetchIface;\n /**\n * Presigned storage requests (attachment uploads). Gets only the headers the\n * presign requires, never auth, client identity or cookies, so a storage\n * bucket's CORS and third-party hosts see nothing of the caller. Defaults to\n * `globalThis.fetch`; keep it free of header-injecting wrappers.\n */\n storageFetch?: FetchIface;\n /** console-web and console-mobile pass their realtime clients; defaults to a gateway connection. */\n realtime?: RealtimeTransportIface;\n /** Seam for the default transport's gateway connection. */\n gatewayConnect?: GatewayConnect;\n /** Seam for stream retry timing (tests). */\n timing?: Partial<StreamTimingIface>;\n /** Seam for idempotency keys and client message ids; defaults to `crypto.randomUUID`. */\n newKey?: () => string;\n /** Seam for the client clock (conversation recovery windows). */\n now?: () => number;\n}\n\nexport interface AgentHandleIface {\n slug: string;\n info: (options?: SignalOptionsIface) => Promise<AgentInfoIface>;\n}\n\nexport interface AgentsApiIface {\n list: (\n options?: SignalOptionsIface & { cursor?: string; limit?: number },\n ) => Promise<AgentListIface>;\n /** I/O-free handle by slug or id. */\n get: (slugOrId: string) => AgentHandleIface;\n}\n\nexport interface PendingApiIface {\n /**\n * The caller's pending topic (`user.collection:<userId>:pending`): `acp.pending`\n * events for requests addressed to them, and a `collection.resync` whenever\n * `mine()` must be re-read (on every subscription, resync and while realtime is down).\n */\n events: (options?: CollectionEventsOptionsIface) => AsyncIterableIterator<CollectionEvent>;\n /** Every open permission and elicitation request whose audience includes the caller, newest first (all pages). */\n mine: (options?: SignalOptionsIface) => Promise<PendingRequestIface[]>;\n /** One page, newest first; pass `nextCursor` as `cursor` for the next. */\n page: (\n options?: SignalOptionsIface & { cursor?: string; limit?: number },\n ) => Promise<PendingRequestListIface>;\n}\n\nexport interface PeopleApiIface {\n /** One page of the caller's organization members, by name; pass `nextCursor` as `cursor` for the next. */\n list: (\n options?: SignalOptionsIface & { cursor?: string; limit?: number },\n ) => Promise<PersonListIface>;\n /** Every member of the caller's organization (all pages). */\n all: (options?: SignalOptionsIface) => Promise<PersonIface[]>;\n}\n\nexport interface RealtimeApiIface {\n /**\n * Mints a gateway session (`POST /v1/realtime/session`) for the signed-in user:\n * the gateway URL, a short-lived token, and the user id realtime topics are keyed by.\n * Pass it as `createSession` to `createGatewayRealtimeTransport` when supplying a transport.\n */\n createSession: (options?: SignalOptionsIface) => Promise<RealtimeSessionResponseIface>;\n}\n\nexport interface AgentClientIface {\n health: (options?: SignalOptionsIface) => Promise<HealthResponseIface>;\n agents: AgentsApiIface;\n people: PeopleApiIface;\n realtime: RealtimeApiIface;\n sessions: SessionsApiIface;\n conversations: ConversationsApiIface;\n definitions: DefinitionsApiIface;\n providers: ProvidersApiIface;\n computerSetupGuides: ComputerSetupGuidesApiIface;\n schedules: SchedulesApiIface;\n pending: PendingApiIface;\n /**\n * ACP-native access (`/acp/ws`): an ACP v2 ClientApp connection on which the\n * session service is the agent. The ACP SDK loads on the first `connect`.\n */\n acp: AcpApiIface;\n /**\n * Ends every stream (they reject with a `client_closed` ClientError) and, when\n * the client created its own realtime transport, closes the gateway connection\n * and cancels its retries. A transport passed in `realtime` belongs to the\n * caller and is left open. Idempotent.\n */\n close: () => void;\n [Symbol.asyncDispose]: () => Promise<void>;\n}\n\nexport const createClient = (options: CreateClientOptionsIface): AgentClientIface => {\n const http = createHttp({\n host: options.host,\n auth: options.auth,\n headers: options.headers,\n fetch: options.fetch,\n });\n let realtime = options.realtime ?? null;\n let ownTransport: GatewayRealtimeTransportIface | null = null;\n const closing = new AbortController();\n const createRealtimeSession: RealtimeApiIface['createSession'] = (sessionOptions) =>\n http.request({\n method: HttpMethod.Post,\n path: '/v1/realtime/session',\n response: realtimeSessionResponseSchema,\n signal: sessionOptions?.signal,\n });\n let realtimeOwner: Promise<RealtimeSessionResponseIface> | null = null;\n const realtimeOwnerSession = () => {\n if (realtimeOwner != null) return realtimeOwner;\n const pending = createRealtimeSession({ signal: closing.signal });\n realtimeOwner = pending;\n void pending.catch(() => {\n if (realtimeOwner === pending) realtimeOwner = null;\n });\n return pending;\n };\n let realtimeUser: Promise<string> | null = null;\n // Shared by every stream, so it is cancelled only with the client, never by one caller.\n const realtimeUserId: ClientContextIface['realtimeUserId'] = () => {\n if (realtimeUser != null) return realtimeUser;\n const pending = realtimeOwnerSession().then((session) => {\n if (session.userId == null)\n throw new Error('This client authenticates as an API-key owner, not a user');\n return session.userId;\n });\n realtimeUser = pending;\n // A failed (or aborted) read is not cached.\n pending.catch(() => {\n if (realtimeUser === pending) realtimeUser = null;\n });\n return pending;\n };\n const context: ClientContextIface = {\n http,\n closeSignal: closing.signal,\n realtimeUserId,\n realtimeCollectionTopic: async (collection) => {\n const owner = await realtimeOwnerSession();\n // Organization and team keys are no conversation members: they reach agents through channels.\n if (owner.userId == null)\n throw new Error('Collection streams belong to users; API-key owners use session streams');\n return collection === ClientCollection.Pending\n ? agentRealtimeTopics.userPending(owner.userId)\n : agentRealtimeTopics.userConversations(owner.userId);\n },\n storageFetch: options.storageFetch ?? ((input, init) => globalThis.fetch(input, init)),\n realtime: () => {\n if (realtime != null) return realtime;\n ownTransport = createGatewayRealtimeTransport({\n createSession: () => createRealtimeSession(),\n connect: options.gatewayConnect,\n });\n if (closing.signal.aborted) ownTransport.close();\n realtime = ownTransport;\n return realtime;\n },\n timing: { ...defaultStreamTiming, ...options.timing },\n newKey: options.newKey ?? (() => globalThis.crypto.randomUUID()),\n now: options.now ?? Date.now,\n };\n\n const close = (): void => {\n if (closing.signal.aborted) return;\n closing.abort(\n clientError({ message: 'Agent client closed', status: 0, code: ClientErrorCode.Closed }),\n );\n ownTransport?.close();\n };\n\n return {\n close,\n [Symbol.asyncDispose]: async () => close(),\n health: (healthOptions) =>\n http.request({\n method: HttpMethod.Get,\n path: '/v1/health',\n response: healthResponseSchema,\n signal: healthOptions?.signal,\n }),\n agents: {\n list: (listOptions) =>\n http.request({\n method: HttpMethod.Get,\n path: '/v1/agents',\n query: { cursor: listOptions?.cursor, limit: listOptions?.limit },\n response: agentListSchema,\n signal: listOptions?.signal,\n }),\n get: (slugOrId) => ({\n slug: slugOrId,\n info: (infoOptions) =>\n http.request({\n method: HttpMethod.Get,\n path: `/v1/agents/${encodePath(slugOrId)}`,\n response: agentInfoSchema,\n signal: infoOptions?.signal,\n }),\n }),\n },\n people: (() => {\n const list: PeopleApiIface['list'] = (listOptions) =>\n http.request({\n method: HttpMethod.Get,\n path: '/v1/people',\n query: { cursor: listOptions?.cursor, limit: listOptions?.limit },\n response: personListSchema,\n signal: listOptions?.signal,\n });\n return {\n list,\n all: async (allOptions) => {\n const all: PersonIface[] = [];\n let cursor: string | undefined;\n for (;;) {\n const next = await list({ cursor, signal: allOptions?.signal });\n all.push(...next.people);\n if (next.nextCursor == null) return all;\n cursor = next.nextCursor;\n }\n },\n };\n })(),\n realtime: { createSession: createRealtimeSession },\n sessions: createSessionsApi(context),\n conversations: createConversationsApi(context),\n definitions: createDefinitionsApi(context),\n providers: createProvidersApi(context),\n computerSetupGuides: createComputerSetupGuidesApi(context),\n schedules: createSchedulesApi(context),\n acp: createAcpApi({ http }),\n pending: (() => {\n const page: PendingApiIface['page'] = (pageOptions) =>\n http.request({\n method: HttpMethod.Get,\n path: '/v1/pending',\n query: { cursor: pageOptions?.cursor, limit: pageOptions?.limit },\n response: pendingRequestListSchema,\n signal: pageOptions?.signal,\n });\n return {\n page,\n events: (eventsOptions) =>\n streamCollectionEvents({\n context,\n topicFor: agentRealtimeTopics.userPending,\n collection: ClientCollection.Pending,\n options: eventsOptions,\n }),\n mine: async (mineOptions) => {\n const all: PendingRequestIface[] = [];\n let cursor: string | undefined;\n for (;;) {\n const next = await page({ cursor, signal: mineOptions?.signal });\n all.push(...next.pending);\n if (next.nextCursor == null) return all;\n cursor = next.nextCursor;\n }\n },\n };\n })(),\n };\n};\n"],
|
|
5
|
-
"mappings": "AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,
|
|
4
|
+
"sourcesContent": ["import {\n agentInfoSchema,\n agentListSchema,\n agentRealtimeTopics,\n healthResponseSchema,\n personListSchema,\n realtimeSessionResponseSchema,\n type AgentInfoIface,\n type AgentListIface,\n type HealthResponseIface,\n type PersonIface,\n type PersonListIface,\n type RealtimeSessionResponseIface,\n} from '@finalai/agent-protocol/agent-platform';\nimport { createAcpApi, type AcpApiIface } from \"./acp.mjs\";\nimport {\n createComputerSetupGuidesApi,\n createDefinitionsApi,\n createProvidersApi,\n createSchedulesApi,\n type ComputerSetupGuidesApiIface,\n type DefinitionsApiIface,\n type ProvidersApiIface,\n type SchedulesApiIface,\n} from \"./build.mjs\";\nimport { encodePath, type ClientContextIface, type SignalOptionsIface } from \"./context.mjs\";\nimport { createConversationsApi, type ConversationsApiIface } from \"./conversations.mjs\";\nimport { clientError, ClientErrorCode } from \"./errors.mjs\";\nimport { defaultStreamTiming, type StreamTimingIface } from \"./frame-stream.mjs\";\nimport {\n createHttp,\n HttpMethod,\n type AgentClientAuth,\n type FetchIface,\n type HeadersOption,\n} from \"./http.mjs\";\nimport {\n createGatewayRealtimeTransport,\n type GatewayConnect,\n type GatewayRealtimeTransportIface,\n type RealtimeTransportIface,\n} from \"./realtime.mjs\";\nimport { createSessionsApi, type SessionsApiIface } from \"./sessions.mjs\";\n\nexport interface CreateClientOptionsIface {\n /**\n * The session service's origin (`https://agent-session.final.ai`; its API is\n * under `/api` and ACP at `/acp/ws`), or a base URL the API routes are mounted\n * under (console-server's `https://<console>/api/agent-platform` proxy).\n */\n host: string;\n auth: AgentClientAuth;\n /** Extra headers per request; console clients send their client identity header here. */\n headers?: HeadersOption;\n /** API requests (the host's routes); carries auth and caller headers. */\n fetch?: FetchIface;\n /**\n * Presigned storage requests (attachment uploads). Gets only the headers the\n * presign requires, never auth, client identity or cookies, so a storage\n * bucket's CORS and third-party hosts see nothing of the caller. Defaults to\n * `globalThis.fetch`; keep it free of header-injecting wrappers.\n */\n storageFetch?: FetchIface;\n /** console-web and console-mobile pass their realtime clients; defaults to a gateway connection. */\n realtime?: RealtimeTransportIface;\n /** Seam for the default transport's gateway connection. */\n gatewayConnect?: GatewayConnect;\n /** Seam for stream retry timing (tests). */\n timing?: Partial<StreamTimingIface>;\n /** Seam for idempotency keys and client message ids; defaults to `crypto.randomUUID`. */\n newKey?: () => string;\n /** Seam for the client clock (conversation recovery windows). */\n now?: () => number;\n}\n\nexport interface AgentHandleIface {\n slug: string;\n info: (options?: SignalOptionsIface) => Promise<AgentInfoIface>;\n}\n\nexport interface AgentsApiIface {\n list: (\n options?: SignalOptionsIface & { cursor?: string; limit?: number },\n ) => Promise<AgentListIface>;\n /** I/O-free handle by slug or id. */\n get: (slugOrId: string) => AgentHandleIface;\n}\n\nexport interface PeopleApiIface {\n /** One page of the caller's organization members, by name; pass `nextCursor` as `cursor` for the next. */\n list: (\n options?: SignalOptionsIface & { cursor?: string; limit?: number },\n ) => Promise<PersonListIface>;\n /** Every member of the caller's organization (all pages). */\n all: (options?: SignalOptionsIface) => Promise<PersonIface[]>;\n}\n\nexport interface RealtimeApiIface {\n /**\n * Mints a gateway session (`POST /v1/realtime/session`) for the signed-in user:\n * the gateway URL, a short-lived token, and the user id realtime topics are keyed by.\n * Pass it as `createSession` to `createGatewayRealtimeTransport` when supplying a transport.\n */\n createSession: (options?: SignalOptionsIface) => Promise<RealtimeSessionResponseIface>;\n}\n\nexport interface AgentClientIface {\n health: (options?: SignalOptionsIface) => Promise<HealthResponseIface>;\n agents: AgentsApiIface;\n people: PeopleApiIface;\n realtime: RealtimeApiIface;\n sessions: SessionsApiIface;\n conversations: ConversationsApiIface;\n definitions: DefinitionsApiIface;\n providers: ProvidersApiIface;\n computerSetupGuides: ComputerSetupGuidesApiIface;\n schedules: SchedulesApiIface;\n /**\n * ACP-native access (`/acp/ws`): an ACP v2 ClientApp connection on which the\n * session service is the agent. The ACP SDK loads on the first `connect`.\n */\n acp: AcpApiIface;\n /**\n * Ends every stream (they reject with a `client_closed` ClientError) and, when\n * the client created its own realtime transport, closes the gateway connection\n * and cancels its retries. A transport passed in `realtime` belongs to the\n * caller and is left open. Idempotent.\n */\n close: () => void;\n [Symbol.asyncDispose]: () => Promise<void>;\n}\n\nexport const createClient = (options: CreateClientOptionsIface): AgentClientIface => {\n const http = createHttp({\n host: options.host,\n auth: options.auth,\n headers: options.headers,\n fetch: options.fetch,\n });\n let realtime = options.realtime ?? null;\n let ownTransport: GatewayRealtimeTransportIface | null = null;\n const closing = new AbortController();\n const createRealtimeSession: RealtimeApiIface['createSession'] = (sessionOptions) =>\n http.request({\n method: HttpMethod.Post,\n path: '/v1/realtime/session',\n response: realtimeSessionResponseSchema,\n signal: sessionOptions?.signal,\n });\n let realtimeOwner: Promise<RealtimeSessionResponseIface> | null = null;\n const realtimeOwnerSession = () => {\n if (realtimeOwner != null) return realtimeOwner;\n const pending = createRealtimeSession({ signal: closing.signal });\n realtimeOwner = pending;\n void pending.catch(() => {\n if (realtimeOwner === pending) realtimeOwner = null;\n });\n return pending;\n };\n let realtimeUser: Promise<string> | null = null;\n // Shared by every stream, so it is cancelled only with the client, never by one caller.\n const realtimeUserId: ClientContextIface['realtimeUserId'] = () => {\n if (realtimeUser != null) return realtimeUser;\n const pending = realtimeOwnerSession().then((session) => {\n if (session.userId == null)\n throw new Error('This client authenticates as an API-key owner, not a user');\n return session.userId;\n });\n realtimeUser = pending;\n // A failed (or aborted) read is not cached.\n pending.catch(() => {\n if (realtimeUser === pending) realtimeUser = null;\n });\n return pending;\n };\n const context: ClientContextIface = {\n http,\n closeSignal: closing.signal,\n realtimeUserId,\n // Conversations is the only collection.\n realtimeCollectionTopic: async () => {\n const owner = await realtimeOwnerSession();\n // Organization and team keys are no conversation members: they reach agents through channels.\n if (owner.userId == null)\n throw new Error('Collection streams belong to users; API-key owners use session streams');\n return agentRealtimeTopics.userConversations(owner.userId);\n },\n storageFetch: options.storageFetch ?? ((input, init) => globalThis.fetch(input, init)),\n realtime: () => {\n if (realtime != null) return realtime;\n ownTransport = createGatewayRealtimeTransport({\n createSession: () => createRealtimeSession(),\n connect: options.gatewayConnect,\n });\n if (closing.signal.aborted) ownTransport.close();\n realtime = ownTransport;\n return realtime;\n },\n timing: { ...defaultStreamTiming, ...options.timing },\n newKey: options.newKey ?? (() => globalThis.crypto.randomUUID()),\n now: options.now ?? Date.now,\n };\n\n const close = (): void => {\n if (closing.signal.aborted) return;\n closing.abort(\n clientError({ message: 'Agent client closed', status: 0, code: ClientErrorCode.Closed }),\n );\n ownTransport?.close();\n };\n\n return {\n close,\n [Symbol.asyncDispose]: async () => close(),\n health: (healthOptions) =>\n http.request({\n method: HttpMethod.Get,\n path: '/v1/health',\n response: healthResponseSchema,\n signal: healthOptions?.signal,\n }),\n agents: {\n list: (listOptions) =>\n http.request({\n method: HttpMethod.Get,\n path: '/v1/agents',\n query: { cursor: listOptions?.cursor, limit: listOptions?.limit },\n response: agentListSchema,\n signal: listOptions?.signal,\n }),\n get: (slugOrId) => ({\n slug: slugOrId,\n info: (infoOptions) =>\n http.request({\n method: HttpMethod.Get,\n path: `/v1/agents/${encodePath(slugOrId)}`,\n response: agentInfoSchema,\n signal: infoOptions?.signal,\n }),\n }),\n },\n people: (() => {\n const list: PeopleApiIface['list'] = (listOptions) =>\n http.request({\n method: HttpMethod.Get,\n path: '/v1/people',\n query: { cursor: listOptions?.cursor, limit: listOptions?.limit },\n response: personListSchema,\n signal: listOptions?.signal,\n });\n return {\n list,\n all: async (allOptions) => {\n const all: PersonIface[] = [];\n let cursor: string | undefined;\n for (;;) {\n const next = await list({ cursor, signal: allOptions?.signal });\n all.push(...next.people);\n if (next.nextCursor == null) return all;\n cursor = next.nextCursor;\n }\n },\n };\n })(),\n realtime: { createSession: createRealtimeSession },\n sessions: createSessionsApi(context),\n conversations: createConversationsApi(context),\n definitions: createDefinitionsApi(context),\n providers: createProvidersApi(context),\n computerSetupGuides: createComputerSetupGuidesApi(context),\n schedules: createSchedulesApi(context),\n acp: createAcpApi({ http }),\n };\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ClientCollection, ClientContextIface, SignalOptionsIface } from "./context.mjs";
|
|
2
2
|
import type { ConversationEvent } from "./conversations.mjs";
|
|
3
3
|
export declare enum CollectionEventKind {
|
|
4
|
-
/** Re-read the collection (`conversations.list()
|
|
4
|
+
/** Re-read the collection (`conversations.list()`); events may have been missed. */
|
|
5
5
|
Resync = "collection.resync"
|
|
6
6
|
}
|
|
7
7
|
export declare enum CollectionResyncReason {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/agent-client/collection-events.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n agentRealtimeFrameSchema,\n AgentRealtimeFrameType,\n parseAgentRealtimeEvent,\n} from '@finalai/agent-protocol/agent-platform';\nimport { createAsyncQueue, failOnAbort } from \"./async-queue.mjs\";\nimport type { ClientCollection, ClientContextIface, SignalOptionsIface } from \"./context.mjs\";\nimport type { ConversationEvent } from \"./conversations.mjs\";\nimport {\n isRetryableClientError,\n isTerminalRealtimeError,\n realtimeSubscriptionError,\n} from \"./errors.mjs\";\nimport type { RealtimeTransportFrameIface } from \"./realtime.mjs\";\n\n// The caller's collection
|
|
4
|
+
"sourcesContent": ["import {\n agentRealtimeFrameSchema,\n AgentRealtimeFrameType,\n parseAgentRealtimeEvent,\n} from '@finalai/agent-protocol/agent-platform';\nimport { createAsyncQueue, failOnAbort } from \"./async-queue.mjs\";\nimport type { ClientCollection, ClientContextIface, SignalOptionsIface } from \"./context.mjs\";\nimport type { ConversationEvent } from \"./conversations.mjs\";\nimport {\n isRetryableClientError,\n isTerminalRealtimeError,\n realtimeSubscriptionError,\n} from \"./errors.mjs\";\nimport type { RealtimeTransportFrameIface } from \"./realtime.mjs\";\n\n// The caller's collection topic (`user.collection:<userId>:conversations`).\n// It carries no replay, so a stream says when the\n// caller must re-read the collection over HTTP: on every (re)subscription and\n// resync, and while the realtime transport reports errors.\n\nexport enum CollectionEventKind {\n /** Re-read the collection (`conversations.list()`); events may have been missed. */\n Resync = 'collection.resync',\n}\n\nexport enum CollectionResyncReason {\n Subscribed = 'subscribed',\n ResyncRequired = 'resync_required',\n /** The realtime transport is down; repeated every `timing.conversationPollIntervalMs`. */\n Degraded = 'degraded',\n}\n\nexport interface CollectionResyncEventIface {\n kind: CollectionEventKind.Resync;\n /** Null while the signed-in user, and so the topic, could not be read yet. */\n topic: string | null;\n reason: CollectionResyncReason;\n}\n\n/** A realtime event of the collection topic, or a cue to re-read the collection. */\nexport type CollectionEvent = ConversationEvent | CollectionResyncEventIface;\n\nexport interface CollectionEventsOptionsIface extends SignalOptionsIface {\n /** Keep streaming; false ends after the first resync. Defaults to true. */\n follow?: boolean;\n /**\n * The signed-in user the topics are keyed by. Defaults to the user of a\n * minted realtime session (`realtime.createSession()`).\n */\n userId?: string;\n}\n\nconst MAX_LOOKUP_RETRY_MS = 30_000;\n\nexport const streamCollectionEvents = (args: {\n context: ClientContextIface;\n topicFor: (userId: string) => string;\n collection?: ClientCollection;\n options: CollectionEventsOptionsIface | undefined;\n}): AsyncIterableIterator<CollectionEvent> => {\n const follow = args.options?.follow ?? true;\n const controller = new AbortController();\n const signal = AbortSignal.any([\n controller.signal,\n args.context.closeSignal,\n ...(args.options?.signal == null ? [] : [args.options.signal]),\n ]);\n let unsubscribe: (() => void) | null = null;\n let pollTimer: ReturnType<typeof setTimeout> | null = null;\n let lookupTimer: ReturnType<typeof setTimeout> | null = null;\n const clearPoll = (): void => {\n if (pollTimer != null) clearTimeout(pollTimer);\n pollTimer = null;\n };\n let detachAbort: () => void = () => {};\n let detachClose: () => void = () => {};\n const queue = createAsyncQueue<CollectionEvent>(() => {\n controller.abort();\n clearPoll();\n if (lookupTimer != null) clearTimeout(lookupTimer);\n unsubscribe?.();\n detachAbort();\n detachClose();\n });\n\n const resync = (topic: string | null, reason: CollectionResyncReason): void => {\n queue.push({ kind: CollectionEventKind.Resync, topic, reason });\n if (!follow) queue.end();\n };\n\n const degrade = (topic: string): void => {\n if (pollTimer != null || queue.closed()) return;\n resync(topic, CollectionResyncReason.Degraded);\n if (!follow || queue.closed()) return;\n pollTimer = setTimeout(() => {\n pollTimer = null;\n degrade(topic);\n }, args.context.timing.conversationPollIntervalMs);\n };\n\n const onFrame =\n (topic: string) =>\n (raw: RealtimeTransportFrameIface): void => {\n const frame = agentRealtimeFrameSchema.safeParse(raw);\n if (!frame.success || queue.closed()) return;\n switch (frame.data.type) {\n case AgentRealtimeFrameType.Event: {\n const envelope = frame.data;\n queue.push({\n ...parseAgentRealtimeEvent(envelope),\n topic: envelope.topic,\n cursor: envelope.cursor,\n createdAt: envelope.createdAt,\n replayed: false,\n });\n return;\n }\n case AgentRealtimeFrameType.Subscribed:\n clearPoll();\n resync(topic, CollectionResyncReason.Subscribed);\n return;\n case AgentRealtimeFrameType.ResyncRequired:\n resync(topic, CollectionResyncReason.ResyncRequired);\n return;\n case AgentRealtimeFrameType.Error:\n if (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 // The transport resubscribes; until then the caller re-reads periodically.\n degrade(topic);\n return;\n default:\n return;\n }\n };\n\n detachAbort = failOnAbort(queue, signal);\n detachClose = failOnAbort(queue, args.context.closeSignal);\n const userId = args.options?.userId;\n let lookupFailures = 0;\n const start = (): void => {\n lookupTimer = null;\n if (queue.closed()) return;\n const topic =\n userId != null\n ? Promise.resolve(args.topicFor(userId))\n : args.collection != null && args.context.realtimeCollectionTopic != null\n ? args.context.realtimeCollectionTopic(args.collection)\n : args.context.realtimeUserId().then(args.topicFor);\n void topic.then(\n (topic) => {\n if (queue.closed()) return;\n const stop = args.context.realtime().subscribe(topic, onFrame(topic));\n if (queue.closed()) stop();\n else unsubscribe = stop;\n },\n (cause: unknown) => {\n if (queue.closed()) return;\n if (!isRetryableClientError(cause)) {\n queue.fail(cause);\n return;\n }\n // The user lookup is unreachable: the caller re-reads meanwhile, and it is retried.\n resync(null, CollectionResyncReason.Degraded);\n if (queue.closed()) return;\n const delay = Math.min(\n MAX_LOOKUP_RETRY_MS,\n args.context.timing.conversationPollIntervalMs * 2 ** lookupFailures,\n );\n lookupFailures += 1;\n lookupTimer = setTimeout(start, delay);\n },\n );\n };\n start();\n return queue.iterator;\n};\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["CollectionEventKind", "CollectionResyncReason", "topic"]
|
|
7
7
|
}
|
|
@@ -2,8 +2,7 @@ import type { StreamTimingIface } from "./frame-stream.mjs";
|
|
|
2
2
|
import type { FetchIface, HttpIface } from "./http.mjs";
|
|
3
3
|
import type { RealtimeTransportIface } from "./realtime.mjs";
|
|
4
4
|
export declare enum ClientCollection {
|
|
5
|
-
Conversations = "conversations"
|
|
6
|
-
Pending = "pending"
|
|
5
|
+
Conversations = "conversations"
|
|
7
6
|
}
|
|
8
7
|
/** What every API group gets from `createClient`. */
|
|
9
8
|
export interface ClientContextIface {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
var ClientCollection = /* @__PURE__ */ ((ClientCollection2) => {
|
|
2
2
|
ClientCollection2["Conversations"] = "conversations";
|
|
3
|
-
ClientCollection2["Pending"] = "pending";
|
|
4
3
|
return ClientCollection2;
|
|
5
4
|
})(ClientCollection || {});
|
|
6
5
|
const encodePath = (segment) => encodeURIComponent(segment);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/agent-client/context.ts"],
|
|
4
|
-
"sourcesContent": ["import type { StreamTimingIface } from \"./frame-stream.mjs\";\nimport type { FetchIface, HttpIface } from \"./http.mjs\";\nimport type { RealtimeTransportIface } from \"./realtime.mjs\";\n\nexport enum ClientCollection {\n Conversations = 'conversations',\n
|
|
5
|
-
"mappings": "AAIO,IAAK,mBAAL,kBAAKA,sBAAL;AACL,EAAAA,kBAAA,mBAAgB;
|
|
4
|
+
"sourcesContent": ["import type { StreamTimingIface } from \"./frame-stream.mjs\";\nimport type { FetchIface, HttpIface } from \"./http.mjs\";\nimport type { RealtimeTransportIface } from \"./realtime.mjs\";\n\nexport enum ClientCollection {\n Conversations = 'conversations',\n}\n\n/** What every API group gets from `createClient`. */\nexport interface ClientContextIface {\n http: HttpIface;\n /** Aborts when `client.close()` is called; every stream ends with it. */\n closeSignal: AbortSignal;\n /** Presigned storage requests: no API credentials, client identity or cookies. */\n storageFetch: FetchIface;\n /** Resolved on first use, so clients that never stream never mint a gateway token. */\n realtime: () => RealtimeTransportIface;\n /** The signed-in user realtime topics are keyed by (a minted realtime session's); cached. */\n realtimeUserId: () => Promise<string>;\n realtimeCollectionTopic?: (collection: ClientCollection) => Promise<string>;\n timing: StreamTimingIface;\n /** Idempotency keys and client message ids. */\n newKey: () => string;\n /** Client clock in epoch ms. */\n now: () => number;\n}\n\nexport interface SignalOptionsIface {\n signal?: AbortSignal;\n}\n\n/** Author calls may use the wire spelling or the exported enum constant. */\nexport type EnumInput<E extends string> = E | `${E}`;\n\nexport const encodePath = (segment: string): string => encodeURIComponent(segment);\n"],
|
|
5
|
+
"mappings": "AAIO,IAAK,mBAAL,kBAAKA,sBAAL;AACL,EAAAA,kBAAA,mBAAgB;AADN,SAAAA;AAAA,GAAA;AA8BL,MAAM,aAAa,CAAC,YAA4B,mBAAmB,OAAO;",
|
|
6
6
|
"names": ["ClientCollection"]
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ProcessInspectionRequestIface, type ProcessInspectionResultIface } from '@finalai/agent-protocol';
|
|
2
|
-
import { AgentSessionState, type PostConversationEventResponseIface, type AgentRealtimeEvent, type AgentTurnPolicy, type AttachmentDownloadIface, type AttachmentUploadIface, type ContentInput, type ConversationIface, type ConversationKind, type ConversationLinkIface, type ConversationListIface, type ConversationMessageIface, type ConversationMessagesPageIface, type ConversationSandboxIface, type ConversationScheduleIface, type CreateConversationScheduleRequestIface, type DecisionsPageIface, type MessageDeliveryIface, type ParticipantIface, type ParticipantInput, type PostConversationEventRequestIface, type ReactionIface, type ReadStateIface, type
|
|
2
|
+
import { AgentSessionState, type PostConversationEventResponseIface, type AgentRealtimeEvent, type AgentTurnPolicy, type AttachmentDownloadIface, type AttachmentUploadIface, type ContentInput, type ConversationIface, type ConversationKind, type ConversationLinkIface, type ConversationListIface, type ConversationMessageIface, type ConversationMessagesPageIface, type ConversationSandboxIface, type ConversationScheduleIface, type CreateConversationScheduleRequestIface, type DecisionsPageIface, type MessageDeliveryIface, type ParticipantIface, type ParticipantInput, type PostConversationEventRequestIface, type ReactionIface, type ReadStateIface, type RespondDecisionResponseIface, type SandboxDirectoryIface, type SandboxEditorSessionIface, type SandboxFileIface, type SandboxPathIface, type SandboxPreviewType, type SandboxSearchIface, type SearchMessagesPageIface, type UpdateConversationRequestIface, type UpdateConversationScheduleRequestIface, type UpdateParticipantRequestIface, type WireFallback } from '@finalai/agent-protocol/agent-platform';
|
|
3
3
|
import { type CollectionEvent, type CollectionEventsOptionsIface } from "./collection-events.mjs";
|
|
4
4
|
import { type ClientContextIface, type EnumInput, type SignalOptionsIface } from "./context.mjs";
|
|
5
5
|
import { type RespondArgs } from "./sessions.mjs";
|
|
@@ -24,7 +24,7 @@ export interface ConversationEventMetaIface {
|
|
|
24
24
|
/** True for messages the SDK fetched over HTTP to fill a realtime gap. */
|
|
25
25
|
replayed: boolean;
|
|
26
26
|
}
|
|
27
|
-
/** A realtime event of the conversation topic, flattened (`event.message`, `event.
|
|
27
|
+
/** A realtime event of the conversation topic, flattened (`event.message`, `event.decision`). */
|
|
28
28
|
export type ConversationEvent = AgentRealtimeEvent & ConversationEventMetaIface;
|
|
29
29
|
export interface ConversationEventsOptionsIface extends SignalOptionsIface {
|
|
30
30
|
/** Keep streaming; false ends once the subscription is current. Defaults to true. */
|
|
@@ -49,9 +49,10 @@ export interface ConversationEventsOptionsIface extends SignalOptionsIface {
|
|
|
49
49
|
/**
|
|
50
50
|
* Also recover what only realtime carries (default true). On every
|
|
51
51
|
* (re)subscribe, resync and fallback poll the stream re-reads the open
|
|
52
|
-
* permission and elicitation
|
|
53
|
-
* and emits replayed `
|
|
54
|
-
* has not emitted, `resolved` for one it emitted open that no
|
|
52
|
+
* permission and elicitation decisions of the conversation's agent sessions
|
|
53
|
+
* and emits replayed `decision.changed` events: `created` for an open
|
|
54
|
+
* decision it has not emitted, `resolved` for one it emitted open that no
|
|
55
|
+
* longer is. And
|
|
55
56
|
* for every turn it saw start whose session is no longer running, it emits a
|
|
56
57
|
* replayed `conversation.turn.ended` (stop reason null). Turns that started
|
|
57
58
|
* and ended while it was away arrive only as messages.
|
|
@@ -155,7 +156,7 @@ export interface ConversationHandleIface {
|
|
|
155
156
|
* source is accepted once; the receipt says whether the agent was prompted.
|
|
156
157
|
*/
|
|
157
158
|
postEvent: (event: PostConversationEventRequestIface, options?: SignalOptionsIface) => Promise<PostConversationEventResponseIface>;
|
|
158
|
-
respond: (args: RespondArgs, options?: SignalOptionsIface) => Promise<
|
|
159
|
+
respond: (args: RespondArgs, options?: SignalOptionsIface) => Promise<RespondDecisionResponseIface>;
|
|
159
160
|
/** Stops the agents' running turns in this conversation. */
|
|
160
161
|
cancel: (options?: SignalOptionsIface & {
|
|
161
162
|
reason?: string;
|
|
@@ -303,7 +304,7 @@ export interface ConversationAgentTurnIface {
|
|
|
303
304
|
participantId: string;
|
|
304
305
|
sessionId: string;
|
|
305
306
|
state: AgentSessionState | WireFallback;
|
|
306
|
-
/** A turn is running or waiting on a
|
|
307
|
+
/** A turn is running or waiting on a decision. */
|
|
307
308
|
running: boolean;
|
|
308
309
|
}
|
|
309
310
|
/** Session states in which a turn is open. */
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
createConversationRequestSchema,
|
|
28
28
|
createConversationScheduleRequestSchema,
|
|
29
29
|
createDirectConversationRequestSchema,
|
|
30
|
+
DecisionEventState,
|
|
31
|
+
decisionRecordListSchema,
|
|
30
32
|
decisionsPageSchema,
|
|
31
33
|
markReadRequestSchema,
|
|
32
34
|
messageDeliveriesSchema,
|
|
@@ -38,8 +40,6 @@ import {
|
|
|
38
40
|
ParticipantKind,
|
|
39
41
|
participantListSchema,
|
|
40
42
|
participantSchema,
|
|
41
|
-
PendingEventState,
|
|
42
|
-
pendingRequestListSchema,
|
|
43
43
|
postConversationEventRequestSchema,
|
|
44
44
|
postConversationEventResponseSchema,
|
|
45
45
|
pushDeliveryOpenResponseSchema,
|
|
@@ -47,8 +47,8 @@ import {
|
|
|
47
47
|
reactionsResponseSchema,
|
|
48
48
|
readStateSchema,
|
|
49
49
|
removeParticipantRequestSchema,
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
respondDecisionResponseSchema,
|
|
51
|
+
respondDecisionSchema,
|
|
52
52
|
sandboxDirectorySchema,
|
|
53
53
|
sandboxEditorAuthorizationSchema,
|
|
54
54
|
sandboxEditorSessionSchema,
|
|
@@ -90,7 +90,7 @@ const TURN_OPEN_SESSION_STATES = /* @__PURE__ */ new Set([
|
|
|
90
90
|
const conversationPath = (conversationId, suffix = "") => `/v1/conversations/${encodePath(conversationId)}${suffix}`;
|
|
91
91
|
const messagePath = (conversationId, messageId, suffix = "") => conversationPath(conversationId, `/messages/${encodePath(messageId)}${suffix}`);
|
|
92
92
|
const MAX_REMEMBERED = 4096;
|
|
93
|
-
const
|
|
93
|
+
const SESSION_DECISIONS_LIMIT = 200;
|
|
94
94
|
const remember = (map, key, value) => {
|
|
95
95
|
map.delete(key);
|
|
96
96
|
map.set(key, value);
|
|
@@ -99,7 +99,7 @@ const remember = (map, key, value) => {
|
|
|
99
99
|
if (oldest.done !== true) map.delete(oldest.value);
|
|
100
100
|
}
|
|
101
101
|
};
|
|
102
|
-
const isTransientEvent = (event) => event.kind === AgentRealtimeKind.
|
|
102
|
+
const isTransientEvent = (event) => event.kind === AgentRealtimeKind.DecisionChanged || event.kind === AgentRealtimeKind.TurnStarted || event.kind === AgentRealtimeKind.TurnEnded;
|
|
103
103
|
const REPLAY_PAGE = 100;
|
|
104
104
|
const RECOVERY_SKEW_MS = 3e4;
|
|
105
105
|
const stampOf = (message) => Math.max(
|
|
@@ -158,7 +158,7 @@ const streamConversationEvents = (args) => {
|
|
|
158
158
|
const held = [];
|
|
159
159
|
const recoverTransientState = args.options?.recoverTransient ?? true;
|
|
160
160
|
let transientClock = 0;
|
|
161
|
-
const
|
|
161
|
+
const decisionsSeen = /* @__PURE__ */ new Map();
|
|
162
162
|
const runningTurns = /* @__PURE__ */ new Map();
|
|
163
163
|
const endedTurns = /* @__PURE__ */ new Map();
|
|
164
164
|
let degraded = false;
|
|
@@ -202,24 +202,25 @@ const streamConversationEvents = (args) => {
|
|
|
202
202
|
previous == null ? { kind: AgentRealtimeKind.MessageCreated, message, ...meta } : { kind: AgentRealtimeKind.MessageUpdated, message, ...meta }
|
|
203
203
|
);
|
|
204
204
|
};
|
|
205
|
-
const
|
|
206
|
-
const seen =
|
|
207
|
-
if (state ===
|
|
208
|
-
if (seen?.state ===
|
|
205
|
+
const noteDecision = (decision, state) => {
|
|
206
|
+
const seen = decisionsSeen.get(decision.id);
|
|
207
|
+
if (state === DecisionEventState.Resolved) {
|
|
208
|
+
if (seen?.state === DecisionEventState.Resolved) return false;
|
|
209
209
|
transientClock += 1;
|
|
210
|
-
remember(
|
|
210
|
+
remember(decisionsSeen, decision.id, { state, decision, noted: transientClock });
|
|
211
211
|
return true;
|
|
212
212
|
}
|
|
213
|
-
if (state ===
|
|
213
|
+
if (state === DecisionEventState.Created) {
|
|
214
214
|
if (seen != null) return false;
|
|
215
215
|
transientClock += 1;
|
|
216
|
-
remember(
|
|
216
|
+
remember(decisionsSeen, decision.id, { state, decision, noted: transientClock });
|
|
217
217
|
return true;
|
|
218
218
|
}
|
|
219
219
|
return true;
|
|
220
220
|
};
|
|
221
221
|
const noteTransient = (event) => {
|
|
222
|
-
if (event.kind === AgentRealtimeKind.
|
|
222
|
+
if (event.kind === AgentRealtimeKind.DecisionChanged)
|
|
223
|
+
return noteDecision(event.decision, event.state);
|
|
223
224
|
if (event.kind === AgentRealtimeKind.TurnStarted) {
|
|
224
225
|
if (!endedTurns.has(event.turnId)) {
|
|
225
226
|
transientClock += 1;
|
|
@@ -353,8 +354,8 @@ const streamConversationEvents = (args) => {
|
|
|
353
354
|
)
|
|
354
355
|
);
|
|
355
356
|
const remembered = /* @__PURE__ */ new Set([
|
|
356
|
-
...[...
|
|
357
|
-
(seen) => seen.state ===
|
|
357
|
+
...[...decisionsSeen.values()].flatMap(
|
|
358
|
+
(seen) => seen.state === DecisionEventState.Created && seen.decision.sessionId != null ? [seen.decision.sessionId] : []
|
|
358
359
|
),
|
|
359
360
|
...[...runningTurns.values()].map((entry) => entry.turn.sessionId)
|
|
360
361
|
]);
|
|
@@ -362,7 +363,7 @@ const streamConversationEvents = (args) => {
|
|
|
362
363
|
[.../* @__PURE__ */ new Set([...current ?? [], ...remembered])].map(async (sessionId) => {
|
|
363
364
|
const [session, open] = await Promise.all([
|
|
364
365
|
optional(() => args.getSession(sessionId, { signal })),
|
|
365
|
-
optional(() => args.
|
|
366
|
+
optional(() => args.sessionDecisions(sessionId, { signal }))
|
|
366
367
|
]);
|
|
367
368
|
const departed = current != null && !current.has(sessionId);
|
|
368
369
|
return {
|
|
@@ -374,24 +375,24 @@ const streamConversationEvents = (args) => {
|
|
|
374
375
|
);
|
|
375
376
|
for (const { sessionId, running, open } of checked) {
|
|
376
377
|
if (open != null) {
|
|
377
|
-
const complete = open.length <
|
|
378
|
-
const openIds = new Set(open.map((
|
|
379
|
-
for (const
|
|
380
|
-
if (
|
|
378
|
+
const complete = open.length < SESSION_DECISIONS_LIMIT;
|
|
379
|
+
const openIds = new Set(open.map((decision) => decision.id));
|
|
380
|
+
for (const decision of open) {
|
|
381
|
+
if (noteDecision(decision, DecisionEventState.Created))
|
|
381
382
|
emitRecovered({
|
|
382
|
-
kind: AgentRealtimeKind.
|
|
383
|
-
|
|
384
|
-
state:
|
|
383
|
+
kind: AgentRealtimeKind.DecisionChanged,
|
|
384
|
+
decision,
|
|
385
|
+
state: DecisionEventState.Created
|
|
385
386
|
});
|
|
386
387
|
}
|
|
387
|
-
for (const seen of [...
|
|
388
|
-
if (!complete || seen.state !==
|
|
388
|
+
for (const seen of [...decisionsSeen.values()]) {
|
|
389
|
+
if (!complete || seen.state !== DecisionEventState.Created || seen.noted > asOf || seen.decision.sessionId !== sessionId || openIds.has(seen.decision.id))
|
|
389
390
|
continue;
|
|
390
|
-
|
|
391
|
+
noteDecision(seen.decision, DecisionEventState.Resolved);
|
|
391
392
|
emitRecovered({
|
|
392
|
-
kind: AgentRealtimeKind.
|
|
393
|
-
|
|
394
|
-
state:
|
|
393
|
+
kind: AgentRealtimeKind.DecisionChanged,
|
|
394
|
+
decision: seen.decision,
|
|
395
|
+
state: DecisionEventState.Resolved
|
|
395
396
|
});
|
|
396
397
|
}
|
|
397
398
|
}
|
|
@@ -623,12 +624,12 @@ const createConversationHandle = (context, conversationId) => {
|
|
|
623
624
|
response: sessionSchema,
|
|
624
625
|
signal: options.signal
|
|
625
626
|
});
|
|
626
|
-
const
|
|
627
|
+
const sessionDecisions = async (sessionId, options) => (await http.request({
|
|
627
628
|
method: HttpMethod.Get,
|
|
628
|
-
path: `/v1/sessions/${encodePath(sessionId)}/
|
|
629
|
-
response:
|
|
629
|
+
path: `/v1/sessions/${encodePath(sessionId)}/decisions`,
|
|
630
|
+
response: decisionRecordListSchema,
|
|
630
631
|
signal: options.signal
|
|
631
|
-
})).
|
|
632
|
+
})).decisions;
|
|
632
633
|
const reaction = async (args) => (await http.request({
|
|
633
634
|
method: args.method,
|
|
634
635
|
path: messagePath(conversationId, args.messageId, "/reactions"),
|
|
@@ -678,7 +679,7 @@ const createConversationHandle = (context, conversationId) => {
|
|
|
678
679
|
listMessages,
|
|
679
680
|
listParticipants,
|
|
680
681
|
getSession,
|
|
681
|
-
|
|
682
|
+
sessionDecisions
|
|
682
683
|
}),
|
|
683
684
|
messages: (options) => listMessages(options),
|
|
684
685
|
deliveries: async (args, options) => (await http.request({
|
|
@@ -740,12 +741,12 @@ const createConversationHandle = (context, conversationId) => {
|
|
|
740
741
|
method: HttpMethod.Post,
|
|
741
742
|
path: conversationPath(conversationId, "/respond"),
|
|
742
743
|
body: {
|
|
743
|
-
schema:
|
|
744
|
-
value: {
|
|
744
|
+
schema: respondDecisionSchema,
|
|
745
|
+
value: { decisionId: args.decisionId, outcome: outcomeOf(args) }
|
|
745
746
|
},
|
|
746
|
-
response:
|
|
747
|
+
response: respondDecisionResponseSchema,
|
|
747
748
|
signal: options?.signal,
|
|
748
|
-
context: {
|
|
749
|
+
context: { decisionId: args.decisionId }
|
|
749
750
|
}),
|
|
750
751
|
cancel: async (options) => {
|
|
751
752
|
await http.request({
|