@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.mjs
CHANGED
|
@@ -14,6 +14,9 @@ var AlquimiaSDK = class {
|
|
|
14
14
|
// Registry version tag resolved by the runtime for this task (v0.5.1), so
|
|
15
15
|
// the tool-completion half of the turn lands on the same agentspace version.
|
|
16
16
|
this.versionTag = null;
|
|
17
|
+
// Agentspace the runtime resolved for this task. Tool completion and
|
|
18
|
+
// approval authorize a non-admin caller against it, so it is echoed back.
|
|
19
|
+
this.agentspaceId = null;
|
|
17
20
|
this.tools = [];
|
|
18
21
|
this.extraInstructions = null;
|
|
19
22
|
this.assistantConfig = null;
|
|
@@ -165,6 +168,7 @@ var AlquimiaSDK = class {
|
|
|
165
168
|
this.traceParent = opts.traceParent ?? null;
|
|
166
169
|
this.streamId = result.taskid ?? null;
|
|
167
170
|
this.versionTag = result.versiontag ?? this.versionTag;
|
|
171
|
+
this.agentspaceId = result.agentspaceid ?? null;
|
|
168
172
|
this.attachmentResponses = result.attachments ?? [];
|
|
169
173
|
this.attachments = [];
|
|
170
174
|
return this;
|
|
@@ -184,17 +188,37 @@ var AlquimiaSDK = class {
|
|
|
184
188
|
"AlquimiaSDK: adapter does not support tool completion (resolveToolCompletionUrl missing)"
|
|
185
189
|
);
|
|
186
190
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
191
|
+
await this.axiosInstance.post(url, response, { headers: this.buildDecisionHeaders() });
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Answer a `HumanApprovalRequired` gate: POSTs a {@link ToolApprovalEvent} to
|
|
195
|
+
* the runtime's `/event/tool-approval`. `controlId` is the gate's own control
|
|
196
|
+
* id (the `HumanApprovalRequired` record's), not the parked tool call's.
|
|
197
|
+
*
|
|
198
|
+
* Never throws for a runtime refusal — each 409 means something different to
|
|
199
|
+
* the UI, so the answer comes back classified as a {@link ToolApprovalResult}.
|
|
200
|
+
* It does throw when the adapter has no approval route, which is a wiring bug
|
|
201
|
+
* rather than a runtime answer.
|
|
202
|
+
*/
|
|
203
|
+
async submitToolApproval(controlId, approved, message) {
|
|
204
|
+
const url = this.adapter.resolveToolApprovalUrl?.();
|
|
205
|
+
if (!url) {
|
|
206
|
+
throw new Error(
|
|
207
|
+
"AlquimiaSDK: adapter does not support tool approval (resolveToolApprovalUrl missing)"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
const event = {
|
|
211
|
+
event_class: "ToolApprovalEvent",
|
|
212
|
+
control_id: controlId,
|
|
213
|
+
result: approved,
|
|
214
|
+
...message && { message }
|
|
196
215
|
};
|
|
197
|
-
|
|
216
|
+
try {
|
|
217
|
+
await this.axiosInstance.post(url, event, { headers: this.buildDecisionHeaders() });
|
|
218
|
+
return { outcome: "accepted" };
|
|
219
|
+
} catch (error) {
|
|
220
|
+
return classifyApprovalError(error);
|
|
221
|
+
}
|
|
198
222
|
}
|
|
199
223
|
async generateImage(query) {
|
|
200
224
|
if (!this.stableDiffusionProvider) {
|
|
@@ -265,6 +289,23 @@ var AlquimiaSDK = class {
|
|
|
265
289
|
if (this.streamId) headers["task-id"] = this.streamId;
|
|
266
290
|
return headers;
|
|
267
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Correlation headers for tool-completion and tool-approval: the runtime's
|
|
294
|
+
* `CommonAttributes` header aliases, with no separator. Not interchangeable
|
|
295
|
+
* with the kebab-case set in {@link buildIdentityHeaders}.
|
|
296
|
+
*/
|
|
297
|
+
buildDecisionHeaders() {
|
|
298
|
+
return {
|
|
299
|
+
"Content-Type": "application/json",
|
|
300
|
+
assistantid: this.assistantId,
|
|
301
|
+
...this.streamId && { taskid: this.streamId },
|
|
302
|
+
...this.conversationId && { sessionid: this.conversationId },
|
|
303
|
+
...this.userId && { userid: this.userId },
|
|
304
|
+
...this.agentspaceId && { agentspaceid: this.agentspaceId },
|
|
305
|
+
...this.versionTag && { versiontag: this.versionTag },
|
|
306
|
+
...this.adapter.getHeaders?.() ?? {}
|
|
307
|
+
};
|
|
308
|
+
}
|
|
268
309
|
getUrlStream() {
|
|
269
310
|
if (!this.streamId) {
|
|
270
311
|
throw new Error("Stream ID not initialized. Call sendMessage() first");
|
|
@@ -285,7 +326,29 @@ var AlquimiaSDK = class {
|
|
|
285
326
|
getVersionTag() {
|
|
286
327
|
return this.versionTag;
|
|
287
328
|
}
|
|
329
|
+
/** Agentspace the runtime resolved for the current task, if any. */
|
|
330
|
+
getAgentspaceId() {
|
|
331
|
+
return this.agentspaceId;
|
|
332
|
+
}
|
|
288
333
|
};
|
|
334
|
+
function classifyApprovalError(error) {
|
|
335
|
+
const response = error?.response;
|
|
336
|
+
const status = response?.status;
|
|
337
|
+
const data = response?.data;
|
|
338
|
+
const rawDetail = typeof data === "string" ? data : data?.detail ?? data?.error ?? error?.message;
|
|
339
|
+
const detail = typeof rawDetail === "string" ? rawDetail : JSON.stringify(rawDetail ?? "");
|
|
340
|
+
let outcome = "error";
|
|
341
|
+
if (status === 409) {
|
|
342
|
+
if (/original channel/i.test(detail)) outcome = "wrong-channel";
|
|
343
|
+
else if (/already been recorded/i.test(detail)) outcome = "already-decided";
|
|
344
|
+
else if (/no pending approval/i.test(detail)) outcome = "not-pending";
|
|
345
|
+
} else if (status === 401 || status === 403) {
|
|
346
|
+
outcome = "forbidden";
|
|
347
|
+
} else if (status === 404) {
|
|
348
|
+
outcome = "not-found";
|
|
349
|
+
}
|
|
350
|
+
return { outcome, ...status !== void 0 && { status }, detail };
|
|
351
|
+
}
|
|
289
352
|
var alquimia_sdk_default = AlquimiaSDK;
|
|
290
353
|
|
|
291
354
|
// src/utils/utils.ts
|
|
@@ -389,6 +452,10 @@ var agentTitle = (r) => {
|
|
|
389
452
|
return typeof name === "string" && name.length > 0 ? `Agent: ${name}` : "Agent-to-agent";
|
|
390
453
|
};
|
|
391
454
|
var constTitle = (label) => () => label;
|
|
455
|
+
var approvalTitle = (r) => {
|
|
456
|
+
const name = asRecord(asRecord(r.data).parameters).name;
|
|
457
|
+
return typeof name === "string" && name.length > 0 ? `Approval required: ${name}` : "Human approval required";
|
|
458
|
+
};
|
|
392
459
|
var shieldBlockTitle = (r) => {
|
|
393
460
|
const id = asRecord(r.data).shield_id;
|
|
394
461
|
return typeof id === "string" && id.length > 0 ? `Blocked by ${id}` : "Blocked by safeguard";
|
|
@@ -440,7 +507,7 @@ var EVENT_REGISTRY = {
|
|
|
440
507
|
HumanApprovalRequired: {
|
|
441
508
|
kind: "tool",
|
|
442
509
|
role: "open",
|
|
443
|
-
title:
|
|
510
|
+
title: approvalTitle,
|
|
444
511
|
pending: true
|
|
445
512
|
},
|
|
446
513
|
HumanApprovalRequiredResponse: { kind: "tool", role: "close", title: constTitle("Human approval") }
|
|
@@ -529,7 +596,12 @@ function reduceWorklog(state, record) {
|
|
|
529
596
|
const pos = Object.prototype.hasOwnProperty.call(next.index, key) ? next.index[key] : void 0;
|
|
530
597
|
if (pos != null) {
|
|
531
598
|
const existing = next.nodes[pos];
|
|
532
|
-
next.nodes[pos] = {
|
|
599
|
+
next.nodes[pos] = {
|
|
600
|
+
...existing,
|
|
601
|
+
command: existing.command ?? record.data,
|
|
602
|
+
title: existing.command === void 0 ? interp.title(record) : existing.title,
|
|
603
|
+
raw: [...existing.raw, record]
|
|
604
|
+
};
|
|
533
605
|
} else {
|
|
534
606
|
next.index[key] = next.nodes.length;
|
|
535
607
|
next.nodes.push(makeOpenNode(record, interp, key));
|
|
@@ -554,6 +626,59 @@ function frameToRecord(frame) {
|
|
|
554
626
|
};
|
|
555
627
|
}
|
|
556
628
|
|
|
629
|
+
// src/worklog/approvals.ts
|
|
630
|
+
function initialApprovalsState() {
|
|
631
|
+
return { order: [], byId: {} };
|
|
632
|
+
}
|
|
633
|
+
var asObject = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
|
|
634
|
+
var MCP_APPROVAL_EXTENSION = "mcp_approval";
|
|
635
|
+
function describeRequest(record) {
|
|
636
|
+
const execution = asObject(asObject(record.data)?.parameters) ?? {};
|
|
637
|
+
const name = execution.name;
|
|
638
|
+
const callId = execution.control_id;
|
|
639
|
+
const args = execution.tool_call_parameters !== void 0 && execution.tool_call_parameters !== null ? execution.tool_call_parameters : execution.parameters;
|
|
640
|
+
const extensions = asObject(execution.parameters)?.runtime_extensions;
|
|
641
|
+
return {
|
|
642
|
+
toolName: typeof name === "string" && name.length > 0 ? name : null,
|
|
643
|
+
toolCallId: typeof callId === "string" ? callId : null,
|
|
644
|
+
arguments: args ?? null,
|
|
645
|
+
channelOwned: Array.isArray(extensions) && extensions.includes(MCP_APPROVAL_EXTENSION)
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
function describeDecision(record) {
|
|
649
|
+
const approved = record.data === true;
|
|
650
|
+
return {
|
|
651
|
+
approved,
|
|
652
|
+
reason: !approved && typeof record.data === "string" && record.data.length > 0 ? record.data : null,
|
|
653
|
+
decidedAt: record.date_submitted
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
var blank = (controlId) => ({
|
|
657
|
+
controlId,
|
|
658
|
+
toolName: null,
|
|
659
|
+
toolCallId: null,
|
|
660
|
+
arguments: null,
|
|
661
|
+
channelOwned: false,
|
|
662
|
+
requestedAt: null,
|
|
663
|
+
decision: null
|
|
664
|
+
});
|
|
665
|
+
function reduceApprovals(state, record) {
|
|
666
|
+
const isRequest = record.event_class === "HumanApprovalRequired";
|
|
667
|
+
const isResponse = record.event_class === "HumanApprovalRequiredResponse";
|
|
668
|
+
if (!isRequest && !isResponse || !record.control_id) return state;
|
|
669
|
+
const id = record.control_id;
|
|
670
|
+
const known = state.byId[id];
|
|
671
|
+
const current = known ?? blank(id);
|
|
672
|
+
const next = isRequest ? { ...current, ...describeRequest(record), requestedAt: record.date_submitted } : { ...current, decision: current.decision ?? describeDecision(record) };
|
|
673
|
+
return {
|
|
674
|
+
order: known ? state.order : [...state.order, id],
|
|
675
|
+
byId: { ...state.byId, [id]: next }
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
function listApprovals(state) {
|
|
679
|
+
return state.order.map((id) => state.byId[id]);
|
|
680
|
+
}
|
|
681
|
+
|
|
557
682
|
// src/hooks/pending-client-tool.ts
|
|
558
683
|
function extractPendingClientTool(frame) {
|
|
559
684
|
if (!frame || typeof frame !== "object") return null;
|
|
@@ -916,6 +1041,7 @@ function buildGenuiClause(manifest, allowlist) {
|
|
|
916
1041
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
917
1042
|
|
|
918
1043
|
// src/hooks/alquimia.hook.tsx
|
|
1044
|
+
var TERMINAL_REFUSALS = /* @__PURE__ */ new Set(["already-decided", "wrong-channel", "not-pending", "not-found", "forbidden"]);
|
|
919
1045
|
function useAlquimia(config) {
|
|
920
1046
|
const sdk = useMemo(() => {
|
|
921
1047
|
const instance = new alquimia_sdk_default(config.assistantId, config.adapter, {
|
|
@@ -960,6 +1086,12 @@ function useAlquimia(config) {
|
|
|
960
1086
|
const [isUploadingAttachments, setIsUploadingAttachments] = useState(false);
|
|
961
1087
|
const [pendingClientTool, setPendingClientTool] = useState(null);
|
|
962
1088
|
const completedToolsRef = useRef(/* @__PURE__ */ new Set());
|
|
1089
|
+
const [approvalsState, setApprovalsState] = useState(initialApprovalsState);
|
|
1090
|
+
const approvalsStateRef = useRef(approvalsState);
|
|
1091
|
+
approvalsStateRef.current = approvalsState;
|
|
1092
|
+
const [approvalSubmissions, setApprovalSubmissions] = useState({});
|
|
1093
|
+
const approvalCallsRef = useRef(/* @__PURE__ */ new Map());
|
|
1094
|
+
const approvalAnchorsRef = useRef({});
|
|
963
1095
|
const genuiCatalog = config.genui?.catalog ?? coreCatalog;
|
|
964
1096
|
const [genuiSurfaces, setGenuiSurfaces] = useState([]);
|
|
965
1097
|
const messagesLenRef = useRef(0);
|
|
@@ -1242,6 +1374,14 @@ function useAlquimia(config) {
|
|
|
1242
1374
|
if (worklogEnabled) {
|
|
1243
1375
|
setWorklog((prev) => reduceWorklog(prev ?? initialWorklogState(), frameToRecord(data)));
|
|
1244
1376
|
}
|
|
1377
|
+
if ((data.event_class === "HumanApprovalRequired" || data.event_class === "HumanApprovalRequiredResponse") && data.control_id) {
|
|
1378
|
+
if (!(data.control_id in approvalAnchorsRef.current)) {
|
|
1379
|
+
approvalAnchorsRef.current[data.control_id] = genuiAnchorRef.current;
|
|
1380
|
+
}
|
|
1381
|
+
const record = frameToRecord(data);
|
|
1382
|
+
setApprovalsState((prev) => reduceApprovals(prev, record));
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1245
1385
|
const pending = extractPendingClientTool(data);
|
|
1246
1386
|
if (pending) {
|
|
1247
1387
|
setPendingClientTool(pending);
|
|
@@ -1290,6 +1430,59 @@ function useAlquimia(config) {
|
|
|
1290
1430
|
},
|
|
1291
1431
|
[sdk]
|
|
1292
1432
|
);
|
|
1433
|
+
const approveTool = useCallback2(
|
|
1434
|
+
(controlId, approved, message) => {
|
|
1435
|
+
const existing = approvalCallsRef.current.get(controlId);
|
|
1436
|
+
if (existing) return existing;
|
|
1437
|
+
if (approvalsStateRef.current.byId[controlId]?.decision) {
|
|
1438
|
+
return Promise.resolve({ outcome: "already-decided" });
|
|
1439
|
+
}
|
|
1440
|
+
const reason = message?.trim() || null;
|
|
1441
|
+
setApprovalSubmissions((prev) => ({
|
|
1442
|
+
...prev,
|
|
1443
|
+
[controlId]: { inFlight: true, approved, reason, result: prev[controlId]?.result ?? null }
|
|
1444
|
+
}));
|
|
1445
|
+
const call = sdk.submitToolApproval(controlId, approved, reason ?? void 0).catch((error) => ({
|
|
1446
|
+
outcome: "error",
|
|
1447
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
1448
|
+
})).then((result) => {
|
|
1449
|
+
if (result.outcome !== "accepted") approvalCallsRef.current.delete(controlId);
|
|
1450
|
+
setApprovalSubmissions((prev) => ({
|
|
1451
|
+
...prev,
|
|
1452
|
+
[controlId]: { inFlight: false, approved, reason, result }
|
|
1453
|
+
}));
|
|
1454
|
+
return result;
|
|
1455
|
+
});
|
|
1456
|
+
approvalCallsRef.current.set(controlId, call);
|
|
1457
|
+
return call;
|
|
1458
|
+
},
|
|
1459
|
+
[sdk]
|
|
1460
|
+
);
|
|
1461
|
+
const toolApprovals = useMemo(
|
|
1462
|
+
() => listApprovals(approvalsState).map((gate) => {
|
|
1463
|
+
const sub = approvalSubmissions[gate.controlId];
|
|
1464
|
+
const acceptedHere = sub?.result?.outcome === "accepted";
|
|
1465
|
+
const refused = !!sub?.result && TERMINAL_REFUSALS.has(sub.result.outcome);
|
|
1466
|
+
let status = "pending";
|
|
1467
|
+
if (gate.decision) status = gate.decision.approved ? "approved" : "rejected";
|
|
1468
|
+
else if (sub?.inFlight) status = "submitting";
|
|
1469
|
+
else if (acceptedHere) status = sub.approved ? "approved" : "rejected";
|
|
1470
|
+
return {
|
|
1471
|
+
...gate,
|
|
1472
|
+
status,
|
|
1473
|
+
reason: gate.decision?.reason ?? (acceptedHere && !sub.approved ? sub.reason : null),
|
|
1474
|
+
decidedHere: acceptedHere && (gate.decision ? gate.decision.approved === sub.approved : true),
|
|
1475
|
+
canAnswer: status === "pending" && !gate.channelOwned && !refused,
|
|
1476
|
+
lastResult: sub?.result ?? null,
|
|
1477
|
+
afterCount: approvalAnchorsRef.current[gate.controlId] ?? 0
|
|
1478
|
+
};
|
|
1479
|
+
}),
|
|
1480
|
+
[approvalsState, approvalSubmissions]
|
|
1481
|
+
);
|
|
1482
|
+
const pendingApprovals2 = useMemo(
|
|
1483
|
+
() => toolApprovals.filter((a) => a.status === "pending" || a.status === "submitting"),
|
|
1484
|
+
[toolApprovals]
|
|
1485
|
+
);
|
|
1293
1486
|
useEffect(() => {
|
|
1294
1487
|
if (!config.genui) return;
|
|
1295
1488
|
if (!pendingClientTool || pendingClientTool.toolName !== "render_ui") return;
|
|
@@ -1375,6 +1568,9 @@ function useAlquimia(config) {
|
|
|
1375
1568
|
activeTool,
|
|
1376
1569
|
pendingClientTool,
|
|
1377
1570
|
completeToolExecution,
|
|
1571
|
+
toolApprovals,
|
|
1572
|
+
pendingApprovals: pendingApprovals2,
|
|
1573
|
+
approveTool,
|
|
1378
1574
|
genui,
|
|
1379
1575
|
cleanMessages,
|
|
1380
1576
|
createMessageId,
|