@stack-spot/portal-network 0.203.1-beta.1 → 0.204.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/api/codeShift.d.ts +13 -1
  3. package/dist/api/codeShift.d.ts.map +1 -1
  4. package/dist/api/codeShift.js.map +1 -1
  5. package/dist/api/genAiInference.d.ts +49 -2
  6. package/dist/api/genAiInference.d.ts.map +1 -1
  7. package/dist/api/genAiInference.js +55 -2
  8. package/dist/api/genAiInference.js.map +1 -1
  9. package/dist/client/ai.d.ts +1 -3
  10. package/dist/client/ai.d.ts.map +1 -1
  11. package/dist/client/ai.js +2 -249
  12. package/dist/client/ai.js.map +1 -1
  13. package/dist/client/discover.d.ts +2 -2
  14. package/dist/client/discover.d.ts.map +1 -1
  15. package/dist/client/discover.js +4 -3
  16. package/dist/client/discover.js.map +1 -1
  17. package/dist/client/gen-ai-inference.d.ts +4 -0
  18. package/dist/client/gen-ai-inference.d.ts.map +1 -1
  19. package/dist/client/gen-ai-inference.js +267 -0
  20. package/dist/client/gen-ai-inference.js.map +1 -1
  21. package/dist/client/types.d.ts +13 -14
  22. package/dist/client/types.d.ts.map +1 -1
  23. package/dist/client/workspace-manager.d.ts +9 -0
  24. package/dist/client/workspace-manager.d.ts.map +1 -1
  25. package/dist/client/workspace-manager.js +10 -1
  26. package/dist/client/workspace-manager.js.map +1 -1
  27. package/dist/utils/StreamedJson.d.ts +9 -1
  28. package/dist/utils/StreamedJson.d.ts.map +1 -1
  29. package/dist/utils/StreamedJson.js +22 -2
  30. package/dist/utils/StreamedJson.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/api/codeShift.ts +13 -1
  33. package/src/api/genAiInference.ts +119 -3
  34. package/src/client/ai.ts +2 -265
  35. package/src/client/discover.ts +7 -6
  36. package/src/client/gen-ai-inference.ts +281 -0
  37. package/src/client/types.ts +14 -14
  38. package/src/client/workspace-manager.ts +5 -0
  39. package/src/utils/StreamedJson.tsx +19 -2
package/src/client/ai.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { HttpError } from '@oazapfts/runtime'
2
- import { findLast, isArray, last } from 'lodash'
2
+ import { isArray } from 'lodash'
3
3
  import { getApiAddresses } from '../api-addresses'
