@inkeep/agents-api 0.0.0-dev-20260122001454 → 0.0.0-dev-20260122004923

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.
@@ -1,5 +1,19 @@
1
1
  {
2
2
  "steps": {
3
+ "src/domains/evals/workflow/functions/runDatasetItem.ts": {
4
+ "callChatApiStep": {
5
+ "stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//callChatApiStep"
6
+ },
7
+ "createRelationStep": {
8
+ "stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//createRelationStep"
9
+ },
10
+ "executeEvaluatorStep": {
11
+ "stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//executeEvaluatorStep"
12
+ },
13
+ "logStep": {
14
+ "stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//logStep"
15
+ }
16
+ },
3
17
  "node_modules/.pnpm/workflow@4.0.1-beta.33_@aws-sdk+client-sts@3.970.0_@opentelemetry+api@1.9.0_@types+reac_5c488396978166b4f12e99cb3aa4a769/node_modules/workflow/dist/internal/builtins.js": {
4
18
  "__builtin_response_array_buffer": {
5
19
  "stepId": "__builtin_response_array_buffer"
@@ -24,20 +38,6 @@
24
38
  "logStep": {
25
39
  "stepId": "step//src/domains/evals/workflow/functions/evaluateConversation.ts//logStep"
26
40
  }
27
- },
28
- "src/domains/evals/workflow/functions/runDatasetItem.ts": {
29
- "callChatApiStep": {
30
- "stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//callChatApiStep"
31
- },
32
- "createRelationStep": {
33
- "stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//createRelationStep"
34
- },
35
- "executeEvaluatorStep": {
36
- "stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//executeEvaluatorStep"
37
- },
38
- "logStep": {
39
- "stepId": "step//src/domains/evals/workflow/functions/runDatasetItem.ts//logStep"
40
- }
41
41
  }
42
42
  },
43
43
  "workflows": {
@@ -127829,7 +127829,9 @@ var FunctionToolInsertSchema = createInsertSchema2(functionTools).extend({
127829
127829
  id: resourceIdSchema
127830
127830
  });
127831
127831
  var FunctionToolUpdateSchema = FunctionToolInsertSchema.partial();
127832
- var FunctionToolApiSelectSchema = createApiSchema(FunctionToolSelectSchema).openapi("FunctionTool");
127832
+ var FunctionToolApiSelectSchema = createApiSchema(FunctionToolSelectSchema).extend({
127833
+ relationshipId: external_exports.string().optional()
127834
+ }).openapi("FunctionTool");
127833
127835
  var FunctionToolApiInsertSchema = createAgentScopedApiInsertSchema(FunctionToolInsertSchema).openapi("FunctionToolCreate");
127834
127836
  var FunctionToolApiUpdateSchema = createApiUpdateSchema(FunctionToolUpdateSchema).openapi("FunctionToolUpdate");
127835
127837
  var SubAgentFunctionToolRelationSelectSchema = createSelectSchema2(subAgentFunctionToolRelations);
@@ -1,10 +1,10 @@
1
1
  import { AppConfig } from "./types/app.js";
2
2
  import "./types/index.js";
3
3
  import { Hono } from "hono";
4
- import * as hono_types8 from "hono/types";
4
+ import * as hono_types1 from "hono/types";
5
5
 
6
6
  //#region src/createApp.d.ts
7
7
  declare const isWebhookRoute: (path: string) => boolean;
8
- declare function createAgentsHono(config: AppConfig): Hono<hono_types8.BlankEnv, hono_types8.BlankSchema, "/">;
8
+ declare function createAgentsHono(config: AppConfig): Hono<hono_types1.BlankEnv, hono_types1.BlankSchema, "/">;
9
9
  //#endregion
10
10
  export { createAgentsHono, isWebhookRoute };
@@ -1,7 +1,7 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
- import * as hono16 from "hono";
2
+ import * as hono14 from "hono";
3
3
 
4
4
  //#region src/domains/evals/routes/datasetTriggers.d.ts
5
- declare const app: OpenAPIHono<hono16.Env, {}, "/">;
5
+ declare const app: OpenAPIHono<hono14.Env, {}, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
- import * as hono17 from "hono";
2
+ import * as hono15 from "hono";
3
3
 
4
4
  //#region src/domains/evals/routes/index.d.ts
5
- declare const app: OpenAPIHono<hono17.Env, {}, "/">;
5
+ declare const app: OpenAPIHono<hono15.Env, {}, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types1 from "hono/types";
2
+ import * as hono_types5 from "hono/types";
3
3
 
4
4
  //#region src/domains/evals/workflow/routes.d.ts
5
- declare const workflowRoutes: Hono<hono_types1.BlankEnv, hono_types1.BlankSchema, "/">;
5
+ declare const workflowRoutes: Hono<hono_types5.BlankEnv, hono_types5.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { workflowRoutes };
@@ -1,7 +1,7 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
- import * as hono14 from "hono";
2
+ import * as hono16 from "hono";
3
3
 
4
4
  //#region src/domains/manage/routes/conversations.d.ts
5
- declare const app: OpenAPIHono<hono14.Env, {}, "/">;
5
+ declare const app: OpenAPIHono<hono16.Env, {}, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { OpenAPIHono } from "@hono/zod-openapi";
2
- import * as hono15 from "hono";
2
+ import * as hono17 from "hono";
3
3
 
4
4
  //#region src/domains/manage/routes/index.d.ts
5
- declare const app: OpenAPIHono<hono15.Env, {}, "/">;
5
+ declare const app: OpenAPIHono<hono17.Env, {}, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -1,7 +1,7 @@
1
1
  import { Hono } from "hono";
2
- import * as hono_types3 from "hono/types";
2
+ import * as hono_types8 from "hono/types";
3
3
 
4
4
  //#region src/domains/manage/routes/mcp.d.ts
5
- declare const app: Hono<hono_types3.BlankEnv, hono_types3.BlankSchema, "/">;
5
+ declare const app: Hono<hono_types8.BlankEnv, hono_types8.BlankSchema, "/">;
6
6
  //#endregion
7
7
  export { app as default };
@@ -99,6 +99,7 @@ declare class Agent {
99
99
  private mcpConnectionLocks;
100
100
  private currentCompressor;
101
101
  private executionContext;
102
+ private functionToolRelationshipIdByName;
102
103
  constructor(config: AgentConfig, executionContext: FullExecutionContext, credentialStoreRegistry?: CredentialStoreRegistry);
103
104
  /**
104
105
  * Get the maximum number of generation steps for this agent
@@ -15,6 +15,7 @@ import { IncrementalStreamParser } from "../services/IncrementalStreamParser.js"
15
15
  import { MidGenerationCompressor } from "../services/MidGenerationCompressor.js";
16
16
  import { pendingToolApprovalManager } from "../services/PendingToolApprovalManager.js";
17
17
  import { ResponseFormatter } from "../services/ResponseFormatter.js";
18
+ import { toolApprovalUiBus } from "../services/ToolApprovalUiBus.js";
18
19
  import { generateToolId } from "../utils/agent-operations.js";
19
20
  import { jsonSchemaToZod } from "../utils/data-component-schema.js";
20
21
  import { ArtifactCreateSchema, ArtifactReferenceSchema } from "../utils/artifact-component-schema.js";
@@ -66,6 +67,7 @@ var Agent = class {
66
67
  mcpConnectionLocks = /* @__PURE__ */ new Map();
67
68
  currentCompressor = null;
68
69
  executionContext;
70
+ functionToolRelationshipIdByName = /* @__PURE__ */ new Map();
69
71
  constructor(config, executionContext, credentialStoreRegistry) {
70
72
  this.artifactComponents = config.artifactComponents || [];
71
73
  this.executionContext = executionContext;
@@ -135,6 +137,7 @@ var Agent = class {
135
137
  if (tool$1.config.mcp.activeTools?.includes(toolName)) return true;
136
138
  return tool$1.name === toolName;
137
139
  }))?.relationshipId;
140
+ if (toolType === "tool") return this.functionToolRelationshipIdByName.get(toolName);
138
141
  if (toolType === "delegation") return this.config.delegateRelations.find((relation) => this.#createRelationToolName("delegate", relation.config.id) === toolName)?.config.relationId;
139
142
  }
140
143
  /**
@@ -221,6 +224,12 @@ var Agent = class {
221
224
  execute: async (args, context$1) => {
222
225
  const startTime = Date.now();
223
226
  const toolCallId = context$1?.toolCallId || generateToolId();
227
+ const streamHelper = this.getStreamingHelper();
228
+ const chunkString = (s, size = 16) => {
229
+ const out = [];
230
+ for (let i = 0; i < s.length; i += size) out.push(s.slice(i, i + size));
231
+ return out;
232
+ };
224
233
  const activeSpan = trace.getActiveSpan();
225
234
  if (activeSpan) {
226
235
  const attributes = {
@@ -235,9 +244,26 @@ var Agent = class {
235
244
  if (options?.mcpServerName) attributes["ai.toolCall.mcpServerName"] = options.mcpServerName;
236
245
  activeSpan.setAttributes(attributes);
237
246
  }
238
- const isInternalTool = toolName.includes("save_tool_result") || toolName.includes("thinking_complete") || toolName.startsWith("transfer_to_");
247
+ const isInternalToolForUi = toolName.includes("save_tool_result") || toolName.includes("thinking_complete") || toolName.startsWith("transfer_to_") || toolName.startsWith("delegate_to_");
239
248
  const needsApproval = options?.needsApproval || false;
240
- if (streamRequestId && !isInternalTool) {
249
+ if (streamRequestId && streamHelper && !isInternalToolForUi) {
250
+ const inputText = JSON.stringify(args ?? {});
251
+ await streamHelper.writeToolInputStart({
252
+ toolCallId,
253
+ toolName
254
+ });
255
+ for (const part of chunkString(inputText, 16)) await streamHelper.writeToolInputDelta({
256
+ toolCallId,
257
+ inputTextDelta: part
258
+ });
259
+ await streamHelper.writeToolInputAvailable({
260
+ toolCallId,
261
+ toolName,
262
+ input: args ?? {},
263
+ providerMetadata: context$1?.providerMetadata
264
+ });
265
+ }
266
+ if (streamRequestId && !isInternalToolForUi) {
241
267
  const toolCallData = {
242
268
  toolName,
243
269
  input: args,
@@ -254,7 +280,7 @@ var Agent = class {
254
280
  const result = await originalExecute(args, context$1);
255
281
  const duration = Date.now() - startTime;
256
282
  const toolResultConversationId = this.getToolResultConversationId();
257
- if (streamRequestId && !isInternalTool && toolResultConversationId) try {
283
+ if (streamRequestId && !isInternalToolForUi && toolResultConversationId) try {
258
284
  const messagePayload = {
259
285
  id: generateId(),
260
286
  tenantId: this.config.tenantId,
@@ -282,7 +308,7 @@ var Agent = class {
282
308
  conversationId: toolResultConversationId
283
309
  }, "Failed to store tool result in conversation history");
284
310
  }
285
- if (streamRequestId && !isInternalTool) agentSessionManager.recordEvent(streamRequestId, "tool_result", this.config.id, {
311
+ if (streamRequestId && !isInternalToolForUi) agentSessionManager.recordEvent(streamRequestId, "tool_result", this.config.id, {
286
312
  toolName,
287
313
  output: result,
288
314
  toolCallId,
@@ -290,11 +316,17 @@ var Agent = class {
290
316
  relationshipId,
291
317
  needsApproval
292
318
  });
319
+ const isDeniedResult = !!result && typeof result === "object" && "__inkeepToolDenied" in result && result.__inkeepToolDenied === true;
320
+ if (streamRequestId && streamHelper && !isInternalToolForUi) if (isDeniedResult) await streamHelper.writeToolOutputDenied({ toolCallId });
321
+ else await streamHelper.writeToolOutputAvailable({
322
+ toolCallId,
323
+ output: result
324
+ });
293
325
  return result;
294
326
  } catch (error) {
295
327
  const duration = Date.now() - startTime;
296
328
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
297
- if (streamRequestId && !isInternalTool) agentSessionManager.recordEvent(streamRequestId, "tool_result", this.config.id, {
329
+ if (streamRequestId && !isInternalToolForUi) agentSessionManager.recordEvent(streamRequestId, "tool_result", this.config.id, {
298
330
  toolName,
299
331
  output: null,
300
332
  toolCallId,
@@ -303,6 +335,10 @@ var Agent = class {
303
335
  relationshipId,
304
336
  needsApproval
305
337
  });
338
+ if (streamRequestId && streamHelper && !isInternalToolForUi) await streamHelper.writeToolOutputError({
339
+ toolCallId,
340
+ error: errorMessage
341
+ });
306
342
  throw error;
307
343
  }
308
344
  }
@@ -372,7 +408,7 @@ var Agent = class {
372
408
  const sessionWrappedTool = tool({
373
409
  description: originalTool.description,
374
410
  inputSchema: originalTool.inputSchema,
375
- execute: async (args, { toolCallId }) => {
411
+ execute: async (args, { toolCallId, providerMetadata }) => {
376
412
  let processedArgs;
377
413
  try {
378
414
  processedArgs = parseEmbeddedJson(args);
@@ -410,22 +446,52 @@ var Agent = class {
410
446
  requestSpan.setStatus({ code: SpanStatusCode.OK });
411
447
  requestSpan.end();
412
448
  });
413
- const approvalResult = await pendingToolApprovalManager.waitForApproval(toolCallId, toolName, args, this.conversationId || "unknown", this.config.id);
414
- if (!approvalResult.approved) return tracer.startActiveSpan("tool.approval_denied", { attributes: {
415
- "tool.name": toolName,
416
- "tool.callId": toolCallId,
417
- "subAgent.id": this.config.id,
418
- "subAgent.name": this.config.name
419
- } }, (denialSpan) => {
420
- logger.info({
421
- toolName,
422
- toolCallId,
423
- reason: approvalResult.reason
424
- }, "Tool execution denied by user");
425
- denialSpan.setStatus({ code: SpanStatusCode.OK });
426
- denialSpan.end();
427
- return `User denied approval to run this tool: ${approvalResult.reason}`;
449
+ const streamHelper = this.getStreamingHelper();
450
+ if (streamHelper) await streamHelper.writeToolApprovalRequest({
451
+ approvalId: `aitxt-${toolCallId}`,
452
+ toolCallId
428
453
  });
454
+ else if (this.isDelegatedAgent) {
455
+ const streamRequestId$1 = this.getStreamRequestId();
456
+ if (streamRequestId$1) await toolApprovalUiBus.publish(streamRequestId$1, {
457
+ type: "approval-needed",
458
+ toolCallId,
459
+ toolName,
460
+ input: finalArgs,
461
+ providerMetadata,
462
+ approvalId: `aitxt-${toolCallId}`
463
+ });
464
+ }
465
+ const approvalResult = await pendingToolApprovalManager.waitForApproval(toolCallId, toolName, args, this.conversationId || "unknown", this.config.id);
466
+ if (!approvalResult.approved) {
467
+ if (!streamHelper && this.isDelegatedAgent) {
468
+ const streamRequestId$1 = this.getStreamRequestId();
469
+ if (streamRequestId$1) await toolApprovalUiBus.publish(streamRequestId$1, {
470
+ type: "approval-resolved",
471
+ toolCallId,
472
+ approved: false
473
+ });
474
+ }
475
+ return tracer.startActiveSpan("tool.approval_denied", { attributes: {
476
+ "tool.name": toolName,
477
+ "tool.callId": toolCallId,
478
+ "subAgent.id": this.config.id,
479
+ "subAgent.name": this.config.name
480
+ } }, (denialSpan) => {
481
+ logger.info({
482
+ toolName,
483
+ toolCallId,
484
+ reason: approvalResult.reason
485
+ }, "Tool execution denied by user");
486
+ denialSpan.setStatus({ code: SpanStatusCode.OK });
487
+ denialSpan.end();
488
+ return {
489
+ __inkeepToolDenied: true,
490
+ toolCallId,
491
+ reason: approvalResult.reason
492
+ };
493
+ });
494
+ }
429
495
  tracer.startActiveSpan("tool.approval_approved", { attributes: {
430
496
  "tool.name": toolName,
431
497
  "tool.callId": toolCallId,
@@ -439,6 +505,14 @@ var Agent = class {
439
505
  approvedSpan.setStatus({ code: SpanStatusCode.OK });
440
506
  approvedSpan.end();
441
507
  });
508
+ if (!streamHelper && this.isDelegatedAgent) {
509
+ const streamRequestId$1 = this.getStreamRequestId();
510
+ if (streamRequestId$1) await toolApprovalUiBus.publish(streamRequestId$1, {
511
+ type: "approval-resolved",
512
+ toolCallId,
513
+ approved: true
514
+ });
515
+ }
442
516
  }
443
517
  logger.debug({
444
518
  toolName,
@@ -498,10 +572,7 @@ var Agent = class {
498
572
  result: enhancedResult,
499
573
  timestamp: Date.now()
500
574
  });
501
- return {
502
- result: enhancedResult,
503
- toolCallId
504
- };
575
+ return enhancedResult;
505
576
  } catch (error) {
506
577
  logger.error({
507
578
  toolName,
@@ -727,6 +798,9 @@ var Agent = class {
727
798
  });
728
799
  })).data ?? [];
729
800
  if (functionToolsData.length === 0) return functionTools;
801
+ this.functionToolRelationshipIdByName = new Map(functionToolsData.flatMap((t) => {
802
+ return t.relationshipId ? [[t.name, t.relationshipId]] : [];
803
+ }));
730
804
  const { SandboxExecutorFactory } = await import("../tools/SandboxExecutorFactory.js");
731
805
  const sandboxExecutor = sessionId ? SandboxExecutorFactory.getForSession(sessionId) : new SandboxExecutorFactory();
732
806
  for (const functionToolDef of functionToolsData) {
@@ -790,10 +864,7 @@ var Agent = class {
790
864
  result,
791
865
  timestamp: Date.now()
792
866
  });
793
- return {
794
- result,
795
- toolCallId
796
- };
867
+ return result;
797
868
  } catch (error) {
798
869
  logger.error({
799
870
  toolName: functionToolDef.name,
@@ -3,6 +3,7 @@ import runDbClient_default from "../../../data/db/runDbClient.js";
3
3
  import { contextValidationMiddleware } from "../context/validation.js";
4
4
  import { handleContextResolution } from "../context/context.js";
5
5
  import "../context/index.js";
6
+ import { toolApprovalUiBus } from "../services/ToolApprovalUiBus.js";
6
7
  import { errorOp } from "../utils/agent-operations.js";
7
8
  import { createSSEStreamHelper } from "../utils/stream-helpers.js";
8
9
  import { ExecutionHandler } from "../handlers/executionHandler.js";
@@ -222,9 +223,50 @@ app.openapi(chatCompletionsRoute, async (c) => {
222
223
  "database.operation": "insert"
223
224
  });
224
225
  return streamSSE(c, async (stream$1) => {
226
+ const chunkString = (s, size = 16) => {
227
+ const out = [];
228
+ for (let i = 0; i < s.length; i += size) out.push(s.slice(i, i + size));
229
+ return out;
230
+ };
231
+ let unsubscribe;
225
232
  try {
226
233
  const sseHelper = createSSEStreamHelper(stream$1, requestId, timestamp);
227
234
  await sseHelper.writeRole();
235
+ const seenToolCalls = /* @__PURE__ */ new Set();
236
+ const seenOutputs = /* @__PURE__ */ new Set();
237
+ unsubscribe = toolApprovalUiBus.subscribe(requestId, async (event) => {
238
+ if (event.type === "approval-needed") {
239
+ if (seenToolCalls.has(event.toolCallId)) return;
240
+ seenToolCalls.add(event.toolCallId);
241
+ await sseHelper.writeToolInputStart({
242
+ toolCallId: event.toolCallId,
243
+ toolName: event.toolName
244
+ });
245
+ const inputText = JSON.stringify(event.input ?? {});
246
+ for (const part of chunkString(inputText, 16)) await sseHelper.writeToolInputDelta({
247
+ toolCallId: event.toolCallId,
248
+ inputTextDelta: part
249
+ });
250
+ await sseHelper.writeToolInputAvailable({
251
+ toolCallId: event.toolCallId,
252
+ toolName: event.toolName,
253
+ input: event.input ?? {},
254
+ providerMetadata: event.providerMetadata
255
+ });
256
+ await sseHelper.writeToolApprovalRequest({
257
+ approvalId: event.approvalId,
258
+ toolCallId: event.toolCallId
259
+ });
260
+ } else if (event.type === "approval-resolved") {
261
+ if (seenOutputs.has(event.toolCallId)) return;
262
+ seenOutputs.add(event.toolCallId);
263
+ if (event.approved) await sseHelper.writeToolOutputAvailable({
264
+ toolCallId: event.toolCallId,
265
+ output: { status: "approved" }
266
+ });
267
+ else await sseHelper.writeToolOutputDenied({ toolCallId: event.toolCallId });
268
+ }
269
+ });
228
270
  logger.info({ subAgentId }, "Starting execution");
229
271
  const emitOperations = c.req.header("x-emit-operations") === "true";
230
272
  const forwardedHeaders = {};
@@ -275,6 +317,10 @@ app.openapi(chatCompletionsRoute, async (c) => {
275
317
  } catch (streamError) {
276
318
  logger.error({ streamError }, "Failed to write error to stream");
277
319
  }
320
+ } finally {
321
+ try {
322
+ unsubscribe?.();
323
+ } catch (_e) {}
278
324
  }
279
325
  });
280
326
  });
