@alquimia-ai/tools 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -12
- package/dist/actions/index.d.mts +1 -1
- package/dist/actions/index.d.ts +1 -1
- package/dist/adapters/fetch.d.mts +1 -0
- package/dist/adapters/fetch.d.ts +1 -0
- package/dist/adapters/fetch.js +4 -0
- package/dist/adapters/fetch.js.map +1 -1
- package/dist/adapters/fetch.mjs +4 -0
- package/dist/adapters/fetch.mjs.map +1 -1
- package/dist/adapters/index.d.mts +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/next.d.mts +1 -0
- package/dist/adapters/next.d.ts +1 -0
- package/dist/adapters/next.js +4 -0
- package/dist/adapters/next.js.map +1 -1
- package/dist/adapters/next.mjs +4 -0
- package/dist/adapters/next.mjs.map +1 -1
- package/dist/approvals-CcnmqUkQ.d.ts +59 -0
- package/dist/approvals-dn4sd_Ot.d.mts +59 -0
- package/dist/hooks/index.d.mts +36 -5
- package/dist/hooks/index.d.ts +36 -5
- package/dist/hooks/index.js +208 -12
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +208 -12
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/next/index.d.mts +7 -0
- package/dist/next/index.d.ts +7 -0
- package/dist/next/index.js +56 -40
- package/dist/next/index.js.map +1 -1
- package/dist/next/index.mjs +56 -40
- package/dist/next/index.mjs.map +1 -1
- package/dist/providers/index.d.mts +3 -3
- package/dist/providers/index.d.ts +3 -3
- package/dist/{providers-mx21gNgZ.d.ts → providers-J_Z9rQ3Q.d.ts} +1 -1
- package/dist/{providers-B4IOiGfr.d.mts → providers-YKiEag2L.d.mts} +1 -1
- package/dist/proxy.d.mts +2 -0
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.js +47 -40
- package/dist/proxy.js.map +1 -1
- package/dist/proxy.mjs +47 -40
- package/dist/proxy.mjs.map +1 -1
- package/dist/sdk/index.d.mts +25 -3
- package/dist/sdk/index.d.ts +25 -3
- package/dist/sdk/index.js +73 -10
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/index.mjs +73 -10
- package/dist/sdk/index.mjs.map +1 -1
- package/dist/services/index.d.mts +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/{type-D-JaSMkV.d.ts → type-BFtGEEb4.d.ts} +1 -1
- package/dist/{type-61gaZga-.d.mts → type-C_sKpIM1.d.mts} +1 -1
- package/dist/types/index.d.mts +23 -2
- package/dist/types/index.d.ts +23 -2
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/worklog/index.d.mts +1 -0
- package/dist/worklog/index.d.ts +1 -0
- package/dist/worklog/index.js +75 -2
- package/dist/worklog/index.js.map +1 -1
- package/dist/worklog/index.mjs +75 -2
- package/dist/worklog/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/hooks/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { AlquimiaSDK } from '../sdk/index.js';
|
|
3
3
|
import { AlquimiaAdapter } from '../adapters/index.js';
|
|
4
|
-
import { W as WhisperProvider, S as StableDiffusionProvider, C as CharacterizationProvider, R as RatingsProvider, L as LoggerProvider } from '../providers-
|
|
5
|
-
import { G as GenuiSurfaceItem,
|
|
6
|
-
import {
|
|
7
|
-
import { a as WorklogState } from '../types-lOliroPw.js';
|
|
4
|
+
import { W as WhisperProvider, S as StableDiffusionProvider, C as CharacterizationProvider, R as RatingsProvider, L as LoggerProvider } from '../providers-J_Z9rQ3Q.js';
|
|
5
|
+
import { G as GenuiSurfaceItem, a as AlquimiaMessage, b as ToolEvent, c as AIMessageChunk, R as RatingData } from '../type-BFtGEEb4.js';
|
|
6
|
+
import { ToolApprovalResult } from '../types/index.js';
|
|
8
7
|
import { createMessageId } from '../utils/index.js';
|
|
8
|
+
import { a as WorklogState } from '../types-lOliroPw.js';
|
|
9
|
+
import { A as ApprovalRequest } from '../approvals-CcnmqUkQ.js';
|
|
10
|
+
import { T as ToolExecutionResponse } from '../types-CMVbyEv8.js';
|
|
9
11
|
import { C as CatalogManifest } from '../registry-Bcu_ZYL1.js';
|
|
10
12
|
import { U as UIAction } from '../actions-CP1d4Ye1.js';
|
|
11
13
|
import '@elastic/apm-rum';
|
|
@@ -40,11 +42,40 @@ interface UseAlquimiaConfig {
|
|
|
40
42
|
source?: 'client' | 'agent';
|
|
41
43
|
};
|
|
42
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* A human-approval gate as the hook exposes it: what the worklog says, overlaid
|
|
47
|
+
* with this client's own submission.
|
|
48
|
+
*/
|
|
49
|
+
interface ToolApproval extends ApprovalRequest {
|
|
50
|
+
/**
|
|
51
|
+
* `pending` until someone decides; `submitting` while this client's call is
|
|
52
|
+
* in flight; `approved`/`rejected` once the runtime accepted a decision —
|
|
53
|
+
* this client's (optimistically, before the stream confirms) or anyone's.
|
|
54
|
+
*/
|
|
55
|
+
status: 'pending' | 'submitting' | 'approved' | 'rejected';
|
|
56
|
+
/** Rejection reason, from the worklog or from this client's submission. */
|
|
57
|
+
reason: string | null;
|
|
58
|
+
/** `true` when the accepted decision was made from this client. */
|
|
59
|
+
decidedHere: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Whether offering Approve / Reject here can still work. `false` for a
|
|
62
|
+
* channel-owned gate, and after the runtime refused this client with a 409,
|
|
63
|
+
* 403 or 404 — retrying those can only fail the same way.
|
|
64
|
+
*/
|
|
65
|
+
canAnswer: boolean;
|
|
66
|
+
/** The runtime's answer to this client's last submission, if any. */
|
|
67
|
+
lastResult: ToolApprovalResult | null;
|
|
68
|
+
/** Message count the gate was raised after, for anchoring it in the chat. */
|
|
69
|
+
afterCount: number;
|
|
70
|
+
}
|
|
43
71
|
declare function useAlquimia(config: UseAlquimiaConfig): {
|
|
44
72
|
sdk: AlquimiaSDK;
|
|
45
73
|
activeTool: any;
|
|
46
74
|
pendingClientTool: PendingClientTool | null;
|
|
47
75
|
completeToolExecution: (controlId: string, result: ToolExecutionResponse) => Promise<void>;
|
|
76
|
+
toolApprovals: ToolApproval[];
|
|
77
|
+
pendingApprovals: ToolApproval[];
|
|
78
|
+
approveTool: (controlId: string, approved: boolean, message?: string) => Promise<ToolApprovalResult>;
|
|
48
79
|
genui: {
|
|
49
80
|
surfaces: GenuiSurfaceItem[];
|
|
50
81
|
onAgentAction: (controlId: string) => (action: UIAction) => void;
|
|
@@ -105,4 +136,4 @@ declare function useRatings({ assistantId, sendRating, topicId, onError }: UseRa
|
|
|
105
136
|
hasReviewed: boolean;
|
|
106
137
|
};
|
|
107
138
|
|
|
108
|
-
export { useAlquimia, useRatings };
|
|
139
|
+
export { type ToolApproval, type UseAlquimiaConfig, useAlquimia, useRatings };
|
package/dist/hooks/index.js
CHANGED
|
@@ -51,6 +51,9 @@ var AlquimiaSDK = class {
|
|
|
51
51
|
// Registry version tag resolved by the runtime for this task (v0.5.1), so
|
|
52
52
|
// the tool-completion half of the turn lands on the same agentspace version.
|
|
53
53
|
this.versionTag = null;
|
|
54
|
+
// Agentspace the runtime resolved for this task. Tool completion and
|
|
55
|
+
// approval authorize a non-admin caller against it, so it is echoed back.
|
|
56
|
+
this.agentspaceId = null;
|
|
54
57
|
this.tools = [];
|
|
55
58
|
this.extraInstructions = null;
|
|
56
59
|
this.assistantConfig = null;
|
|
@@ -202,6 +205,7 @@ var AlquimiaSDK = class {
|
|
|
202
205
|
this.traceParent = opts.traceParent ?? null;
|
|
203
206
|
this.streamId = result.taskid ?? null;
|
|
204
207
|
this.versionTag = result.versiontag ?? this.versionTag;
|
|
208
|
+
this.agentspaceId = result.agentspaceid ?? null;
|
|
205
209
|
this.attachmentResponses = result.attachments ?? [];
|
|
206
210
|
this.attachments = [];
|
|
207
211
|
return this;
|
|
@@ -221,17 +225,37 @@ var AlquimiaSDK = class {
|
|
|
221
225
|
"AlquimiaSDK: adapter does not support tool completion (resolveToolCompletionUrl missing)"
|
|
222
226
|
);
|
|
223
227
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
228
|
+
await this.axiosInstance.post(url, response, { headers: this.buildDecisionHeaders() });
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Answer a `HumanApprovalRequired` gate: POSTs a {@link ToolApprovalEvent} to
|
|
232
|
+
* the runtime's `/event/tool-approval`. `controlId` is the gate's own control
|
|
233
|
+
* id (the `HumanApprovalRequired` record's), not the parked tool call's.
|
|
234
|
+
*
|
|
235
|
+
* Never throws for a runtime refusal — each 409 means something different to
|
|
236
|
+
* the UI, so the answer comes back classified as a {@link ToolApprovalResult}.
|
|
237
|
+
* It does throw when the adapter has no approval route, which is a wiring bug
|
|
238
|
+
* rather than a runtime answer.
|
|
239
|
+
*/
|
|
240
|
+
async submitToolApproval(controlId, approved, message) {
|
|
241
|
+
const url = this.adapter.resolveToolApprovalUrl?.();
|
|
242
|
+
if (!url) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
"AlquimiaSDK: adapter does not support tool approval (resolveToolApprovalUrl missing)"
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const event = {
|
|
248
|
+
event_class: "ToolApprovalEvent",
|
|
249
|
+
control_id: controlId,
|
|
250
|
+
result: approved,
|
|
251
|
+
...message && { message }
|
|
233
252
|
};
|
|
234
|
-
|
|
253
|
+
try {
|
|
254
|
+
await this.axiosInstance.post(url, event, { headers: this.buildDecisionHeaders() });
|
|
255
|
+
return { outcome: "accepted" };
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return classifyApprovalError(error);
|
|
258
|
+
}
|
|
235
259
|
}
|
|
236
260
|
async generateImage(query) {
|
|
237
261
|
if (!this.stableDiffusionProvider) {
|
|
@@ -302,6 +326,23 @@ var AlquimiaSDK = class {
|
|
|
302
326
|
if (this.streamId) headers["task-id"] = this.streamId;
|
|
303
327
|
return headers;
|
|
304
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Correlation headers for tool-completion and tool-approval: the runtime's
|
|
331
|
+
* `CommonAttributes` header aliases, with no separator. Not interchangeable
|
|
332
|
+
* with the kebab-case set in {@link buildIdentityHeaders}.
|
|
333
|
+
*/
|
|
334
|
+
buildDecisionHeaders() {
|
|
335
|
+
return {
|
|
336
|
+
"Content-Type": "application/json",
|
|
337
|
+
assistantid: this.assistantId,
|
|
338
|
+
...this.streamId && { taskid: this.streamId },
|
|
339
|
+
...this.conversationId && { sessionid: this.conversationId },
|
|
340
|
+
...this.userId && { userid: this.userId },
|
|
341
|
+
...this.agentspaceId && { agentspaceid: this.agentspaceId },
|
|
342
|
+
...this.versionTag && { versiontag: this.versionTag },
|
|
343
|
+
...this.adapter.getHeaders?.() ?? {}
|
|
344
|
+
};
|
|
345
|
+
}
|
|
305
346
|
getUrlStream() {
|
|
306
347
|
if (!this.streamId) {
|
|
307
348
|
throw new Error("Stream ID not initialized. Call sendMessage() first");
|
|
@@ -322,7 +363,29 @@ var AlquimiaSDK = class {
|
|
|
322
363
|
getVersionTag() {
|
|
323
364
|
return this.versionTag;
|
|
324
365
|
}
|
|
366
|
+
/** Agentspace the runtime resolved for the current task, if any. */
|
|
367
|
+
getAgentspaceId() {
|
|
368
|
+
return this.agentspaceId;
|
|
369
|
+
}
|
|
325
370
|
};
|
|
371
|
+
function classifyApprovalError(error) {
|
|
372
|
+
const response = error?.response;
|
|
373
|
+
const status = response?.status;
|
|
374
|
+
const data = response?.data;
|
|
375
|
+
const rawDetail = typeof data === "string" ? data : data?.detail ?? data?.error ?? error?.message;
|
|
376
|
+
const detail = typeof rawDetail === "string" ? rawDetail : JSON.stringify(rawDetail ?? "");
|
|
377
|
+
let outcome = "error";
|
|
378
|
+
if (status === 409) {
|
|
379
|
+
if (/original channel/i.test(detail)) outcome = "wrong-channel";
|
|
380
|
+
else if (/already been recorded/i.test(detail)) outcome = "already-decided";
|
|
381
|
+
else if (/no pending approval/i.test(detail)) outcome = "not-pending";
|
|
382
|
+
} else if (status === 401 || status === 403) {
|
|
383
|
+
outcome = "forbidden";
|
|
384
|
+
} else if (status === 404) {
|
|
385
|
+
outcome = "not-found";
|
|
386
|
+
}
|
|
387
|
+
return { outcome, ...status !== void 0 && { status }, detail };
|
|
388
|
+
}
|
|
326
389
|
var alquimia_sdk_default = AlquimiaSDK;
|
|
327
390
|
|
|
328
391
|
// src/utils/utils.ts
|
|
@@ -426,6 +489,10 @@ var agentTitle = (r) => {
|
|
|
426
489
|
return typeof name === "string" && name.length > 0 ? `Agent: ${name}` : "Agent-to-agent";
|
|
427
490
|
};
|
|
428
491
|
var constTitle = (label) => () => label;
|
|
492
|
+
var approvalTitle = (r) => {
|
|
493
|
+
const name = asRecord(asRecord(r.data).parameters).name;
|
|
494
|
+
return typeof name === "string" && name.length > 0 ? `Approval required: ${name}` : "Human approval required";
|
|
495
|
+
};
|
|
429
496
|
var shieldBlockTitle = (r) => {
|
|
430
497
|
const id = asRecord(r.data).shield_id;
|
|
431
498
|
return typeof id === "string" && id.length > 0 ? `Blocked by ${id}` : "Blocked by safeguard";
|
|
@@ -477,7 +544,7 @@ var EVENT_REGISTRY = {
|
|
|
477
544
|
HumanApprovalRequired: {
|
|
478
545
|
kind: "tool",
|
|
479
546
|
role: "open",
|
|
480
|
-
title:
|
|
547
|
+
title: approvalTitle,
|
|
481
548
|
pending: true
|
|
482
549
|
},
|
|
483
550
|
HumanApprovalRequiredResponse: { kind: "tool", role: "close", title: constTitle("Human approval") }
|
|
@@ -566,7 +633,12 @@ function reduceWorklog(state, record) {
|
|
|
566
633
|
const pos = Object.prototype.hasOwnProperty.call(next.index, key) ? next.index[key] : void 0;
|
|
567
634
|
if (pos != null) {
|
|
568
635
|
const existing = next.nodes[pos];
|
|
569
|
-
next.nodes[pos] = {
|
|
636
|
+
next.nodes[pos] = {
|
|
637
|
+
...existing,
|
|
638
|
+
command: existing.command ?? record.data,
|
|
639
|
+
title: existing.command === void 0 ? interp.title(record) : existing.title,
|
|
640
|
+
raw: [...existing.raw, record]
|
|
641
|
+
};
|
|
570
642
|
} else {
|
|
571
643
|
next.index[key] = next.nodes.length;
|
|
572
644
|
next.nodes.push(makeOpenNode(record, interp, key));
|
|
@@ -591,6 +663,59 @@ function frameToRecord(frame) {
|
|
|
591
663
|
};
|
|
592
664
|
}
|
|
593
665
|
|
|
666
|
+
// src/worklog/approvals.ts
|
|
667
|
+
function initialApprovalsState() {
|
|
668
|
+
return { order: [], byId: {} };
|
|
669
|
+
}
|
|
670
|
+
var asObject = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
671
|
+
var MCP_APPROVAL_EXTENSION = "mcp_approval";
|
|
672
|
+
function describeRequest(record) {
|
|
673
|
+
const execution = asObject(asObject(record.data)?.parameters) ?? {};
|
|
674
|
+
const name = execution.name;
|
|
675
|
+
const callId = execution.control_id;
|
|
676
|
+
const args = execution.tool_call_parameters !== void 0 && execution.tool_call_parameters !== null ? execution.tool_call_parameters : execution.parameters;
|
|
677
|
+
const extensions = asObject(execution.parameters)?.runtime_extensions;
|
|
678
|
+
return {
|
|
679
|
+
toolName: typeof name === "string" && name.length > 0 ? name : null,
|
|
680
|
+
toolCallId: typeof callId === "string" ? callId : null,
|
|
681
|
+
arguments: args ?? null,
|
|
682
|
+
channelOwned: Array.isArray(extensions) && extensions.includes(MCP_APPROVAL_EXTENSION)
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function describeDecision(record) {
|
|
686
|
+
const approved = record.data === true;
|
|
687
|
+
return {
|
|
688
|
+
approved,
|
|
689
|
+
reason: !approved && typeof record.data === "string" && record.data.length > 0 ? record.data : null,
|
|
690
|
+
decidedAt: record.date_submitted
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
var blank = (controlId) => ({
|
|
694
|
+
controlId,
|
|
695
|
+
toolName: null,
|
|
696
|
+
toolCallId: null,
|
|
697
|
+
arguments: null,
|
|
698
|
+
channelOwned: false,
|
|
699
|
+
requestedAt: null,
|
|
700
|
+
decision: null
|
|
701
|
+
});
|
|
702
|
+
function reduceApprovals(state, record) {
|
|
703
|
+
const isRequest = record.event_class === "HumanApprovalRequired";
|
|
704
|
+
const isResponse = record.event_class === "HumanApprovalRequiredResponse";
|
|
705
|
+
if (!isRequest && !isResponse || !record.control_id) return state;
|
|
706
|
+
const id = record.control_id;
|
|
707
|
+
const known = state.byId[id];
|
|
708
|
+
const current = known ?? blank(id);
|
|
709
|
+
const next = isRequest ? { ...current, ...describeRequest(record), requestedAt: record.date_submitted } : { ...current, decision: current.decision ?? describeDecision(record) };
|
|
710
|
+
return {
|
|
711
|
+
order: known ? state.order : [...state.order, id],
|
|
712
|
+
byId: { ...state.byId, [id]: next }
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
function listApprovals(state) {
|
|
716
|
+
return state.order.map((id) => state.byId[id]);
|
|
717
|
+
}
|
|
718
|
+
|
|
594
719
|
// src/hooks/pending-client-tool.ts
|
|
595
720
|
function extractPendingClientTool(frame) {
|
|
596
721
|
if (!frame || typeof frame !== "object") return null;
|
|
@@ -953,6 +1078,7 @@ function buildGenuiClause(manifest, allowlist) {
|
|
|
953
1078
|
var import_zod_to_json_schema = require("zod-to-json-schema");
|
|
954
1079
|
|
|
955
1080
|
// src/hooks/alquimia.hook.tsx
|
|
1081
|
+
var TERMINAL_REFUSALS = /* @__PURE__ */ new Set(["already-decided", "wrong-channel", "not-pending", "not-found", "forbidden"]);
|
|
956
1082
|
function useAlquimia(config) {
|
|
957
1083
|
const sdk = (0, import_react2.useMemo)(() => {
|
|
958
1084
|
const instance = new alquimia_sdk_default(config.assistantId, config.adapter, {
|
|
@@ -997,6 +1123,12 @@ function useAlquimia(config) {
|
|
|
997
1123
|
const [isUploadingAttachments, setIsUploadingAttachments] = (0, import_react2.useState)(false);
|
|
998
1124
|
const [pendingClientTool, setPendingClientTool] = (0, import_react2.useState)(null);
|
|
999
1125
|
const completedToolsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Set());
|
|
1126
|
+
const [approvalsState, setApprovalsState] = (0, import_react2.useState)(initialApprovalsState);
|
|
1127
|
+
const approvalsStateRef = (0, import_react2.useRef)(approvalsState);
|
|
1128
|
+
approvalsStateRef.current = approvalsState;
|
|
1129
|
+
const [approvalSubmissions, setApprovalSubmissions] = (0, import_react2.useState)({});
|
|
1130
|
+
const approvalCallsRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
|
|
1131
|
+
const approvalAnchorsRef = (0, import_react2.useRef)({});
|
|
1000
1132
|
const genuiCatalog = config.genui?.catalog ?? coreCatalog;
|
|
1001
1133
|
const [genuiSurfaces, setGenuiSurfaces] = (0, import_react2.useState)([]);
|
|
1002
1134
|
const messagesLenRef = (0, import_react2.useRef)(0);
|
|
@@ -1279,6 +1411,14 @@ function useAlquimia(config) {
|
|
|
1279
1411
|
if (worklogEnabled) {
|
|
1280
1412
|
setWorklog((prev) => reduceWorklog(prev ?? initialWorklogState(), frameToRecord(data)));
|
|
1281
1413
|
}
|
|
1414
|
+
if ((data.event_class === "HumanApprovalRequired" || data.event_class === "HumanApprovalRequiredResponse") && data.control_id) {
|
|
1415
|
+
if (!(data.control_id in approvalAnchorsRef.current)) {
|
|
1416
|
+
approvalAnchorsRef.current[data.control_id] = genuiAnchorRef.current;
|
|
1417
|
+
}
|
|
1418
|
+
const record = frameToRecord(data);
|
|
1419
|
+
setApprovalsState((prev) => reduceApprovals(prev, record));
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1282
1422
|
const pending = extractPendingClientTool(data);
|
|
1283
1423
|
if (pending) {
|
|
1284
1424
|
setPendingClientTool(pending);
|
|
@@ -1327,6 +1467,59 @@ function useAlquimia(config) {
|
|
|
1327
1467
|
},
|
|
1328
1468
|
[sdk]
|
|
1329
1469
|
);
|
|
1470
|
+
const approveTool = (0, import_react2.useCallback)(
|
|
1471
|
+
(controlId, approved, message) => {
|
|
1472
|
+
const existing = approvalCallsRef.current.get(controlId);
|
|
1473
|
+
if (existing) return existing;
|
|
1474
|
+
if (approvalsStateRef.current.byId[controlId]?.decision) {
|
|
1475
|
+
return Promise.resolve({ outcome: "already-decided" });
|
|
1476
|
+
}
|
|
1477
|
+
const reason = message?.trim() || null;
|
|
1478
|
+
setApprovalSubmissions((prev) => ({
|
|
1479
|
+
...prev,
|
|
1480
|
+
[controlId]: { inFlight: true, approved, reason, result: prev[controlId]?.result ?? null }
|
|
1481
|
+
}));
|
|
1482
|
+
const call = sdk.submitToolApproval(controlId, approved, reason ?? void 0).catch((error) => ({
|
|
1483
|
+
outcome: "error",
|
|
1484
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
1485
|
+
})).then((result) => {
|
|
1486
|
+
if (result.outcome !== "accepted") approvalCallsRef.current.delete(controlId);
|
|
1487
|
+
setApprovalSubmissions((prev) => ({
|
|
1488
|
+
...prev,
|
|
1489
|
+
[controlId]: { inFlight: false, approved, reason, result }
|
|
1490
|
+
}));
|
|
1491
|
+
return result;
|
|
1492
|
+
});
|
|
1493
|
+
approvalCallsRef.current.set(controlId, call);
|
|
1494
|
+
return call;
|
|
1495
|
+
},
|
|
1496
|
+
[sdk]
|
|
1497
|
+
);
|
|
1498
|
+
const toolApprovals = (0, import_react2.useMemo)(
|
|
1499
|
+
() => listApprovals(approvalsState).map((gate) => {
|
|
1500
|
+
const sub = approvalSubmissions[gate.controlId];
|
|
1501
|
+
const acceptedHere = sub?.result?.outcome === "accepted";
|
|
1502
|
+
const refused = !!sub?.result && TERMINAL_REFUSALS.has(sub.result.outcome);
|
|
1503
|
+
let status = "pending";
|
|
1504
|
+
if (gate.decision) status = gate.decision.approved ? "approved" : "rejected";
|
|
1505
|
+
else if (sub?.inFlight) status = "submitting";
|
|
1506
|
+
else if (acceptedHere) status = sub.approved ? "approved" : "rejected";
|
|
1507
|
+
return {
|
|
1508
|
+
...gate,
|
|
1509
|
+
status,
|
|
1510
|
+
reason: gate.decision?.reason ?? (acceptedHere && !sub.approved ? sub.reason : null),
|
|
1511
|
+
decidedHere: acceptedHere && (gate.decision ? gate.decision.approved === sub.approved : true),
|
|
1512
|
+
canAnswer: status === "pending" && !gate.channelOwned && !refused,
|
|
1513
|
+
lastResult: sub?.result ?? null,
|
|
1514
|
+
afterCount: approvalAnchorsRef.current[gate.controlId] ?? 0
|
|
1515
|
+
};
|
|
1516
|
+
}),
|
|
1517
|
+
[approvalsState, approvalSubmissions]
|
|
1518
|
+
);
|
|
1519
|
+
const pendingApprovals2 = (0, import_react2.useMemo)(
|
|
1520
|
+
() => toolApprovals.filter((a) => a.status === "pending" || a.status === "submitting"),
|
|
1521
|
+
[toolApprovals]
|
|
1522
|
+
);
|
|
1330
1523
|
(0, import_react2.useEffect)(() => {
|
|
1331
1524
|
if (!config.genui) return;
|
|
1332
1525
|
if (!pendingClientTool || pendingClientTool.toolName !== "render_ui") return;
|
|
@@ -1412,6 +1605,9 @@ function useAlquimia(config) {
|
|
|
1412
1605
|
activeTool,
|
|
1413
1606
|
pendingClientTool,
|
|
1414
1607
|
completeToolExecution,
|
|
1608
|
+
toolApprovals,
|
|
1609
|
+
pendingApprovals: pendingApprovals2,
|
|
1610
|
+
approveTool,
|
|
1415
1611
|
genui,
|
|
1416
1612
|
cleanMessages,
|
|
1417
1613
|
createMessageId,
|