4
4
  import {
5
5
  addFavoriteV1AiStacksStackIdFavoritePost,
@@ -45,9 +45,9 @@ import {
45
45
  resetKnowledgeObjectsV1KnowledgeSourcesSlugObjectsDelete,
46
46
  runFetchStepV1QuickCommandsSlugStepsStepSlugFetchRunPost,
47
47
  searchKnowledgeSourcesV1KnowledgeSourcesSearchPost,
48
+ startScriptStepV1QuickCommandsSlugStepsStepSlugStartScriptPost,
48
49
  tokensByUserV1AnalyticsTokensByUserGet,
49
50
  tokensDailyUsageV1AnalyticsTokensDailyUsageGet,
50
- startScriptStepV1QuickCommandsSlugStepsStepSlugStartScriptPost,
51
51
  totalV1TokensUsageTotalGet,
52
52
  updateQuickCommandV1QuickCommandsSlugPatch,
53
53
  updateResourceReviewV1ResourcesResourceTypeSlugResourceSlugReviewsReviewIdPatch,
@@ -61,18 +61,10 @@ import { StackspotAPIError } from '../error/StackspotAPIError'
61
61
  import { ReactQueryNetworkClient } from '../network/ReactQueryNetworkClient'
62
62
  import { removeAuthorizationParam } from '../utils/remove-authorization-param'
63
63
  import { StreamedJson } from '../utils/StreamedJson'
64
- import { formatJson } from '../utils/string'
65
- import { agentToolsClient } from './agent-tools'
66
64
  import {
67
- AgentInfo,
68
- ChatAgentTool,
69
- ChatResponseWithSteps,
70
- FixedChatRequest,
71
- FixedChatResponse,
72
65
  FixedConversationResponse,
73
66
  FixedDependencyResponse,
74
67
  ReplaceResult,
75
- StepChatStep,
76
68
  } from './types'
77
69
 
78
70
  const listQAV3WithoutAuthorization = removeAuthorizationParam(listAllV3QuickCommandsGet)
@@ -340,261 +332,6 @@ class AIClient extends ReactQueryNetworkClient {
340
332
  deleteReviewComment = this.mutation(
341
333
  removeAuthorizationParam(deleteReviewCommentV1ResourcesResourceTypeSlugResourceSlugReviewsReviewIdAnswersAnswerIdDelete))
342
334
 
343
- private static async toolsOfAgent(agentId?: string) {
344
- try {
345
- const agent = agentId ? await agentToolsClient.agent.query({ agentId }) : undefined
346
- if (!agent) return []
347
- const tools: (Omit<ChatAgentTool, 'duration' | 'prompt' | 'output'>)[] = []
348
- agent.toolkits?.builtin_toolkits?.forEach(kit => kit.tools?.forEach(({ id, name, description }) => {
349
- if (id) tools.push({ image: kit.image_url, id, name: name || id, description })
350
- }))
351
- agent.toolkits?.custom_toolkits?.forEach(kit => kit.tools?.forEach(({ id, name, description }) => {
352
- if (id) tools.push({ image: kit.avatar ?? undefined, id, name: name || id, description })
353
- }))
354
- return tools
355
- } catch {
356
- return []
357
- }
358
- }
359
-
360
- sendChatMessage(request: FixedChatRequest, minChangeIntervalMS?: number): StreamedJson<ChatResponseWithSteps> {
361
- const abortController = new AbortController()
362
- const headers = {
363
- 'Content-Type': 'application/json',
364
- 'Accept': 'text/event-stream',
365
- }
366
- const events = this.stream(
367
- this.resolveURL('v3/chat'),
368
- { method: 'post', body: JSON.stringify(request), headers, signal: abortController.signal },
369
- )
370
-
371
- const DYNAMIC_TOOL_ID = 'dynamic'
372
- function isDynamicTool(info: AgentInfo) {
373
- return info.type === 'tool' && info.id === DYNAMIC_TOOL_ID
374
- }
375
- /**
376
- * This function treats events in the streaming that deals with the execution of tools. Since these events are not concatenated like
377
- * normal streamings of data, we need this separate function to deal with them. It transforms the internal data model of the
378
- * StreamedJson object whenever an event is triggered.
379
- */
380
- async function transform(event: Partial<FixedChatResponse>, data: Partial<ChatResponseWithSteps>) {
381
- const info = event.agent_info
382
- if (!info) return
383
- const tools = await AIClient.toolsOfAgent(request.context?.agent_id)
384
- data.steps = data.steps ? [...data.steps] : []
385
-
386
- if (info.type === 'planning' && info.action === 'end') {
387
- data.steps.push({
388
- id: 'planning',
389
- type: 'planning',
390
- status: 'success',
391
- duration: info.duration || 0,
392
- steps: info.data?.steps?.map(s => s.goal) ?? [],
393
- goal: info.data?.plan_goal ?? '',
394
- })
395
-
396
- info.data?.steps.forEach(s => data.steps?.push({
397
- id: s.id,
398
- type: 'step',
399
- status: 'pending',
400
- input: s.goal,
401
- attempts: [{
402
- tools: s.tools?.map(t => ({
403
- ...(tools.find(({ id }) => id === t.tool_id) ?? { id: t.tool_id, name: t.tool_id }),
404
- executionId: t.tool_execution_id,
405
- goal: t.goal,
406
- })),
407
- }],
408
- }))
409
- data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
410
- }
411
-
412
- if (info.type === 'planning' && info.action === 'awaiting_approval') {
413
- data.steps.push({
414
- id: 'planning',
415
- type: 'planning',
416
- status: 'awaiting_approval',
417
- user_question: info.data?.user_question,
418
- duration: info.duration || 0,
419
- steps: info.data?.steps?.map(s => s.goal) ?? [],
420
- goal: info.data?.plan_goal ?? '',
421
- })
422
- info.data?.steps.forEach(s => data.steps?.push({
423
- id: s.id,
424
- type: 'step',
425
- status: 'pending',
426
- input: s.goal,
427
- attempts: [{
428
- tools: s.tools?.map(t => ({
429
- ...(tools.find(({ id }) => id === t.tool_id) ?? { id: t.tool_id, name: t.tool_id }),
430
- executionId: t.tool_execution_id,
431
- goal: t.goal,
432
- })),
433
- }],
434
- }))
435
- data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
436
- }
437
-
438
- if (info.type === 'step' && info.action === 'start') {
439
- const step = data.steps.find(s => s.id === info.id)
440
- if (step) step.status = 'running'
441
- }
442
-
443
- if (info.type === 'step' && info.action === 'end') {
444
- const step = data.steps.find(s => s.id === info.id) as StepChatStep
445
- if (step) {
446
- step.status = 'success'
447
- step.duration = info.duration
448
- const lastToolId = last(step.attempts[0].tools)?.id
449
- const lastAttemptOfLastTool = findLast(step.attempts.map(a => a.tools).flat(), t => t?.id === lastToolId)
450
- step.output = lastAttemptOfLastTool?.output
451
- }
452
- }
453
-
454
- if (info.type === 'tool_calls' && info.action === 'start') {
455
- const hasPlanning = data.steps.find(s => s.type === 'planning')
456
- // On the first tool_calls:start, create the synthetic planning ("dynamic") step.
457
- if (!hasPlanning) {
458
- const userPrompt = request.user_prompt === 'string' ? request.user_prompt : JSON.stringify(request.user_prompt)
459
- data.steps.push({
460
- id: 'dynamic',
461
- type: 'planning',
462
- status: 'success',
463
- steps: [],
464
- goal: userPrompt,
465
- user_question: userPrompt,
466
- })
467
- }
468
- const toolsStepId = data.steps.filter(s => s.id === 'tools' || s.id.startsWith('tools-')).length + 1
469
- data.steps.push({
470
- id: `tools-${toolsStepId.toString()}`,
471
- type: 'step',
472
- status: 'running',
473
- attempts: [{ tools: [] }],
474
- } as StepChatStep)
475
- }
476
-
477
- if (info.type === 'tool_calls' && info.action === 'end') {
478
- const lastStep = findLast(data.steps, s => s.id === 'tools' || s.id.startsWith('tools-')) as StepChatStep
479
- if (lastStep) {
480
- lastStep.status = 'success'
481
- lastStep.duration = info.duration
482
- const lastAttemptOfLastTool = last(lastStep.attempts.map(a => a.tools).flat())
483
- lastStep.output = lastAttemptOfLastTool?.output
484
- }
485
- }
486
-
487
- if (info.type === 'tool' && info.action === 'awaiting_approval') {
488
- const tool = tools.find(({ id }) => id === info.data?.tool_id)
489
- data.steps.push({
490
- id: info.id,
491
- type: 'tool',
492
- status: 'awaiting_approval',
493
- duration: info.duration || 0,
494
- input: info.data?.input,
495
- user_question: info.data?.user_question,
496
- attempts: [{
497
- tools: [{
498
- executionId: info.id,
499
- id: info.data?.tool_id ?? '',
500
- name: tool?.name ?? '',
501
- goal: tool?.goal,
502
- ...tool,
503
- }],
504
- }],
505
- })
506
- data.steps.push({ id: 'answer', type: 'answer', status: 'pending' })
507
- }
508
-
509
- if (info.type === 'tool' && info.action === 'start') {
510
- if (!info.data) return
511
- const input = formatJson(info.data.input)
512
- const tool = findLast(tools, ({ id }) => id === info.data?.tool_id) ?? { id: info.data?.tool_id, name: info.data?.tool_id }
513
-
514
- const currentStep = findLast(data.steps, s => s.status === 'running') as StepChatStep
515
-
516
- //There might be a tool with status awaiting_approval, so we want to inform tool has already started
517
- if (!currentStep || !currentStep?.attempts?.[0]?.tools) {
518
- data.steps.push({
519
- id: info.id,
520
- type: 'tool',
521
- status: 'running',
522
- duration: info.duration || 0,
523
- input: info.data?.input,
524
- user_question: info.data?.user_question,
525
- attempts: [{
526
- tools: [{ ...tool, executionId: info.id, input }],
527
- }],
528
- })
529
- } else {
530
- const toolInFirstAttempt = findLast(currentStep?.attempts?.[0]?.tools, t => t.executionId === info.id)
531
- //One step might have multiple tools. When in an approval mode, we might not have all the tools in the array yet.
532
- //For dynamic tools (id === 'dynamic'), we always push a new tool, since dynamic executions can trigger
533
- //multiple tool runs in the same step and do not follow the planned tool structure.
534
- //So we make sure to add any tools that are not in there, or always add for dynamic tools.
535
- if (!toolInFirstAttempt || isDynamicTool(info)) {
536
- currentStep.attempts?.[0].tools?.push({
537
- ...tool,
538
- executionId: info.id,
539
- input,
540
- status: 'running',
541
- })
542
- } else {
543
- const input = formatJson(info.data.input)
544
- if (info.data.attempt === 1) {
545
- toolInFirstAttempt.input = input
546
- } else {
547
- currentStep.attempts[info.data.attempt - 1] ??= { tools: [] }
548
- currentStep.attempts[info.data.attempt - 1].tools?.push({
549
- ...tool,
550
- executionId: info.id,
551
- input,
552
- })
553
- }
554
- }
555
- }
556
- }
557
-
558
- if (info.type === 'tool' && info.action === 'end') {
559
- const currentStep = data.steps.find(s => s.status === 'running') as StepChatStep
560
- if (!currentStep || !info.data) return
561
-
562
- // attempt index for tool execution starts at 0 for dynamically executed tools,while for planned tools it starts at 1
563
- const attempt = isDynamicTool(info) ? info.data.attempt : info.data.attempt - 1
564
- const tool = last(currentStep?.attempts?.[attempt]?.tools)
565
- if (tool) {
566
- tool.output = formatJson(info.data.output)
567
- tool.duration = info.duration
568
- tool.status = 'success'
569
- }
570
- }
571
-
572
- if (info.type === 'final_answer' && info.action === 'start') {
573
- const answerStep = last(data.steps)
574
- if (answerStep) answerStep.status = 'running'
575
- }
576
-
577
-
578
- if (info.type === 'chat' && info.action === 'end') {
579
- const lastStep = last(data.steps)
580
- if (lastStep?.type === 'answer') {
581
- lastStep.status = 'success'
582
- lastStep.duration = info.duration
583
- } else {
584
- data.steps.push({ id: 'answer', type: 'answer', status: 'success' })
585
- }
586
- }
587
- }
588
-
589
- return new StreamedJson({
590
- eventsPromise: events,
591
- abortController,
592
- minChangeIntervalMS,
593
- ignoreKeys: ['agent_info'],
594
- transform,
595
- })
596
- }
597
-
598
335
  contentDependencies = this.query(removeAuthorizationParam(
599
336
  getContentDependenciesV1ContentContentTypeContentIdDependenciesGet as ReplaceResult<
600
337
  typeof getContentDependenciesV1ContentContentTypeContentIdDependenciesGet,
@@ -2,16 +2,16 @@ 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, AiChatRequest, refreshInsights } from '../api/discover'
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, ChatResponseWithSteps, FixedChatResponse, StepChatStep } from './types'
15
15
 
16
16
  export interface ChatConversionDetails extends ConversationResponse {
17
17
  opportunityName?: string,
@@ -137,7 +137,8 @@ class DiscoverClient extends ReactQueryNetworkClient {
137
137
  * StreamedJson object whenever an event is triggered.
138
138
  */
139
139
  async function transform(event: Partial<FixedChatResponse>, data: Partial<ChatResponseWithSteps>) {
140
- const info = event.agent_info
140
+ // todo fix
141
+ const info = event.agent_info as unknown as AgentInfo
141
142
 
142
143
  if (!info) return
143
144
 
@@ -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()
@@ -1,9 +1,10 @@
1
1
  import { RequestOpts } from '@oazapfts/runtime'
2
2
  import { AccountScmInfoSaveRequest, AccountScmInfoUpdateRequest, AccountScmStatusResponse, GroupsFromResourceResponse, MembersFromResourceResponse } from '../api/account'
3
3
  import { AgentVisibilityLevelEnum, HttpMethod, ListAgentResponse, VisibilityLevelEnum } from '../api/agent-tools'
4
- import { ChatRequest, ChatResponse3, ContentDependencyResponse, ConversationHistoryResponse, ConversationResponse, DependencyResponse } from '../api/ai'
4
+ import { ChatResponse3, ContentDependencyResponse, ConversationHistoryResponse, ConversationResponse, DependencyResponse, SourceKnowledgeSource, SourceProjectFile3, SourceStackAi } from '../api/ai'
5
5
  import { ConnectAccountRequestV2, ManagedAccountProvisionRequest } from '../api/cloudAccount'
6
6
  import { AllocationCostRequest, AllocationCostResponse, ChargePeriod, getAllocationCostFilters, ManagedService, ServiceResource } from '../api/cloudServices'
7
+ import { ChatRequest } from '../api/genAiInference'
7
8
  import { Action } from '../api/workspace-ai'
8
9
  import { ActivityResponse, FullInputContextResponse, InputConditionResponse, InputValuesContextResponse, PaginatedActivityResponse, PluginForAppCreationV2Response, PluginInputValuesInConsolidatedContextResponse, ValueByEnvResponse, WorkflowForCreationResponse } from '../api/workspaceManager'
9
10
 
@@ -172,20 +173,11 @@ export interface FixedPaginatedActivityResponse extends Omit<PaginatedActivityRe
172
173
 
173
174
  export interface FixedChatRequest extends ChatRequest {
174
175
  context?: {
175
- workspace?: string,
176
- conversation_id?: string,
177
- stack_id?: string,
178
- language?: string,
179
- project_recent_files?: string,
180
- knowledge_sources?: string[],
181
- agent_id?: string,
182
- agent_built_in?: boolean,
183
176
  platform?: string,
184
177
  platform_version?: string,
185
178
  stackspot_ai_version?: string,
186
179
  os?: string,
187
- upload_ids?: string[],
188
- selected_model_id?: string,
180
+ agent_id?: string,
189
181
  },
190
182
  }
191
183
 
@@ -366,9 +358,17 @@ export interface ToolCallsAgentInfo extends BaseAgentInfo {
366
358
 
367
359
  export type AgentInfo = GenericAgentInfo | PlanningAgentInfo | StepAgentInfo | ToolAgentInfo | ToolCallsAgentInfo
368
360
 
369
- export interface FixedChatResponse extends ChatResponse3 {
370
- agent_info: AgentInfo,
371
- tools?: string[],
361
+ export type ChatResponse = {
362
+ message: string,
363
+ cross_account_source: SourceKnowledgeSource[],
364
+ source: (SourceStackAi | SourceKnowledgeSource | SourceProjectFile3)[],
365
+ message_id: string | null,
366
+ stop_reason: 'stop',
367
+ };
368
+
369
+ export interface FixedChatResponse extends ChatResponse {
370
+ agent_info: AgentInfo[],
371
+ tools_id?: string[],
372
372
  }
373
373
  export interface OpportunitiesPMAgent {
374
374
  title: string,