@@ -4,6 +4,7 @@ import { contextValidationMiddleware } from "../context/validation.js";
4
4
  import { handleContextResolution } from "../context/context.js";
5
5
  import "../context/index.js";
6
6
  import { pendingToolApprovalManager } from "../services/PendingToolApprovalManager.js";
7
+ import { toolApprovalUiBus } from "../services/ToolApprovalUiBus.js";
7
8
  import { errorOp } from "../utils/agent-operations.js";
8
9
  import { createBufferingStreamHelper, createVercelStreamHelper } from "../utils/stream-helpers.js";
9
10
  import { ExecutionHandler } from "../handlers/executionHandler.js";
@@ -34,16 +35,31 @@ const chatDataStreamRoute = createRoute({
34
35
  "tool"
35
36
  ]),
36
37
  content: z.any(),
37
- parts: z.array(z.object({
38
- type: z.union([z.enum([
39
- "text",
40
- "image",
41
- "audio",
42
- "video",
43
- "file"
44
- ]), z.string().regex(/^data-/, "Type must start with \"data-\"")]),
45
- text: z.string().optional()
46
- })).optional()
38
+ parts: z.array(z.union([
39
+ z.object({
40
+ type: z.union([z.enum([
41
+ "text",
42
+ "image",
43
+ "audio",
44
+ "video",
45
+ "file"
46
+ ]), z.string().regex(/^data-/, "Type must start with \"data-\"")]),
47
+ text: z.string().optional()
48
+ }),
49
+ z.object({
50
+ type: z.string().regex(/^tool-/, "Type must start with \"tool-\""),
51
+ toolCallId: z.string(),
52
+ state: z.literal("approval-responded"),
53
+ approval: z.object({
54
+ id: z.string(),
55
+ approved: z.boolean(),
56
+ reason: z.string().optional()
57
+ }),
58
+ input: z.any().optional(),
59
+ callProviderMetadata: z.any().optional()
60
+ }),
61
+ z.object({ type: z.literal("step-start") })
62
+ ])).optional()
47
63
  })),
