@stack-spot/portal-network 1.0.0-dev.1769537511491 → 1.0.0-dev.1769783915267
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/CHANGELOG.md +42 -0
- package/dist/api/accountAssetManager.d.ts +1 -0
- package/dist/api/accountAssetManager.d.ts.map +1 -1
- package/dist/api/accountAssetManager.js.map +1 -1
- package/dist/api/cloudPlatform.d.ts +79 -33
- package/dist/api/cloudPlatform.d.ts.map +1 -1
- package/dist/api/cloudPlatform.js +86 -76
- package/dist/api/cloudPlatform.js.map +1 -1
- package/dist/api/codeShift.d.ts +7 -3
- package/dist/api/codeShift.d.ts.map +1 -1
- package/dist/api/codeShift.js +3 -2
- package/dist/api/codeShift.js.map +1 -1
- package/dist/api/genAiInference.d.ts +49 -2
- package/dist/api/genAiInference.d.ts.map +1 -1
- package/dist/api/genAiInference.js +55 -2
- package/dist/api/genAiInference.js.map +1 -1
- package/dist/apis.json +3 -3
- package/dist/client/account.d.ts +16 -0
- package/dist/client/account.d.ts.map +1 -1
- package/dist/client/account.js +9 -0
- package/dist/client/account.js.map +1 -1
- package/dist/client/ai.d.ts +5 -28
- package/dist/client/ai.d.ts.map +1 -1
- package/dist/client/ai.js +1 -560
- package/dist/client/ai.js.map +1 -1
- package/dist/client/cloud-platform.d.ts +4 -12
- package/dist/client/cloud-platform.d.ts.map +1 -1
- package/dist/client/cloud-platform.js +5 -14
- package/dist/client/cloud-platform.js.map +1 -1
- package/dist/client/code-shift.d.ts +2 -1
- package/dist/client/code-shift.d.ts.map +1 -1
- package/dist/client/discover.d.ts +11 -4
- package/dist/client/discover.d.ts.map +1 -1
- package/dist/client/discover.js +140 -136
- package/dist/client/discover.js.map +1 -1
- package/dist/client/gen-ai-inference.d.ts +4 -0
- package/dist/client/gen-ai-inference.d.ts.map +1 -1
- package/dist/client/gen-ai-inference.js +267 -0
- package/dist/client/gen-ai-inference.js.map +1 -1
- package/dist/client/types.d.ts +12 -14
- package/dist/client/types.d.ts.map +1 -1
- package/dist/utils/StreamedJson.d.ts +7 -1
- package/dist/utils/StreamedJson.d.ts.map +1 -1
- package/dist/utils/StreamedJson.js +10 -2
- package/dist/utils/StreamedJson.js.map +1 -1
- package/package.json +1 -1
- package/src/api/accountAssetManager.ts +1 -0
- package/src/api/cloudPlatform.ts +170 -111
- package/src/api/codeShift.ts +9 -4
- package/src/api/genAiInference.ts +119 -3
- package/src/apis.json +8 -8
- package/src/client/account.ts +4 -0
- package/src/client/ai.ts +1 -626
- package/src/client/cloud-platform.ts +4 -9
- package/src/client/discover.ts +151 -137
- package/src/client/gen-ai-inference.ts +281 -0
- package/src/client/types.ts +13 -14
- package/src/utils/StreamedJson.tsx +11 -2
package/src/client/discover.ts
CHANGED
|
@@ -2,16 +2,25 @@ import { HttpError } from '@oazapfts/runtime'
|
|
|
2
2
|
import { findLast, last } from 'lodash'
|
|
3
3
|
import { getApiAddresses } from '../api-addresses'
|
|
4
4
|
import { ConversationResponse } from '../api/ai'
|
|
5
|
-
import { create, create1, create2, defaults, deleteById, deleteById1, deleteById2, fetchInsights, getAll, getAll1, getAll2, getAllByHypothesis, getById, getById1, getById2, getInsightById, GetOpportunityResponse,
|
|
5
|
+
import { AiChatRequest, create, create1, create2, defaults, deleteById, deleteById1, deleteById2, fetchInsights, getAll, getAll1, getAll2, getAllByHypothesis, getById, getById1, getById2, getInsightById, GetOpportunityResponse, refreshInsights } from '../api/discover'
|
|
6
6
|
import { DefaultAPIError } from '../error/DefaultAPIError'
|
|
7
|
+
import { baseDictionary } from '../error/dictionary/base'
|
|
7
8
|
import { StackspotAPIError } from '../error/StackspotAPIError'
|
|
9
|
+
import { ReactQueryNetworkClient } from '../network/ReactQueryNetworkClient'
|
|
8
10
|
import { StreamedJson } from '../utils/StreamedJson'
|
|
9
|
-
import { baseDictionary } from '../error/dictionary/base'
|
|
10
11
|
import { formatJson } from '../utils/string'
|
|
11
|
-
import { ReactQueryNetworkClient } from '../network/ReactQueryNetworkClient'
|
|
12
|
-
import { aiClient } from './ai'
|
|
13
|
-
import { ChatAgentTool, ChatResponseWithSteps, FixedChatResponse, StepChatStep } from './types'
|
|
14
12
|
import { agentToolsClient } from './agent-tools'
|
|
13
|
+
import { aiClient } from './ai'
|
|
14
|
+
import { AgentInfo, ChatAgentTool, ChatResponse, ChatStep, StepChatStep } from './types'
|
|
15
|
+
|
|
16
|
+
export interface FixedDiscoveryChatResponse extends ChatResponse {
|
|
17
|
+
agent_info: AgentInfo[],
|
|
18
|
+
tools?: string[],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DiscoveryChatResponseWithSteps extends FixedDiscoveryChatResponse {
|
|
22
|
+
steps: ChatStep[],
|
|
23
|
+
}
|
|
15
24
|
|
|
16
25
|
export interface ChatConversionDetails extends ConversationResponse {
|
|
17
26
|
opportunityName?: string,
|
|
@@ -121,7 +130,8 @@ class DiscoverClient extends ReactQueryNetworkClient {
|
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
|
|
124
|
-
sendChatMessage(request: AiChatRequest & { agentId: string }, minChangeIntervalMS?: number)
|
|
133
|
+
sendChatMessage(request: AiChatRequest & { agentId: string }, minChangeIntervalMS?: number)
|
|
134
|
+
: StreamedJson<DiscoveryChatResponseWithSteps> {
|
|
125
135
|
const abortController = new AbortController()
|
|
126
136
|
const headers = {
|
|
127
137
|
'Content-Type': 'application/json',
|
|
@@ -136,172 +146,176 @@ class DiscoverClient extends ReactQueryNetworkClient {
|
|
|
136
146
|
* normal streamings of data, we need this separate function to deal with them. It transforms the internal data model of the
|
|
137
147
|
* StreamedJson object whenever an event is triggered.
|
|
138
148
|
*/
|
|
139
|
-
async function transform(event: Partial<
|
|
140
|
-
const
|
|
149
|
+
async function transform(event: Partial<FixedDiscoveryChatResponse>, data: Partial<DiscoveryChatResponseWithSteps>) {
|
|
150
|
+
const agentInfoList = event.agent_info
|
|
141
151
|
|
|
142
|
-
if (!
|
|
152
|
+
if (!agentInfoList?.length) return
|
|
143
153
|
|
|
144
154
|
const tools = await DiscoverClient.toolsOfAgent(request.agentId)
|
|
145
155
|
data.steps = data.steps ? [...data.steps] : []
|
|
156
|
+
|
|
157
|
+
for (const info of agentInfoList) {
|
|
158
|
+
if (!info) continue
|
|
146
159
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
info.data?.steps.forEach(s => data.steps?.push({
|
|
158
|
-
id: s.id,
|
|
159
|
-
type: 'step',
|
|
160
|
-
status: 'pending',
|
|
161
|
-
input: s.goal,
|
|
162
|
-
attempts: [{
|
|
163
|
-
tools: s.tools?.map(t => ({
|
|
164
|
-
...(tools.find(({ id }) => id === t.tool_id) ?? { id: t.tool_id, name: t.tool_id }),
|
|
165
|
-
executionId: t.tool_execution_id,
|
|
166
|
-
goal: t.goal,
|
|
167
|
-
})),
|
|
168
|
-
}],
|
|
169
|
-
}))
|
|
170
|
-
data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (info.type === 'planning' && info.action === 'awaiting_approval') {
|
|
174
|
-
data.steps.push({
|
|
175
|
-
id: 'planning',
|
|
176
|
-
type: 'planning',
|
|
177
|
-
status: 'awaiting_approval',
|
|
178
|
-
user_question: info.data?.user_question,
|
|
179
|
-
duration: info.duration || 0,
|
|
180
|
-
steps: info.data?.steps?.map(s => s.goal) ?? [],
|
|
181
|
-
goal: info.data?.plan_goal ?? '',
|
|
182
|
-
})
|
|
183
|
-
info.data?.steps.forEach(s => data.steps?.push({
|
|
184
|
-
id: s.id,
|
|
185
|
-
type: 'step',
|
|
186
|
-
status: 'pending',
|
|
187
|
-
input: s.goal,
|
|
188
|
-
attempts: [{
|
|
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
|
-
goal: t.goal,
|
|
193
|
-
})),
|
|
194
|
-
}],
|
|
195
|
-
}))
|
|
196
|
-
data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (info.type === 'step' && info.action === 'start') {
|
|
200
|
-
const step = data.steps.find(s => s.id === info.id)
|
|
201
|
-
if (step) step.status = 'running'
|
|
202
|
-
}
|
|
160
|
+
if (info.type === 'planning' && info.action === 'end') {
|
|
161
|
+
data.steps.push({
|
|
162
|
+
id: 'planning',
|
|
163
|
+
type: 'planning',
|
|
164
|
+
status: 'success',
|
|
165
|
+
duration: info.duration || 0,
|
|
166
|
+
steps: info.data?.steps?.map(s => s.goal) ?? [],
|
|
167
|
+
goal: info.data?.plan_goal ?? '',
|
|
168
|
+
})
|
|
203
169
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
170
|
+
info.data?.steps.forEach(s => data.steps?.push({
|
|
171
|
+
id: s.id,
|
|
172
|
+
type: 'step',
|
|
173
|
+
status: 'pending',
|
|
174
|
+
input: s.goal,
|
|
175
|
+
attempts: [{
|
|
176
|
+
tools: s.tools?.map(t => ({
|
|
177
|
+
...(tools.find(({ id }) => id === t.tool_id) ?? { id: t.tool_id, name: t.tool_id }),
|
|
178
|
+
executionId: t.tool_execution_id,
|
|
179
|
+
goal: t.goal,
|
|
180
|
+
})),
|
|
181
|
+
}],
|
|
182
|
+
}))
|
|
183
|
+
data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
|
|
212
184
|
}
|
|
213
|
-
}
|
|
214
185
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
186
|
+
if (info.type === 'planning' && info.action === 'awaiting_approval') {
|
|
187
|
+
data.steps.push({
|
|
188
|
+
id: 'planning',
|
|
189
|
+
type: 'planning',
|
|
190
|
+
status: 'awaiting_approval',
|
|
191
|
+
user_question: info.data?.user_question,
|
|
192
|
+
duration: info.duration || 0,
|
|
193
|
+
steps: info.data?.steps?.map(s => s.goal) ?? [],
|
|
194
|
+
goal: info.data?.plan_goal ?? '',
|
|
195
|
+
})
|
|
196
|
+
info.data?.steps.forEach(s => data.steps?.push({
|
|
197
|
+
id: s.id,
|
|
198
|
+
type: 'step',
|
|
199
|
+
status: 'pending',
|
|
200
|
+
input: s.goal,
|
|
201
|
+
attempts: [{
|
|
202
|
+
tools: s.tools?.map(t => ({
|
|
203
|
+
...(tools.find(({ id }) => id === t.tool_id) ?? { id: t.tool_id, name: t.tool_id }),
|
|
204
|
+
executionId: t.tool_execution_id,
|
|
205
|
+
goal: t.goal,
|
|
206
|
+
})),
|
|
231
207
|
}],
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
208
|
+
}))
|
|
209
|
+
data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (info.type === 'step' && info.action === 'start') {
|
|
213
|
+
const step = data.steps.find(s => s.id === info.id)
|
|
214
|
+
if (step) step.status = 'running'
|
|
215
|
+
}
|
|
236
216
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
217
|
+
if (info.type === 'step' && info.action === 'end') {
|
|
218
|
+
const step = data.steps.find(s => s.id === info.id) as StepChatStep
|
|
219
|
+
if (step) {
|
|
220
|
+
step.status = 'success'
|
|
221
|
+
step.duration = info.duration
|
|
222
|
+
const lastToolId = last(step.attempts[0].tools)?.id
|
|
223
|
+
const lastAttemptOfLastTool = findLast(step.attempts.map(a => a.tools).flat(), t => t?.id === lastToolId)
|
|
224
|
+
step.output = lastAttemptOfLastTool?.output
|
|
225
|
+
}
|
|
226
|
+
}
|
|
240
227
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
const input = formatJson(info.data.input)
|
|
244
|
-
const tool = tools.find(({ id }) => id === info.data?.tool_id) ?? { id: info.data?.tool_id, name: info.data?.tool_id }
|
|
228
|
+
if (info.type === 'tool' && info.action === 'awaiting_approval') {
|
|
229
|
+
const tool = tools.find(({ id }) => id === info.data?.tool_id)
|
|
245
230
|
data.steps.push({
|
|
246
231
|
id: info.id,
|
|
247
232
|
type: 'tool',
|
|
248
|
-
status: '
|
|
233
|
+
status: 'awaiting_approval',
|
|
249
234
|
duration: info.duration || 0,
|
|
250
235
|
input: info.data?.input,
|
|
251
236
|
user_question: info.data?.user_question,
|
|
252
237
|
attempts: [{
|
|
253
|
-
tools: [{
|
|
238
|
+
tools: [{
|
|
239
|
+
executionId: info.id,
|
|
240
|
+
id: info.data?.tool_id ?? '',
|
|
241
|
+
name: tool?.name ?? '',
|
|
242
|
+
goal: tool?.goal,
|
|
243
|
+
...tool,
|
|
244
|
+
}],
|
|
254
245
|
}],
|
|
255
246
|
})
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
247
|
+
data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (info.type === 'tool' && info.action === 'start') {
|
|
251
|
+
const currentStep = data.steps.find(s => s.status === 'running') as StepChatStep
|
|
252
|
+
if (!info.data) return
|
|
253
|
+
|
|
254
|
+
//There might be a tool with status awaiting_approval, so we want to inform tool has already started
|
|
255
|
+
if (!currentStep || !currentStep.attempts[0].tools) {
|
|
261
256
|
const input = formatJson(info.data.input)
|
|
262
|
-
const tool = tools
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
257
|
+
const tool = tools.find(({ id }) => id === info.data?.tool_id) ?? { id: info.data?.tool_id, name: info.data?.tool_id }
|
|
258
|
+
data.steps.push({
|
|
259
|
+
id: info.id,
|
|
260
|
+
type: 'tool',
|
|
261
|
+
status: 'running',
|
|
262
|
+
duration: info.duration || 0,
|
|
263
|
+
input: info.data?.input,
|
|
264
|
+
user_question: info.data?.user_question,
|
|
265
|
+
attempts: [{
|
|
266
|
+
tools: [{ ...tool, executionId: info.id, input }],
|
|
267
|
+
}],
|
|
267
268
|
})
|
|
268
269
|
} else {
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
270
|
+
const toolInFirstAttempt = currentStep.attempts[0].tools?.find(t => t.executionId === info.id)
|
|
271
|
+
//One step might have multiple tools. When in an approval mode, we might not have all the tools in the array yet.
|
|
272
|
+
//So we make sure to add any tools that are not in there.
|
|
273
|
+
if (!toolInFirstAttempt) {
|
|
274
|
+
const input = formatJson(info.data.input)
|
|
275
|
+
const tool = tools?.find(({ id }) => id === info.data?.tool_id) ?? { id: info.data?.tool_id, name: info.data?.tool_id }
|
|
275
276
|
currentStep.attempts[info.data.attempt - 1].tools?.push({
|
|
276
277
|
...tool,
|
|
277
278
|
executionId: info.id,
|
|
278
279
|
input,
|
|
279
280
|
})
|
|
281
|
+
} else {
|
|
282
|
+
const input = formatJson(info.data.input)
|
|
283
|
+
if (info.data.attempt === 1) {
|
|
284
|
+
toolInFirstAttempt.input = input
|
|
285
|
+
} else {
|
|
286
|
+
const tool = tools.find(({ id }) => id === info.data?.tool_id) ?? { id: info.data?.tool_id, name: info.data?.tool_id }
|
|
287
|
+
currentStep.attempts[info.data.attempt - 1] ??= { tools: [] }
|
|
288
|
+
currentStep.attempts[info.data.attempt - 1].tools?.push({
|
|
289
|
+
...tool,
|
|
290
|
+
executionId: info.id,
|
|
291
|
+
input,
|
|
292
|
+
})
|
|
293
|
+
}
|
|
280
294
|
}
|
|
281
295
|
}
|
|
282
296
|
}
|
|
283
|
-
}
|
|
284
297
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
298
|
+
if (info.type === 'tool' && info.action === 'end') {
|
|
299
|
+
const currentStep = data.steps.find(s => s.status === 'running') as StepChatStep
|
|
300
|
+
if (!currentStep || !info.data) return
|
|
301
|
+
const tool = currentStep.attempts[info.data.attempt - 1]?.tools?.find(t => t.executionId === info.id)
|
|
302
|
+
if (tool) {
|
|
303
|
+
tool.output = formatJson(info.data.output)
|
|
304
|
+
tool.duration = info.duration
|
|
305
|
+
}
|
|
292
306
|
}
|
|
293
|
-
}
|
|
294
307
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
308
|
+
if (info.type === 'final_answer' && info.action === 'start') {
|
|
309
|
+
const answerStep = last(data.steps)
|
|
310
|
+
if (answerStep) answerStep.status = 'running'
|
|
311
|
+
}
|
|
299
312
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
313
|
+
if (info.type === 'chat' && info.action === 'end') {
|
|
314
|
+
const answerStep = last(data.steps)
|
|
315
|
+
if (answerStep) {
|
|
316
|
+
answerStep.status = 'success'
|
|
317
|
+
answerStep.duration = info.duration
|
|
318
|
+
}
|
|
305
319
|
}
|
|
306
320
|
}
|
|
307
321
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { HttpError } from '@oazapfts/runtime'
|
|
3
|
+
import { findLast, last } from 'lodash'
|
|
3
4
|
import { getApiAddresses } from '../api-addresses'
|
|
4
5
|
import { addSelfHostedModelV1LlmModelsPost, agentChatV1AgentAgentIdChatPost, defaults, deleteModelResourcesV1LlmResourcesResourceIdDelete, deleteV1LlmModelsModelIdDelete, getModelV1LlmModelsModelIdGet, listLlmProvidersV1LlmProvidersGet, listModelsV1LlmModelsGet, saveOrUpdateModelResourcesV1LlmModelsModelIdResourcesPut, toggleModelStatusV1LlmModelsModelIdPatch, updateV1LlmModelsModelIdPut } from '../api/genAiInference'
|
|
5
6
|
import { DefaultAPIError } from '../error/DefaultAPIError'
|
|
@@ -7,6 +8,10 @@ import { inferenceDictionary } from '../error/dictionary/ai-inference'
|
|
|
7
8
|
import { StackspotAPIError } from '../error/StackspotAPIError'
|
|
8
9
|
import { ReactQueryNetworkClient } from '../network/ReactQueryNetworkClient'
|
|
9
10
|
import { removeAuthorizationParam } from '../utils/remove-authorization-param'
|
|
11
|
+
import { StreamedJson } from '../utils/StreamedJson'
|
|
12
|
+
import { formatJson } from '../utils/string'
|
|
13
|
+
import { agentToolsClient } from './agent-tools'
|
|
14
|
+
import { AgentInfo, ChatAgentTool, ChatResponseWithSteps, FixedChatRequest, FixedChatResponse, StepChatStep } from './types'
|
|
10
15
|
|
|
11
16
|
class GenAiInference extends ReactQueryNetworkClient {
|
|
12
17
|
constructor() {
|
|
@@ -60,6 +65,282 @@ class GenAiInference extends ReactQueryNetworkClient {
|
|
|
60
65
|
* Interaction with a specific AI agent
|
|
61
66
|
*/
|
|
62
67
|
sendAgentMessage = this.mutation(removeAuthorizationParam(agentChatV1AgentAgentIdChatPost))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
private static async toolsOfAgent(agentId?: string) {
|
|
71
|
+
try {
|
|
72
|
+
const agent = agentId ? await agentToolsClient.agent.query({ agentId }) : undefined
|
|
73
|
+
if (!agent) return []
|
|
74
|
+
const tools: (Omit<ChatAgentTool, 'duration' | 'prompt' | 'output'>)[] = []
|
|
75
|
+
agent.toolkits?.builtin_toolkits?.forEach(kit => kit.tools?.forEach(({ id, name, description }) => {
|
|
76
|
+
if (id) tools.push({ image: kit.image_url, id, name: name || id, description })
|
|
77
|
+
}))
|
|
78
|
+
agent.toolkits?.custom_toolkits?.forEach(kit => kit.tools?.forEach(({ id, name, description }) => {
|
|
79
|
+
if (id) tools.push({ image: kit.avatar ?? undefined, id, name: name || id, description })
|
|
80
|
+
}))
|
|
81
|
+
return tools
|
|
82
|
+
} catch {
|
|
83
|
+
return []
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
sendChatMessage(request: FixedChatRequest, minChangeIntervalMS?: number): StreamedJson<ChatResponseWithSteps> {
|
|
88
|
+
const abortController = new AbortController()
|
|
89
|
+
const { context, ...body } = request ?? {}
|
|
90
|
+
const headers = {
|
|
91
|
+
'Content-Type': 'application/json',
|
|
92
|
+
'Accept': 'text/event-stream',
|
|
93
|
+
'x-platform': context?.platform ?? '',
|
|
94
|
+
'x-os': context?.os ?? '',
|
|
95
|
+
'x-platform-version': context?.platform_version ?? '',
|
|
96
|
+
'x-stackspot-ai-version': context?.stackspot_ai_version ?? '',
|
|
97
|
+
'x-request-origin': 'chat',
|
|
98
|
+
}
|
|
99
|
+
const events = this.stream(
|
|
100
|
+
this.resolveURL(`/v1/agent/${context?.agent_id}/chat`),
|
|
101
|
+
{ method: 'post', body: JSON.stringify(body), headers, signal: abortController.signal },
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const DYNAMIC_TOOL_ID = 'dynamic'
|
|
105
|
+
function isDynamicTool(info: AgentInfo) {
|
|
106
|
+
return info.type === 'tool' && info.id === DYNAMIC_TOOL_ID
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* This function treats events in the streaming that deals with the execution of tools. Since these events are not concatenated like
|
|
110
|
+
* normal streamings of data, we need this separate function to deal with them. It transforms the internal data model of the
|
|
111
|
+
* StreamedJson object whenever an event is triggered.
|
|
112
|
+
*/
|
|
113
|
+
async function transform(event: Partial<FixedChatResponse>, data: Partial<ChatResponseWithSteps>) {
|
|
114
|
+
// The API send a final snapshot containing the full `agent_info`.
|
|
115
|
+
// When `stop_reason === 'stop'` we can ignore it to avoid duplicating steps.
|
|
116
|
+
if (event?.stop_reason === 'stop') return
|
|
117
|
+
|
|
118
|
+
const infoList = event.agent_info ?? []
|
|
119
|
+
if (infoList.length === 0) return
|
|
120
|
+
|
|
121
|
+
const tools = await GenAiInference.toolsOfAgent(request.context?.agent_id)
|
|
122
|
+
data.steps = data.steps ? [...data.steps] : []
|
|
123
|
+
|
|
124
|
+
for (const info of infoList) {
|
|
125
|
+
|
|
126
|
+
if (info.type === 'planning' && info.action === 'end') {
|
|
127
|
+
data.steps.push({
|
|
128
|
+
id: 'planning',
|
|
129
|
+
type: 'planning',
|
|
130
|
+
status: 'success',
|
|
131
|
+
duration: info.duration || 0,
|
|
132
|
+
steps: info.data?.steps?.map(s => s.goal) ?? [],
|
|
133
|
+
goal: info.data?.plan_goal ?? '',
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
info.data?.steps.forEach(s => data.steps?.push({
|
|
137
|
+
id: s.id,
|
|
138
|
+
type: 'step',
|
|
139
|
+
status: 'pending',
|
|
140
|
+
input: s.goal,
|
|
141
|
+
attempts: [{
|
|
142
|
+
tools: s.tools?.map(t => ({
|
|
143
|
+
...(tools.find(({ id }) => id === t.tool_id) ?? { id: t.tool_id, name: t.tool_id }),
|
|
144
|
+
executionId: t.tool_execution_id,
|
|
145
|
+
goal: t.goal,
|
|
146
|
+
})),
|
|
147
|
+
}],
|
|
148
|
+
}))
|
|
149
|
+
data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (info.type === 'planning' && info.action === 'awaiting_approval') {
|
|
153
|
+
data.steps.push({
|
|
154
|
+
id: 'planning',
|
|
155
|
+
type: 'planning',
|
|
156
|
+
status: 'awaiting_approval',
|
|
157
|
+
user_question: info.data?.user_question,
|
|
158
|
+
duration: info.duration || 0,
|
|
159
|
+
steps: info.data?.steps?.map(s => s.goal) ?? [],
|
|
160
|
+
goal: info.data?.plan_goal ?? '',
|
|
161
|
+
})
|
|
162
|
+
info.data?.steps.forEach(s => data.steps?.push({
|
|
163
|
+
id: s.id,
|
|
164
|
+
type: 'step',
|
|
165
|
+
status: 'pending',
|
|
166
|
+
input: s.goal,
|
|
167
|
+
attempts: [{
|
|
168
|
+
tools: s.tools?.map(t => ({
|
|
169
|
+
...(tools.find(({ id }) => id === t.tool_id) ?? { id: t.tool_id, name: t.tool_id }),
|
|
170
|
+
executionId: t.tool_execution_id,
|
|
171
|
+
goal: t.goal,
|
|
172
|
+
})),
|
|
173
|
+
}],
|
|
174
|
+
}))
|
|
175
|
+
data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (info.type === 'step' && info.action === 'start') {
|
|
179
|
+
const step = data.steps.find(s => s.id === info.id)
|
|
180
|
+
if (step) step.status = 'running'
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (info.type === 'step' && info.action === 'end') {
|
|
184
|
+
const step = data.steps.find(s => s.id === info.id) as StepChatStep
|
|
185
|
+
if (step) {
|
|
186
|
+
step.status = 'success'
|
|
187
|
+
step.duration = info.duration
|
|
188
|
+
const lastToolId = last(step.attempts[0].tools)?.id
|
|
189
|
+
const lastAttemptOfLastTool = findLast(step.attempts.map(a => a.tools).flat(), t => t?.id === lastToolId)
|
|
190
|
+
step.output = lastAttemptOfLastTool?.output
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (info.type === 'tool_calls' && info.action === 'start') {
|
|
195
|
+
const hasPlanning = data.steps.find(s => s.type === 'planning')
|
|
196
|
+
// On the first tool_calls:start, create the synthetic planning ("dynamic") step.
|
|
197
|
+
if (!hasPlanning) {
|
|
198
|
+
const userPrompt = request.user_prompt === 'string' ? request.user_prompt : JSON.stringify(request.user_prompt)
|
|
199
|
+
data.steps.push({
|
|
200
|
+
id: 'dynamic',
|
|
201
|
+
type: 'planning',
|
|
202
|
+
status: 'success',
|
|
203
|
+
steps: [],
|
|
204
|
+
goal: userPrompt,
|
|
205
|
+
user_question: userPrompt,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
const toolsStepId = data.steps.filter(s => s.id === 'tools' || s.id.startsWith('tools-')).length + 1
|
|
209
|
+
data.steps.push({
|
|
210
|
+
id: `tools-${toolsStepId.toString()}`,
|
|
211
|
+
type: 'step',
|
|
212
|
+
status: 'running',
|
|
213
|
+
attempts: [{ tools: [] }],
|
|
214
|
+
} as StepChatStep)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (info.type === 'tool_calls' && info.action === 'end') {
|
|
218
|
+
const lastStep = findLast(data.steps, s => s.id === 'tools' || s.id.startsWith('tools-')) as StepChatStep
|
|
219
|
+
if (lastStep) {
|
|
220
|
+
lastStep.status = 'success'
|
|
221
|
+
lastStep.duration = info.duration
|
|
222
|
+
const lastAttemptOfLastTool = last(lastStep.attempts.map(a => a.tools).flat())
|
|
223
|
+
lastStep.output = lastAttemptOfLastTool?.output
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (info.type === 'tool' && info.action === 'awaiting_approval') {
|
|
228
|
+
const tool = tools.find(({ id }) => id === info.data?.tool_id)
|
|
229
|
+
data.steps.push({
|
|
230
|
+
id: info.id,
|
|
231
|
+
type: 'tool',
|
|
232
|
+
status: 'awaiting_approval',
|
|
233
|
+
duration: info.duration || 0,
|
|
234
|
+
input: info.data?.input,
|
|
235
|
+
user_question: info.data?.user_question,
|
|
236
|
+
attempts: [{
|
|
237
|
+
tools: [{
|
|
238
|
+
executionId: info.id,
|
|
239
|
+
id: info.data?.tool_id ?? '',
|
|
240
|
+
name: tool?.name ?? '',
|
|
241
|
+
goal: tool?.goal,
|
|
242
|
+
...tool,
|
|
243
|
+
}],
|
|
244
|
+
}],
|
|
245
|
+
})
|
|
246
|
+
data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (info.type === 'tool' && info.action === 'start') {
|
|
250
|
+
if (!info.data) return
|
|
251
|
+
const input = formatJson(info.data.input)
|
|
252
|
+
const tool = findLast(tools, ({ id }) => id === info.data?.tool_id) ?? { id: info.data?.tool_id, name: info.data?.tool_id }
|
|
253
|
+
|
|
254
|
+
const currentStep = findLast(data.steps, s => s.status === 'running') as StepChatStep
|
|
255
|
+
|
|
256
|
+
//There might be a tool with status awaiting_approval, so we want to inform tool has already started
|
|
257
|
+
if (!currentStep || !currentStep?.attempts?.[0]?.tools) {
|
|
258
|
+
data.steps.push({
|
|
259
|
+
id: info.id,
|
|
260
|
+
type: 'tool',
|
|
261
|
+
status: 'running',
|
|
262
|
+
duration: info.duration || 0,
|
|
263
|
+
input: info.data?.input,
|
|
264
|
+
user_question: info.data?.user_question,
|
|
265
|
+
attempts: [{
|
|
266
|
+
tools: [{ ...tool, executionId: info.id, input }],
|
|
267
|
+
}],
|
|
268
|
+
})
|
|
269
|
+
} else {
|
|
270
|
+
const toolInFirstAttempt = findLast(currentStep?.attempts?.[0]?.tools, t => t.executionId === info.id)
|
|
271
|
+
//One step might have multiple tools. When in an approval mode, we might not have all the tools in the array yet.
|
|
272
|
+
//For dynamic tools (id === 'dynamic'), we always push a new tool, since dynamic executions can trigger
|
|
273
|
+
//multiple tool runs in the same step and do not follow the planned tool structure.
|
|
274
|
+
//So we make sure to add any tools that are not in there, or always add for dynamic tools.
|
|
275
|
+
if (!toolInFirstAttempt || isDynamicTool(info)) {
|
|
276
|
+
currentStep.attempts?.[0].tools?.push({
|
|
277
|
+
...tool,
|
|
278
|
+
executionId: info.id,
|
|
279
|
+
input,
|
|
280
|
+
status: 'running',
|
|
281
|
+
})
|
|
282
|
+
} else {
|
|
283
|
+
const input = formatJson(info.data.input)
|
|
284
|
+
if (info.data.attempt === 1) {
|
|
285
|
+
toolInFirstAttempt.input = input
|
|
286
|
+
} else {
|
|
287
|
+
currentStep.attempts[info.data.attempt - 1] ??= { tools: [] }
|
|
288
|
+
currentStep.attempts[info.data.attempt - 1].tools?.push({
|
|
289
|
+
...tool,
|
|
290
|
+
executionId: info.id,
|
|
291
|
+
input,
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (info.type === 'tool' && info.action === 'end') {
|
|
299
|
+
const currentStep = data.steps.find(s => s.status === 'running') as StepChatStep
|
|
300
|
+
if (!currentStep || !info.data) return
|
|
301
|
+
|
|
302
|
+
// attempt index for tool execution starts at 0 for dynamically executed tools,while for planned tools it starts at 1
|
|
303
|
+
const attempt = isDynamicTool(info) ? info.data.attempt : info.data.attempt - 1
|
|
304
|
+
const tool = last(currentStep?.attempts?.[attempt]?.tools)
|
|
305
|
+
if (tool) {
|
|
306
|
+
tool.output = formatJson(info.data.output)
|
|
307
|
+
tool.duration = info.duration
|
|
308
|
+
tool.status = 'success'
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (info.type === 'final_answer' && info.action === 'start') {
|
|
313
|
+
const answerStep = last(data.steps)
|
|
314
|
+
if (answerStep) answerStep.status = 'running'
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
if (info.type === 'chat' && info.action === 'end') {
|
|
319
|
+
const lastStep = last(data.steps)
|
|
320
|
+
if (lastStep?.type === 'answer') {
|
|
321
|
+
lastStep.status = 'success'
|
|
322
|
+
lastStep.duration = info.duration
|
|
323
|
+
} else {
|
|
324
|
+
data.steps.push({ id: 'answer', type: 'answer', status: 'success' })
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return new StreamedJson({
|
|
331
|
+
eventsPromise: events,
|
|
332
|
+
abortController,
|
|
333
|
+
ignoreKeys: ['agent_info'],
|
|
334
|
+
transform,
|
|
335
|
+
textFromErrorEvent: data => data.message ?? 'Unknown error',
|
|
336
|
+
minChangeIntervalMS,
|
|
337
|
+
minChangeIntervalMSFromEvent: (event) => {
|
|
338
|
+
if (event?.agent_info?.length) return 0
|
|
339
|
+
return minChangeIntervalMS
|
|
340
|
+
},
|
|
341
|
+
})
|
|
342
|
+
}
|
|
343
|
+
|
|
63
344
|
}
|
|
64
345
|
|
|
65
346
|
export const genAiInferenceClient = new GenAiInference()
|