@stack-spot/portal-network 0.100.0 → 0.102.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.
@@ -24,7 +24,7 @@ export type FoundationResponse = {
24
24
  stackSpotAccountId: string;
25
25
  foundationId: string;
26
26
  details: FoundationDetails;
27
- status: "READY" | "PENDING" | "ERROR";
27
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
28
28
  createdAt: string;
29
29
  updatedAt: string;
30
30
  deletedAt?: string;
@@ -40,6 +40,35 @@ export type CreateFoundationRequest = {
40
40
  cloudProvider: string;
41
41
  region: string;
42
42
  };
43
+ export type TunnelStatus = {
44
+ tunnel1?: string;
45
+ tunnel2?: string;
46
+ };
47
+ export type VpnDetails = {
48
+ peerCustomerGatewayIp: string;
49
+ destinationCidrBlock: string;
50
+ tunnelStatus: TunnelStatus;
51
+ };
52
+ export type VpnResponse = {
53
+ stackSpotAccountId: string;
54
+ foundationId: string;
55
+ vpnId: string;
56
+ details: VpnDetails;
57
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
58
+ createdAt: string;
59
+ updatedAt: string;
60
+ deletedAt?: string;
61
+ };
62
+ export type ListVpnResponse = {
63
+ stackSpotAccountId: string;
64
+ foundationId: string;
65
+ pendingResources: boolean;
66
+ content: VpnResponse[];
67
+ };
68
+ export type CreateVpnRequest = {
69
+ peerCustomerGatewayIp: string;
70
+ destinationCidrBlock: string;
71
+ };
43
72
  export type ProjectDetails = {
44
73
  name: string;
45
74
  folderName: string;
@@ -50,7 +79,7 @@ export type ProjectResponse = {
50
79
  parentFolderId: string;
51
80
  projectId: string;
52
81
  details: ProjectDetails;
53
- status: "READY" | "PENDING" | "ERROR";
82
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
54
83
  createdAt: string;
55
84
  updatedAt: string;
56
85
  deletedAt?: string;
@@ -78,7 +107,7 @@ export type NetworkResponse = {
78
107
  projectId: string;
79
108
  networkId: string;
80
109
  details: NetworkDetails;
81
- status: "READY" | "PENDING" | "ERROR";
110
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
82
111
  createdAt: string;
83
112
  updatedAt: string;
84
113
  deletedAt?: string;
@@ -110,7 +139,7 @@ export type NetworkConnectionResponse = {
110
139
  requesterProjectId: string;
111
140
  networkConnectionId: string;
112
141
  details: NetworkConnectionDetails;
113
- status: "READY" | "PENDING" | "ERROR";
142
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
114
143
  createdAt: string;
115
144
  updatedAt: string;
116
145
  deletedAt?: string;
@@ -138,7 +167,7 @@ export type InboundResponse = {
138
167
  projectId: string;
139
168
  inboundId: string;
140
169
  details: InboundDetails;
141
- status: "READY" | "PENDING" | "ERROR";
170
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
142
171
  createdAt: string;
143
172
  updatedAt: string;
144
173
  deletedAt?: string;
@@ -159,7 +188,7 @@ export type FolderContent = {
159
188
  "type": "FOLDER" | "PROJECT";
160
189
  id: string;
161
190
  name: string;
162
- status: "READY" | "PENDING" | "ERROR";
191
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
163
192
  };
164
193
  export type FolderDetails = {
165
194
  name: string;
@@ -172,7 +201,7 @@ export type FolderResponse = {
172
201
  parentFolderId: string;
173
202
  folderId: string;
174
203
  details: FolderDetails;
175
- status: "READY" | "PENDING" | "ERROR";
204
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
176
205
  createdAt: string;
177
206
  updatedAt: string;
178
207
  deletedAt?: string;
@@ -190,7 +219,7 @@ export type DnsZoneResponse = {
190
219
  foundationId: string;
191
220
  dnsZoneId: string;
192
221
  details: DnsZoneDetails;
193
- status: "READY" | "PENDING" | "ERROR";
222
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
194
223
  createdAt: string;
195
224
  updatedAt: string;
196
225
  deletedAt?: string;
@@ -219,7 +248,7 @@ export type DnsRecordResponse = {
219
248
  dnsZoneId: string;
220
249
  dnsRecordId: string;
221
250
  details: DnsRecordDetails;
222
- status: "READY" | "PENDING" | "ERROR";
251
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
223
252
  createdAt: string;
224
253
  updatedAt: string;
225
254
  deletedAt?: string;
@@ -254,7 +283,7 @@ export type CidrResponse = {
254
283
  foundationId: string;
255
284
  cidrId: string;
256
285
  details: CidrDetails;
257
- status: "READY" | "PENDING" | "ERROR";
286
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
258
287
  createdAt: string;
259
288
  updatedAt: string;
260
289
  deletedAt?: string;
@@ -291,7 +320,7 @@ export type CertificateResponse = {
291
320
  foundationId: string;
292
321
  certificateId: string;
293
322
  details: CertificateDetails;
294
- status: "READY" | "PENDING" | "ERROR";
323
+ status: "READY" | "PENDING" | "PENDING_APPROVAL" | "ERROR";
295
324
  createdAt: string;
296
325
  updatedAt: string;
297
326
  deletedAt?: string;
@@ -325,6 +354,15 @@ export type ImportCertificateRequest = {
325
354
  certificatePrivateKey?: string;
326
355
  certificateChain?: string;
327
356
  };
357
+ export type Configuration = {
358
+ ikev1: string;
359
+ ikev2: string;
360
+ };
361
+ export type VpnConfigurationResponse = {
362
+ vpnId: string;
363
+ foundationId: string;
364
+ configuration: Configuration;
365
+ };
328
366
  export function listFoundations({ authorization }: {
329
367
  authorization: string;
330
368
  }, opts?: Oazapfts.RequestOpts) {
@@ -354,6 +392,37 @@ export function createFoundation({ authorization, createFoundationRequest }: {
354
392
  })
355
393
  })));
356
394
  }
395
+ export function listVpns({ authorization, foundationId }: {
396
+ authorization: string;
397
+ foundationId: string;
398
+ }, opts?: Oazapfts.RequestOpts) {
399
+ return oazapfts.ok(oazapfts.fetchJson<{
400
+ status: 200;
401
+ data: ListVpnResponse;
402
+ }>(`/v1/foundations/${encodeURIComponent(foundationId)}/vpns`, {
403
+ ...opts,
404
+ headers: oazapfts.mergeHeaders(opts?.headers, {
405
+ Authorization: authorization
406
+ })
407
+ }));
408
+ }
409
+ export function createVpn({ authorization, foundationId, createVpnRequest }: {
410
+ authorization: string;
411
+ foundationId: string;
412
+ createVpnRequest: CreateVpnRequest;
413
+ }, opts?: Oazapfts.RequestOpts) {
414
+ return oazapfts.ok(oazapfts.fetchJson<{
415
+ status: 200;
416
+ data: VpnResponse;
417
+ }>(`/v1/foundations/${encodeURIComponent(foundationId)}/vpns`, oazapfts.json({
418
+ ...opts,
419
+ method: "POST",
420
+ body: createVpnRequest,
421
+ headers: oazapfts.mergeHeaders(opts?.headers, {
422
+ Authorization: authorization
423
+ })
424
+ })));
425
+ }
357
426
  export function listProject({ authorization, foundationId, parentFolderId }: {
358
427
  authorization: string;
359
428
  foundationId: string;
@@ -694,6 +763,36 @@ export function getFoundation({ authorization, foundationId }: {
694
763
  })
695
764
  }));
696
765
  }
766
+ export function getVpn({ authorization, foundationId, vpnId }: {
767
+ authorization: string;
768
+ foundationId: string;
769
+ vpnId: string;
770
+ }, opts?: Oazapfts.RequestOpts) {
771
+ return oazapfts.ok(oazapfts.fetchJson<{
772
+ status: 200;
773
+ data: VpnResponse;
774
+ }>(`/v1/foundations/${encodeURIComponent(foundationId)}/vpns/${encodeURIComponent(vpnId)}`, {
775
+ ...opts,
776
+ headers: oazapfts.mergeHeaders(opts?.headers, {
777
+ Authorization: authorization
778
+ })
779
+ }));
780
+ }
781
+ export function getVpnConfiguration({ authorization, foundationId, vpnId }: {
782
+ authorization: string;
783
+ foundationId: string;
784
+ vpnId: string;
785
+ }, opts?: Oazapfts.RequestOpts) {
786
+ return oazapfts.ok(oazapfts.fetchJson<{
787
+ status: 200;
788
+ data: VpnConfigurationResponse;
789
+ }>(`/v1/foundations/${encodeURIComponent(foundationId)}/vpns/${encodeURIComponent(vpnId)}/configuration`, {
790
+ ...opts,
791
+ headers: oazapfts.mergeHeaders(opts?.headers, {
792
+ Authorization: authorization
793
+ })
794
+ }));
795
+ }
697
796
  export function getProject({ authorization, foundationId, projectId }: {
698
797
  authorization: string;
699
798
  foundationId: string;
package/src/client/ai.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { HttpError } from '@oazapfts/runtime'
2
+ import { last } from 'lodash'
2
3
  import {
3
4
  addFavoriteV1FavoritesPost,
4
- ChatResponse3,
5
5
  conversationHistoryV1ConversationsConversationIdGet,
6
6
  defaults,
7
7
  deleteConversationV1ConversationsConversationIdDelete,
@@ -30,7 +30,16 @@ import { StackspotAPIError } from '../error/StackspotAPIError'
30
30
  import { ReactQueryNetworkClient } from '../network/ReactQueryNetworkClient'
31
31
  import { removeAuthorizationParam } from '../utils/remove-authorization-param'
32
32
  import { StreamedJson } from '../utils/StreamedJson'
33
- import { FixedChatRequest, FixedConversationResponse, FixedDependencyResponse, ReplaceResult } from './types'
33
+ import { agentClient } from './agent'
34
+ import {
35
+ ChatAgentTool,
36
+ ChatResponseWithSteps,
37
+ FixedChatRequest,
38
+ FixedChatResponse,
39
+ FixedConversationResponse,
40
+ FixedDependencyResponse,
41
+ ReplaceResult,
42
+ } from './types'
34
43
 
35
44
  class AIClient extends ReactQueryNetworkClient {
36
45
  constructor() {
@@ -46,42 +55,107 @@ class AIClient extends ReactQueryNetworkClient {
46
55
  })
47
56
  }
48
57
 
58
+ /**
59
+ * Chat: runs a quick action.
60
+ */
49
61
  runQuickAction = this.mutation(removeAuthorizationParam(quickActionsV1QuickActionsPost))
62
+ /**
63
+ * Lists the AI Stacks according to their visibilities.
64
+ */
50
65
  aiStacks = this.query(removeAuthorizationParam(listAiStacksV1AiStacksGet))
66
+ /**
67
+ * Gets a workspace by its id.
68
+ */
51
69
  workspace = this.query(removeAuthorizationParam(listAssociationV1WorkspaceWorkspaceIdGet))
70
+ /**
71
+ * Lists the quick commands according to filters passed as parameter.
72
+ */
52
73
  quickCommands = this.query(removeAuthorizationParam(listAllV1QuickCommandsAllGet))
74
+ /**
75
+ * Gets a quick command by its slug.
76
+ */
53
77
  quickCommand = this.query(removeAuthorizationParam(getQuickCommandV1QuickCommandsSlugGet))
78
+ /**
79
+ * Lists the knowledge sources according to filters passed as parameter.
80
+ */
54
81
  knowledgeSources = this.query(removeAuthorizationParam(listKnowledgeSourcesV1KnowledgeSourcesGet))
82
+ /**
83
+ * Gets a knowledge source by its slug.
84
+ */
55
85
  knowledgeSource = this.query(removeAuthorizationParam(findKnowledgeSourceV1KnowledgeSourcesSlugGet))
86
+ /**
87
+ * Gets a knowledge source document by the slug of the parent knowledge source and the document id.
88
+ */
56
89
  knowledgeSourceDocument = this.query(removeAuthorizationParam(findKnowledgeObjectByCustomIdV1KnowledgeSourcesSlugObjectsCustomIdGet))
90
+ /**
91
+ * Gets the chat history. This is a paginated resource.
92
+ */
57
93
  chats = this.infiniteQuery(removeAuthorizationParam(listConversationsV1ConversationsGet))
94
+ /**
95
+ * Gets a specific chat from the history according to its id.
96
+ */
58
97
  chat = this.query(removeAuthorizationParam(
59
98
  conversationHistoryV1ConversationsConversationIdGet as ReplaceResult<
60
99
  typeof conversationHistoryV1ConversationsConversationIdGet,
61
100
  FixedConversationResponse
62
101
  >,
63
102
  ))
103
+ /**
104
+ * Deletes a chat from the chat history.
105
+ */
64
106
  deleteChat = this.mutation(removeAuthorizationParam(deleteConversationV1ConversationsConversationIdDelete))
107
+ /**
108
+ * Gets a plain text version of the chat with id passed as parameter.
109
+ */
65
110
  downloadChat = this.mutation(removeAuthorizationParam(downloadConversationV1ConversationsConversationIdDownloadGet))
111
+ /**
112
+ * Renames a chat.
113
+ */
66
114
  renameChat = this.mutation(removeAuthorizationParam(updateTitleV1ConversationsConversationIdPatch))
115
+ /**
116
+ * Creates an event so it can be used as metric by the product designers (analytics).
117
+ */
67
118
  createEvent = this.mutation(removeAuthorizationParam(postEventV1EventsPost))
119
+ /**
120
+ * Runs a step of type "fetch" of a quick command.
121
+ */
68
122
  fetchStepOfQuickCommand = this.mutation(removeAuthorizationParam(formatFetchStepV1QuickCommandsSlugStepsStepSlugFetchFormatPost))
123
+ /**
124
+ * Runs a step of type "llm" of a quick command.
125
+ */
69
126
  llmStepOfQuickCommand = this.mutation(removeAuthorizationParam(quickCommandsRunV2V2QuickCommandsSlugStepsStepSlugRunPost))
127
+ /**
128
+ * Formats the result of a quick command into a human-readable text (markdown).
129
+ */
70
130
  formatResultOfQuickCommand = this.mutation(removeAuthorizationParam(formatResultV1QuickCommandsSlugResultFormatPost))
71
131
  /**
72
- * List of favorites by type
73
- */
132
+ * Lists the resources of type "$type" marked as favorites.
133
+ */
74
134
  listFavoritesByType = this.query(removeAuthorizationParam(getFavoritesByTypeV1FavoritesGet))
75
135
  /**
76
- * Remove favorite from list
77
- */
136
+ * Removes the resource of type "favoriteRequest.type" from the list of favorites.
137
+ */
78
138
  removeFavorite = this.mutation(removeAuthorizationParam(deleteFavoriteV1FavoritesDelete))
79
139
  /**
80
- * Add favorite to list
81
- */
140
+ * Adds the resource of type "favoriteRequest.type" to the list of favorites.
141
+ */
82
142
  addFavorite = this.mutation(removeAuthorizationParam(addFavoriteV1FavoritesPost))
83
143
 
84
- sendChatMessage(request: FixedChatRequest, minChangeIntervalMS?: number): StreamedJson<ChatResponse3> {
144
+ private static async toolsOfAgent(agentId?: string) {
145
+ try {
146
+ const agent = agentId ? await agentClient.agent.query({ agentId }) : undefined
147
+ if (!agent) return []
148
+ const tools: (Omit<ChatAgentTool, 'duration' | 'prompt' | 'output'>)[] = []
149
+ agent.toolkits?.builtins?.forEach(kit => kit.tools?.forEach(({ id, name, description }) => {
150
+ if (id) tools.push({ image: kit.image_url, id, name: name || id, description })
151
+ }))
152
+ return tools
153
+ } catch {
154
+ return []
155
+ }
156
+ }
157
+
158
+ sendChatMessage(request: FixedChatRequest, minChangeIntervalMS?: number): StreamedJson<ChatResponseWithSteps> {
85
159
  const abortController = new AbortController()
86
160
  const headers = {
87
161
  'Content-Type': 'application/json',
@@ -91,7 +165,92 @@ class AIClient extends ReactQueryNetworkClient {
91
165
  this.resolveURL('v3/chat'),
92
166
  { method: 'post', body: JSON.stringify(request), headers, signal: abortController.signal },
93
167
  )
94
- return new StreamedJson(events, abortController, minChangeIntervalMS)
168
+
169
+ /**
170
+ * This function treats events in the streaming that deals with the execution of tools. Since these events are not concatenated like
171
+ * normal streamings of data, we need this separate function to deal with them. It transforms the internal data model of the
172
+ * StreamedJson object whenever an event is triggered.
173
+ */
174
+ async function transform(event: Partial<FixedChatResponse>, data: Partial<ChatResponseWithSteps>) {
175
+ const info = event.agent_info
176
+ if (!info) return
177
+ data.steps = data.steps ? [...data.steps] : []
178
+
179
+ if (info.type === 'planning' && info.action === 'end') {
180
+ const tools = await AIClient.toolsOfAgent(request.context?.agent_id)
181
+ data.steps.push(
182
+ { id: 'planning', type: 'planning', duration: info.duration, input: info.data?.plan_goal ?? '', status: 'success' },
183
+ )
184
+ info.data?.steps.forEach(s => data.steps?.push({
185
+ id: s.id,
186
+ type: 'step',
187
+ input: s.goal,
188
+ status: 'pending',
189
+ tools: s.tools?.map(t => ({
190
+ ...(tools.find(({ id }) => id === t.tool_id) ?? { id: t.tool_id, name: t.tool_id }),
191
+ executionId: t.tool_execution_id,
192
+ })),
193
+ }))
194
+ data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
195
+ }
196
+
197
+ if (info.type === 'step' && info.action === 'start') {
198
+ const step = data.steps.find(s => s.id === info.id)
199
+ if (step) step.status = 'running'
200
+ }
201
+
202
+ if (info.type === 'step' && info.action === 'end') {
203
+ const step = data.steps.find(s => s.id === info.id)
204
+ if (step) {
205
+ step.status = 'success'
206
+ step.duration = info.duration
207
+ step.output = last(step.tools)?.output
208
+ }
209
+ }
210
+
211
+ if (info.type === 'tool' && info.action === 'start') {
212
+ const currentStep = data.steps.find(s => s.status === 'running')
213
+ const tool = currentStep?.tools?.find(t => t.executionId === info.id)
214
+ if (tool) {
215
+ tool.input = info.data ? JSON.stringify(info.data.input, null, 2) : undefined
216
+ }
217
+ }
218
+
219
+ if (info.type === 'tool' && info.action === 'end') {
220
+ const currentStep = data.steps.find(s => s.status === 'running')
221
+ const tool = currentStep?.tools?.find(t => t.executionId === info.id)
222
+ if (tool) {
223
+ tool.output = info.data?.output
224
+ tool.duration ??= 0
225
+ tool.duration += info.duration ?? 0
226
+ }
227
+ }
228
+
229
+ if (info.type === 'final_answer' && info.action === 'start') {
230
+ // if we get a "final_answer" event while there's a previous step still running, it means that step has failed.
231
+ data.steps?.forEach((s) => {
232
+ if (s.type === 'step' && s.status === 'running') s.status = 'error'
233
+ })
234
+ const answerStep = last(data.steps)
235
+ if (answerStep) answerStep.status = 'running'
236
+ }
237
+
238
+ if (info.type === 'final_answer' && info.action === 'end') {
239
+ const answerStep = last(data.steps)
240
+ if (answerStep) {
241
+ answerStep.status = 'success'
242
+ answerStep.duration = info.duration
243
+ }
244
+ }
245
+ }
246
+
247
+ return new StreamedJson({
248
+ eventsPromise: events,
249
+ abortController,
250
+ minChangeIntervalMS,
251
+ ignoreKeys: ['agent_info'],
252
+ transform,
253
+ })
95
254
  }
96
255
 
97
256
  contentDependencies = this.query(removeAuthorizationParam(
@@ -12,11 +12,13 @@ import
12
12
  createNetwork,
13
13
  createNetworkConnection,
14
14
  createProject,
15
+ createVpn,
15
16
  defaults,
16
17
  getCertificate,
17
18
  getFolder,
18
19
  getFoundation,
19
20
  getProject,
21
+ getVpnConfiguration,
20
22
  listCertificates,
21
23
  listCidr,
22
24
  listDnsRecord,
@@ -25,6 +27,7 @@ import
25
27
  listInbound,
26
28
  listNetwork,
27
29
  listNetworkConnection,
30
+ listVpns,
28
31
  providers,
29
32
  } from '../api/cloudPlatform'
30
33
  import apis from '../apis.json'
@@ -138,6 +141,18 @@ class CloudPlatformClient extends ReactQueryNetworkClient {
138
141
  * Accept a network connection
139
142
  */
140
143
  acceptNetworkConnection = this.mutation(removeAuthorizationParam(acceptNetworkConnection))
144
+ /**
145
+ * Get a list of vpn's
146
+ */
147
+ listVpns = this.query(removeAuthorizationParam(listVpns))
148
+ /**
149
+ * Get vpn configuration
150
+ */
151
+ getVpnConfiguration = this.query(removeAuthorizationParam(getVpnConfiguration))
152
+ /**
153
+ * Create a vpn
154
+ */
155
+ createVpn = this.mutation(removeAuthorizationParam(createVpn))
141
156
  }
142
157
 
143
158
  export const cloudPlatformClient = new CloudPlatformClient()
@@ -227,6 +227,78 @@ export interface FixedAddResourceToWorkspaceAi {
227
227
  error_id: string[],
228
228
  }
229
229
 
230
+ export interface ChatAgentTool {
231
+ id: string,
232
+ executionId?: string,
233
+ duration?: number,
234
+ name: string,
235
+ description?: string,
236
+ image?: string,
237
+ input?: string,
238
+ output?: string,
239
+ }
240
+
241
+ export interface ChatStep {
242
+ id: string,
243
+ type: 'planning' | 'step' | 'answer',
244
+ duration?: number,
245
+ input?: string,
246
+ output?: string,
247
+ status: 'pending' | 'running' | 'success' | 'error',
248
+ tools?: ChatAgentTool[],
249
+ }
250
+
251
+ export interface BaseAgentInfo {
252
+ type: 'chat' | 'planning' | 'step' | 'tool' | 'final_answer',
253
+ action: 'start' | 'end',
254
+ duration?: number,
255
+ }
256
+
257
+ export interface PlanningAgentInfo extends BaseAgentInfo {
258
+ type: 'planning',
259
+ data?: {
260
+ plan_goal: string,
261
+ total_steps: number,
262
+ steps: {
263
+ id: string,
264
+ goal: string,
265
+ tools?: {
266
+ tool_id: string,
267
+ tool_execution_id: string,
268
+ }[],
269
+ }[],
270
+ },
271
+ }
272
+
273
+ export interface StepAgentInfo extends BaseAgentInfo {
274
+ type: 'step',
275
+ id: string,
276
+ }
277
+
278
+ export interface ToolAgentInfo extends BaseAgentInfo {
279
+ type: 'tool',
280
+ id: string,
281
+ data?: {
282
+ input?: any,
283
+ attempt: number,
284
+ output?: string,
285
+ },
286
+ }
287
+
288
+ export interface GenericAgentInfo extends BaseAgentInfo {
289
+ type: 'chat' | 'final_answer',
290
+ }
291
+
292
+ export type AgentInfo = GenericAgentInfo | PlanningAgentInfo | StepAgentInfo | ToolAgentInfo
293
+
294
+ export interface FixedChatResponse extends ChatResponse3 {
295
+ agent_info: AgentInfo,
296
+ }
297
+
298
+ export interface ChatResponseWithSteps extends FixedChatResponse {
299
+ steps: ChatStep[],
300
+ }
301
+
230
302
  export type OazapftsFunction<Variables = any, Result = any> = (variables: Variables, opts?: RequestOpts) => Promise<Result>
231
303
 
232
304
  type Unpromisify<T> = T extends Promise<infer R> ? Unpromisify<R> : T
@@ -7,6 +7,32 @@ import { FetchEventStream } from '../network/types'
7
7
  type StreamingStatus = 'pending' | 'success' | 'error'
8
8
  type OnChangeListener<T> = (value: Partial<T>) => void
9
9
 
10
+ interface ConstructorParams<T> {
11
+ /**
12
+ * The promise that results in the EventStream to build the final json.
13
+ */
14
+ eventsPromise: Promise<FetchEventStream>,
15
+ /**
16
+ * A controller to abort the streaming.
17
+ */
18
+ abortController: AbortController,
19
+ /**
20
+ * A minimum number of milliseconds to wait before calling onChange listeners.
21
+ * @default 50
22
+ */
23
+ minChangeIntervalMS?: number,
24
+ /**
25
+ * Optional. If set, this function will be called with every streaming event and must transform the current data object according to the
26
+ * message received.
27
+ */
28
+ transform?: (event: Partial<T>, data: Partial<T>) => void | Promise<void>,
29
+ /**
30
+ * Optional. Keys to ignore when merging the result with the current event. Ignored keys are always replaced by the newest value instead
31
+ * of merged.
32
+ */
33
+ ignoreKeys?: (keyof T)[],
34
+ }
35
+
10
36
  /**
11
37
  * An object represented by a JSON stream. This can be watched as the stream runs.
12
38
  */
@@ -16,13 +42,17 @@ export class StreamedJson<T> {
16
42
  private data: Partial<T> = {}
17
43
  private fullPromise = new CompletablePromise<T>()
18
44
  private abortController: AbortController | undefined
45
+ private transform?: (event: Partial<T>, data: Partial<T>) => void | Promise<void>
46
+ private ignoreKeys?: (keyof T)[]
19
47
 
20
48
  /**
21
49
  * @param response the fetch response.
22
50
  * @param minChangeIntervalMS a stream can be too fast. This sets a minimum interval between running the listeners. The default is 50ms.
23
51
  */
24
- constructor(eventsPromise: Promise<FetchEventStream>, abortController: AbortController, minChangeIntervalMS = 50) {
52
+ constructor({ eventsPromise, abortController, minChangeIntervalMS = 50, transform, ignoreKeys }: ConstructorParams<T>) {
25
53
  this.abortController = abortController
54
+ this.transform = transform
55
+ this.ignoreKeys = ignoreKeys
26
56
  this.run(eventsPromise, minChangeIntervalMS)
27
57
  }
28
58
 
@@ -35,7 +65,8 @@ export class StreamedJson<T> {
35
65
  if (this.error) return
36
66
  if (event.data) {
37
67
  const json = JSON.parse(event.data)
38
- StreamedJson.merge(json, this.data)
68
+ await this.transform?.(json, this.data)
69
+ this.merge(json, this.data)
39
70
  if (new Date().getTime() - lastChangeCall >= minChangeIntervalMS) {
40
71
  this.onChangeListeners.forEach(l => l(this.data))
41
72
  lastChangeCall = new Date().getTime()
@@ -55,9 +86,9 @@ export class StreamedJson<T> {
55
86
  if (!this.error) this.complete()
56
87
  }
57
88
 
58
- private static merge(source: Record<string, any>, target: Record<string, any>) {
89
+ private merge(source: Record<string, any>, target: Record<string, any>) {
59
90
  Object.keys(source).forEach((k) => {
60
- if (typeof source[k] !== typeof target[k]) target[k] = source[k]
91
+ if (this.ignoreKeys?.includes(k as keyof T) || typeof source[k] !== typeof target[k]) target[k] = source[k]
61
92
  else if (typeof source[k] === 'string') target[k] += source[k]
62
93
  else if (Array.isArray(source[k])) target[k].push(...source[k])
63
94
  else if (typeof source[k] === 'number') parseFloat(target[k] + source[k])