48
64
  id: z.string().optional(),
49
65
  conversationId: z.string().optional(),
@@ -74,7 +90,39 @@ app.openapi(chatDataStreamRoute, async (c) => {
74
90
  agentId
75
91
  }, "Extracted chatDataStream parameters");
76
92
  const body = c.get("requestBody") || {};
77
- const conversationId = body.conversationId || getConversationId();
93
+ const approvalPart = (body.messages || []).flatMap((m) => m?.parts || []).find((p) => p?.state === "approval-responded" && typeof p?.toolCallId === "string");
94
+ const isApprovalResponse = !!approvalPart;
95
+ const conversationId = isApprovalResponse ? body.conversationId : body.conversationId || getConversationId();
96
+ if (isApprovalResponse) {
97
+ if (!conversationId) return c.json({
98
+ success: false,
99
+ error: "conversationId is required for approval response"
100
+ }, 400);
101
+ const toolCallId = approvalPart.toolCallId;
102
+ const approved = !!approvalPart.approval?.approved;
103
+ const reason = approvalPart.approval?.reason;
104
+ if (!await getConversation(runDbClient_default)({
105
+ scopes: {
106
+ tenantId,
107
+ projectId
108
+ },
109
+ conversationId
110
+ })) return c.json({
111
+ success: false,
112
+ error: "Conversation not found"
113
+ }, 404);
114
+ if (!(approved ? pendingToolApprovalManager.approveToolCall(toolCallId) : pendingToolApprovalManager.denyToolCall(toolCallId, reason))) return c.json({
115
+ success: true,
116
+ toolCallId,
117
+ approved,
118
+ alreadyProcessed: true
119
+ });
120
+ return c.json({
121
+ success: true,
122
+ toolCallId,
123
+ approved
124
+ });
125
+ }
78
126
  const targetTenantId = c.req.header("x-target-tenant-id");
79
127
  const targetProjectId = c.req.header("x-target-project-id");
80
128
  const targetAgentId = c.req.header("x-target-agent-id");
@@ -227,16 +275,58 @@ app.openapi(chatDataStreamRoute, async (c) => {
227
275
  }
228
276
  const dataStream = createUIMessageStream({ execute: async ({ writer }) => {
229
277
  const streamHelper = createVercelStreamHelper(writer);
278
+ let unsubscribe;
230
279
  try {
231
280
  const emitOperations = c.req.header("x-emit-operations") === "true";
232
281
  const executionHandler = new ExecutionHandler();
233
282
  const datasetRunId = c.req.header("x-inkeep-dataset-run-id");
283
+ const requestId = `chatds-${Date.now()}`;
284
+ const chunkString = (s, size = 16) => {
285
+ const out = [];
286
+ for (let i = 0; i < s.length; i += size) out.push(s.slice(i, i + size));
287
+ return out;
288
+ };
289
+ const seenToolCalls = /* @__PURE__ */ new Set();
290
+ const seenOutputs = /* @__PURE__ */ new Set();
291
+ unsubscribe = toolApprovalUiBus.subscribe(requestId, async (event) => {
292
+ if (event.type === "approval-needed") {
293
+ if (seenToolCalls.has(event.toolCallId)) return;
294
+ seenToolCalls.add(event.toolCallId);
295
+ await streamHelper.writeToolInputStart({
296
+ toolCallId: event.toolCallId,
297
+ toolName: event.toolName
298
+ });
299
+ const inputText = JSON.stringify(event.input ?? {});
300
+ for (const part of chunkString(inputText, 16)) await streamHelper.writeToolInputDelta({
301
+ toolCallId: event.toolCallId,
302
+ inputTextDelta: part
303
+ });
304
+ await streamHelper.writeToolInputAvailable({
305
+ toolCallId: event.toolCallId,
306
+ toolName: event.toolName,
307
+ input: event.input ?? {},
308
+ providerMetadata: event.providerMetadata
309
+ });
310
+ await streamHelper.writeToolApprovalRequest({
311
+ approvalId: event.approvalId,
312
+ toolCallId: event.toolCallId
313
+ });
314
+ } else if (event.type === "approval-resolved") {
315
+ if (seenOutputs.has(event.toolCallId)) return;
316
+ seenOutputs.add(event.toolCallId);
317
+ if (event.approved) await streamHelper.writeToolOutputAvailable({
318
+ toolCallId: event.toolCallId,
319
+ output: { status: "approved" }
320
+ });
321
+ else await streamHelper.writeToolOutputDenied({ toolCallId: event.toolCallId });
322
+ }
323
+ });
234
324
  if (!(await executionHandler.execute({
235
325
  executionContext,
236
326
  conversationId,
237
327
  userMessage: userText,
238
328
  initialAgentId: subAgentId,
239
- requestId: `chatds-${Date.now()}`,
329
+ requestId,
240
330
  sseHelper: streamHelper,
241
331
  emitOperations,
242
332
  datasetRunId: datasetRunId || void 0,
@@ -246,6 +336,9 @@ app.openapi(chatDataStreamRoute, async (c) => {
246
336
  logger.error({ err }, "Streaming error");
247
337
  await streamHelper.writeOperation(errorOp("Internal server error", "system"));
248
338
  } finally {
339
+ try {
340
+ unsubscribe?.();
341
+ } catch (_e) {}
249
342
  if ("cleanup" in streamHelper && typeof streamHelper.cleanup === "function") streamHelper.cleanup();
250
343
  }
251
344
  } });
@@ -0,0 +1,28 @@
1
+ //#region src/domains/run/services/ToolApprovalUiBus.d.ts
2
+ type ToolApprovalUiEvent = {
3
+ type: 'approval-needed';
4
+ toolCallId: string;
5
+ toolName: string;
6
+ input: any;
7
+ providerMetadata?: any;
8
+ approvalId: string;
9
+ } | {
10
+ type: 'approval-resolved';
11
+ toolCallId: string;
12
+ approved: boolean;
13
+ };
14
+ type Listener = (event: ToolApprovalUiEvent) => void | Promise<void>;
15
+ /**
16
+ * In-process event bus keyed by streamRequestId.
17
+ *
18
+ * Used to propagate approval UI events from delegated agents (who must not write to stream)
19
+ * up to the user-facing request handler, which can stream tool UI parts to the client.
20
+ */
21
+ declare class ToolApprovalUiBus {
22
+ private listeners;
23
+ subscribe(streamRequestId: string, listener: Listener): () => void;
24
+ publish(streamRequestId: string, event: ToolApprovalUiEvent): Promise<void>;
25
+ }
26
+ declare const toolApprovalUiBus: ToolApprovalUiBus;
27
+ //#endregion
28
+ export { ToolApprovalUiBus, ToolApprovalUiEvent, toolApprovalUiBus };