@cadraos/sdk 0.3.14-p46.4 → 0.3.14
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 +104 -53
- package/dist/ai-functions/react/index.cjs +176 -0
- package/dist/ai-functions/react/index.cjs.map +1 -0
- package/dist/ai-functions/react/index.d.cts +129 -0
- package/dist/ai-functions/react/index.d.ts +129 -0
- package/dist/ai-functions/react/index.js +153 -0
- package/dist/ai-functions/react/index.js.map +1 -0
- package/dist/ai-functions/server/index.cjs +296 -0
- package/dist/ai-functions/server/index.cjs.map +1 -0
- package/dist/ai-functions/server/index.d.cts +334 -0
- package/dist/ai-functions/server/index.d.ts +334 -0
- package/dist/ai-functions/server/index.js +284 -0
- package/dist/ai-functions/server/index.js.map +1 -0
- package/dist/chat/index.cjs +299 -17
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +208 -0
- package/dist/chat/index.d.ts +208 -0
- package/dist/chat/index.js +299 -17
- package/dist/chat/index.js.map +1 -1
- package/dist/index.cjs +221 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +269 -1
- package/dist/index.d.ts +269 -1
- package/dist/index.js +215 -4
- package/dist/index.js.map +1 -1
- package/dist/kanban/index.cjs +214 -3
- package/dist/kanban/index.cjs.map +1 -1
- package/dist/kanban/index.d.cts +208 -0
- package/dist/kanban/index.d.ts +208 -0
- package/dist/kanban/index.js +214 -3
- package/dist/kanban/index.js.map +1 -1
- package/dist/teams/index.cjs +214 -3
- package/dist/teams/index.cjs.map +1 -1
- package/dist/teams/index.d.cts +208 -0
- package/dist/teams/index.d.ts +208 -0
- package/dist/teams/index.js +214 -3
- package/dist/teams/index.js.map +1 -1
- package/package.json +21 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI function types (p93 — specs §4 run contract, D27 discovery, §6.5 run reads).
|
|
5
|
+
*
|
|
6
|
+
* An AI function is a published prompt in the Cadra Prompts library that makes
|
|
7
|
+
* ONE direct AI-provider call (never an agent). Callers reference it by its
|
|
8
|
+
* `functionKey` (unique per org), by prompt uuid, or — for platform features —
|
|
9
|
+
* by slot key.
|
|
10
|
+
*/
|
|
11
|
+
/** Output kind of an AI function. */
|
|
12
|
+
type AiFunctionOutputKind = 'text' | 'json' | 'image';
|
|
13
|
+
/** Parameter (prompt variable) types — specs D10 + D26d. */
|
|
14
|
+
type AiFunctionParameterType = 'string' | 'text' | 'number' | 'boolean' | 'enum' | 'image';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Host-app AI function slots — shared types (p93 AIF-017, specs D28).
|
|
18
|
+
*
|
|
19
|
+
* Type-only module: imported by both `@cadraos/sdk/ai-functions/server` and
|
|
20
|
+
* `@cadraos/sdk/ai-functions/react` (the panel renders the rows the server
|
|
21
|
+
* handlers return), so it must stay free of runtime code and of React.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** One parameter a host slot passes when it calls its function. */
|
|
25
|
+
interface AiFunctionSlotParameter {
|
|
26
|
+
name: string;
|
|
27
|
+
type: AiFunctionParameterType;
|
|
28
|
+
required: boolean;
|
|
29
|
+
maxLength?: number;
|
|
30
|
+
options?: string[];
|
|
31
|
+
}
|
|
32
|
+
/** What a slot is bound to: a function of the host's Cadra org. */
|
|
33
|
+
interface AiFunctionSlotBinding {
|
|
34
|
+
functionKey: string;
|
|
35
|
+
}
|
|
36
|
+
/** Why a function does not fit a slot (D16 rule applied to a D27 descriptor). */
|
|
37
|
+
type AiFunctionSlotCompatIssueCode = 'function_unavailable' | 'output_kind' | 'parameter_missing' | 'parameter_type' | 'parameter_required' | 'options' | 'max_length';
|
|
38
|
+
interface AiFunctionSlotCompatIssue {
|
|
39
|
+
code: AiFunctionSlotCompatIssueCode;
|
|
40
|
+
/** The parameter concerned, when there is one. */
|
|
41
|
+
parameter?: string;
|
|
42
|
+
message: string;
|
|
43
|
+
}
|
|
44
|
+
interface AiFunctionSlotCompatVerdict {
|
|
45
|
+
compatible: boolean;
|
|
46
|
+
issues: AiFunctionSlotCompatIssue[];
|
|
47
|
+
}
|
|
48
|
+
/** A function offered in a slot's picker. */
|
|
49
|
+
interface AiFunctionSlotOption {
|
|
50
|
+
functionKey: string;
|
|
51
|
+
title: string;
|
|
52
|
+
liveVersion: number;
|
|
53
|
+
}
|
|
54
|
+
/** One row of `listSlots()` — catalog + binding + live descriptor + verdict. */
|
|
55
|
+
interface AiFunctionSlotRow {
|
|
56
|
+
key: string;
|
|
57
|
+
label: string;
|
|
58
|
+
usedFor: string | null;
|
|
59
|
+
outputKind: AiFunctionOutputKind;
|
|
60
|
+
parameters: AiFunctionSlotParameter[];
|
|
61
|
+
/** The stored binding, or null when the slot is unset. */
|
|
62
|
+
binding: AiFunctionSlotBinding | null;
|
|
63
|
+
/** The bound function's live descriptor summary; null when unset or unavailable. */
|
|
64
|
+
boundFunction: (AiFunctionSlotOption & {
|
|
65
|
+
outputKind: AiFunctionOutputKind;
|
|
66
|
+
}) | null;
|
|
67
|
+
/** Compatibility of the bound function; null when the slot is unset. */
|
|
68
|
+
compat: AiFunctionSlotCompatVerdict | null;
|
|
69
|
+
/** Functions compatible with this slot (the picker's choices). */
|
|
70
|
+
options: AiFunctionSlotOption[];
|
|
71
|
+
}
|
|
72
|
+
/** Result of a successful `setSlot`. */
|
|
73
|
+
interface AiFunctionSetSlotResult {
|
|
74
|
+
key: string;
|
|
75
|
+
functionKey: string;
|
|
76
|
+
liveVersion: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* <AiFunctionSlotsPanel/> — admin table for a host app's AI function slots
|
|
81
|
+
* (p93 AIF-017, specs D28).
|
|
82
|
+
*
|
|
83
|
+
* Table: Slot | Used for | Bound to (select of compatible functions + live
|
|
84
|
+
* version) | Reset. Zero-dependency and unstyled: every element takes a class
|
|
85
|
+
* from `classNames` so the host styles it with its own tokens. No toasts — a
|
|
86
|
+
* failed load shows an error banner (never an empty table) and a failed write
|
|
87
|
+
* shows inline on its row. All visible text can be replaced via `labels`.
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
interface AiFunctionSlotsPanelApi {
|
|
91
|
+
listSlots(): Promise<AiFunctionSlotRow[]>;
|
|
92
|
+
setSlot(input: {
|
|
93
|
+
key: string;
|
|
94
|
+
functionKey: string;
|
|
95
|
+
}): Promise<AiFunctionSetSlotResult | unknown>;
|
|
96
|
+
resetSlot(input: {
|
|
97
|
+
key: string;
|
|
98
|
+
}): Promise<unknown>;
|
|
99
|
+
}
|
|
100
|
+
interface AiFunctionSlotsPanelLabels {
|
|
101
|
+
slot: string;
|
|
102
|
+
usedFor: string;
|
|
103
|
+
boundTo: string;
|
|
104
|
+
actions: string;
|
|
105
|
+
notSet: string;
|
|
106
|
+
reset: string;
|
|
107
|
+
retry: string;
|
|
108
|
+
loading: string;
|
|
109
|
+
empty: string;
|
|
110
|
+
loadFailed: string;
|
|
111
|
+
saveFailed: string;
|
|
112
|
+
incompatible: string;
|
|
113
|
+
unavailable: string;
|
|
114
|
+
version: (n: number) => string;
|
|
115
|
+
}
|
|
116
|
+
declare const DEFAULT_AI_FUNCTION_SLOTS_PANEL_LABELS: AiFunctionSlotsPanelLabels;
|
|
117
|
+
type AiFunctionSlotsPanelPart = 'root' | 'banner' | 'table' | 'headerRow' | 'headerCell' | 'row' | 'cell' | 'slotLabel' | 'slotKey' | 'select' | 'meta' | 'issues' | 'rowError' | 'button';
|
|
118
|
+
interface AiFunctionSlotsPanelProps {
|
|
119
|
+
api: AiFunctionSlotsPanelApi;
|
|
120
|
+
/** Class on the root element. */
|
|
121
|
+
className?: string;
|
|
122
|
+
/** Classes per part, for host styling. */
|
|
123
|
+
classNames?: Partial<Record<AiFunctionSlotsPanelPart, string>>;
|
|
124
|
+
/** Replace any visible text. */
|
|
125
|
+
labels?: Partial<AiFunctionSlotsPanelLabels>;
|
|
126
|
+
}
|
|
127
|
+
declare function AiFunctionSlotsPanel(props: AiFunctionSlotsPanelProps): React.ReactElement;
|
|
128
|
+
|
|
129
|
+
export { type AiFunctionSetSlotResult, type AiFunctionSlotCompatVerdict, type AiFunctionSlotOption, type AiFunctionSlotRow, AiFunctionSlotsPanel, type AiFunctionSlotsPanelApi, type AiFunctionSlotsPanelLabels, type AiFunctionSlotsPanelPart, type AiFunctionSlotsPanelProps, DEFAULT_AI_FUNCTION_SLOTS_PANEL_LABELS };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
var DEFAULT_AI_FUNCTION_SLOTS_PANEL_LABELS = {
|
|
5
|
+
slot: "Slot",
|
|
6
|
+
usedFor: "Used for",
|
|
7
|
+
boundTo: "Bound to",
|
|
8
|
+
actions: "Actions",
|
|
9
|
+
notSet: "Not set",
|
|
10
|
+
reset: "Reset",
|
|
11
|
+
retry: "Retry",
|
|
12
|
+
loading: "Loading\u2026",
|
|
13
|
+
empty: "No AI function slots are declared.",
|
|
14
|
+
loadFailed: "Could not load AI function slots",
|
|
15
|
+
saveFailed: "Could not save",
|
|
16
|
+
incompatible: "Incompatible",
|
|
17
|
+
unavailable: "unavailable",
|
|
18
|
+
version: (n) => `v${n}`
|
|
19
|
+
};
|
|
20
|
+
function messageOf(error) {
|
|
21
|
+
if (error && typeof error === "object" && typeof error.message === "string") {
|
|
22
|
+
return error.message;
|
|
23
|
+
}
|
|
24
|
+
return typeof error === "string" ? error : "";
|
|
25
|
+
}
|
|
26
|
+
function AiFunctionSlotsPanel(props) {
|
|
27
|
+
const { api, className, classNames = {} } = props;
|
|
28
|
+
const L = { ...DEFAULT_AI_FUNCTION_SLOTS_PANEL_LABELS, ...props.labels };
|
|
29
|
+
const [state, setState] = React.useState({ status: "loading" });
|
|
30
|
+
const [pendingKey, setPendingKey] = React.useState(null);
|
|
31
|
+
const [rowErrors, setRowErrors] = React.useState({});
|
|
32
|
+
const mounted = React.useRef(true);
|
|
33
|
+
const load = React.useCallback(async () => {
|
|
34
|
+
try {
|
|
35
|
+
const rows2 = await api.listSlots();
|
|
36
|
+
if (mounted.current) setState({ status: "ready", rows: rows2 });
|
|
37
|
+
} catch (error) {
|
|
38
|
+
if (mounted.current) setState({ status: "error", message: messageOf(error) });
|
|
39
|
+
}
|
|
40
|
+
}, [api]);
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
mounted.current = true;
|
|
43
|
+
void load();
|
|
44
|
+
return () => {
|
|
45
|
+
mounted.current = false;
|
|
46
|
+
};
|
|
47
|
+
}, [load]);
|
|
48
|
+
const mutate = async (key, action) => {
|
|
49
|
+
setPendingKey(key);
|
|
50
|
+
setRowErrors((e) => {
|
|
51
|
+
const next = { ...e };
|
|
52
|
+
delete next[key];
|
|
53
|
+
return next;
|
|
54
|
+
});
|
|
55
|
+
try {
|
|
56
|
+
await action();
|
|
57
|
+
await load();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (mounted.current) {
|
|
60
|
+
const msg = messageOf(error);
|
|
61
|
+
setRowErrors((e) => ({ ...e, [key]: msg ? `${L.saveFailed}: ${msg}` : L.saveFailed }));
|
|
62
|
+
}
|
|
63
|
+
} finally {
|
|
64
|
+
if (mounted.current) setPendingKey(null);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const rootClass = [classNames.root, className].filter(Boolean).join(" ") || void 0;
|
|
68
|
+
if (state.status === "loading") {
|
|
69
|
+
return /* @__PURE__ */ jsx("div", { className: rootClass, "aria-busy": "true", "data-state": "loading", children: L.loading });
|
|
70
|
+
}
|
|
71
|
+
if (state.status === "error") {
|
|
72
|
+
return /* @__PURE__ */ jsx("div", { className: rootClass, "data-state": "error", children: /* @__PURE__ */ jsxs("div", { role: "alert", className: classNames.banner, children: [
|
|
73
|
+
/* @__PURE__ */ jsx("span", { children: state.message ? `${L.loadFailed}: ${state.message}` : L.loadFailed }),
|
|
74
|
+
" ",
|
|
75
|
+
/* @__PURE__ */ jsx(
|
|
76
|
+
"button",
|
|
77
|
+
{
|
|
78
|
+
type: "button",
|
|
79
|
+
className: classNames.button,
|
|
80
|
+
onClick: () => {
|
|
81
|
+
setState({ status: "loading" });
|
|
82
|
+
void load();
|
|
83
|
+
},
|
|
84
|
+
children: L.retry
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
] }) });
|
|
88
|
+
}
|
|
89
|
+
const { rows } = state;
|
|
90
|
+
if (rows.length === 0) {
|
|
91
|
+
return /* @__PURE__ */ jsx("div", { className: rootClass, "data-state": "empty", children: L.empty });
|
|
92
|
+
}
|
|
93
|
+
return /* @__PURE__ */ jsx("div", { className: rootClass, "data-state": "ready", children: /* @__PURE__ */ jsxs("table", { className: classNames.table, children: [
|
|
94
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: classNames.headerRow, children: [
|
|
95
|
+
/* @__PURE__ */ jsx("th", { scope: "col", className: classNames.headerCell, children: L.slot }),
|
|
96
|
+
/* @__PURE__ */ jsx("th", { scope: "col", className: classNames.headerCell, children: L.usedFor }),
|
|
97
|
+
/* @__PURE__ */ jsx("th", { scope: "col", className: classNames.headerCell, children: L.boundTo }),
|
|
98
|
+
/* @__PURE__ */ jsx("th", { scope: "col", className: classNames.headerCell, children: L.actions })
|
|
99
|
+
] }) }),
|
|
100
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((row) => {
|
|
101
|
+
const bound = row.binding?.functionKey ?? "";
|
|
102
|
+
const boundIsOption = row.options.some((o) => o.functionKey === bound);
|
|
103
|
+
const busy = pendingKey === row.key;
|
|
104
|
+
const incompatible = row.compat && !row.compat.compatible;
|
|
105
|
+
return /* @__PURE__ */ jsxs("tr", { className: classNames.row, "data-slot": row.key, children: [
|
|
106
|
+
/* @__PURE__ */ jsxs("td", { className: classNames.cell, children: [
|
|
107
|
+
/* @__PURE__ */ jsx("div", { className: classNames.slotLabel, children: row.label }),
|
|
108
|
+
/* @__PURE__ */ jsx("div", { className: classNames.slotKey, children: row.key })
|
|
109
|
+
] }),
|
|
110
|
+
/* @__PURE__ */ jsx("td", { className: classNames.cell, children: row.usedFor ?? "" }),
|
|
111
|
+
/* @__PURE__ */ jsxs("td", { className: classNames.cell, children: [
|
|
112
|
+
/* @__PURE__ */ jsxs(
|
|
113
|
+
"select",
|
|
114
|
+
{
|
|
115
|
+
"aria-label": `${L.boundTo}: ${row.label}`,
|
|
116
|
+
className: classNames.select,
|
|
117
|
+
value: bound,
|
|
118
|
+
disabled: busy,
|
|
119
|
+
onChange: (e) => {
|
|
120
|
+
const functionKey = e.target.value;
|
|
121
|
+
if (functionKey && functionKey !== bound) {
|
|
122
|
+
void mutate(row.key, () => api.setSlot({ key: row.key, functionKey }));
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
children: [
|
|
126
|
+
/* @__PURE__ */ jsx("option", { value: "", disabled: Boolean(bound), children: L.notSet }),
|
|
127
|
+
bound && !boundIsOption ? /* @__PURE__ */ jsx("option", { value: bound, disabled: true, children: `${row.boundFunction?.title ?? bound} (${incompatible ? L.incompatible : L.unavailable})` }) : null,
|
|
128
|
+
row.options.map((o) => /* @__PURE__ */ jsx("option", { value: o.functionKey, children: `${o.title} \xB7 ${L.version(o.liveVersion)}` }, o.functionKey))
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
),
|
|
132
|
+
row.boundFunction ? /* @__PURE__ */ jsx("div", { className: classNames.meta, children: `${row.boundFunction.functionKey} \xB7 ${L.version(row.boundFunction.liveVersion)}` }) : null,
|
|
133
|
+
incompatible ? /* @__PURE__ */ jsx("ul", { role: "alert", className: classNames.issues, "aria-label": L.incompatible, children: row.compat.issues.map((issue, i) => /* @__PURE__ */ jsx("li", { children: issue.message }, i)) }) : null,
|
|
134
|
+
rowErrors[row.key] ? /* @__PURE__ */ jsx("div", { role: "alert", className: classNames.rowError, children: rowErrors[row.key] }) : null
|
|
135
|
+
] }),
|
|
136
|
+
/* @__PURE__ */ jsx("td", { className: classNames.cell, children: /* @__PURE__ */ jsx(
|
|
137
|
+
"button",
|
|
138
|
+
{
|
|
139
|
+
type: "button",
|
|
140
|
+
className: classNames.button,
|
|
141
|
+
disabled: busy || !row.binding,
|
|
142
|
+
onClick: () => void mutate(row.key, () => api.resetSlot({ key: row.key })),
|
|
143
|
+
children: L.reset
|
|
144
|
+
}
|
|
145
|
+
) })
|
|
146
|
+
] }, row.key);
|
|
147
|
+
}) })
|
|
148
|
+
] }) });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export { AiFunctionSlotsPanel, DEFAULT_AI_FUNCTION_SLOTS_PANEL_LABELS };
|
|
152
|
+
//# sourceMappingURL=index.js.map
|
|
153
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ai-functions/react/AiFunctionSlotsPanel.tsx"],"names":["rows"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * <AiFunctionSlotsPanel/> — admin table for a host app's AI function slots\n * (p93 AIF-017, specs D28).\n *\n * Table: Slot | Used for | Bound to (select of compatible functions + live\n * version) | Reset. Zero-dependency and unstyled: every element takes a class\n * from `classNames` so the host styles it with its own tokens. No toasts — a\n * failed load shows an error banner (never an empty table) and a failed write\n * shows inline on its row. All visible text can be replaced via `labels`.\n */\n\nimport * as React from 'react';\nimport type { AiFunctionSetSlotResult, AiFunctionSlotRow } from '../server/types.js';\n\nexport interface AiFunctionSlotsPanelApi {\n listSlots(): Promise<AiFunctionSlotRow[]>;\n setSlot(input: { key: string; functionKey: string }): Promise<AiFunctionSetSlotResult | unknown>;\n resetSlot(input: { key: string }): Promise<unknown>;\n}\n\nexport interface AiFunctionSlotsPanelLabels {\n slot: string;\n usedFor: string;\n boundTo: string;\n actions: string;\n notSet: string;\n reset: string;\n retry: string;\n loading: string;\n empty: string;\n loadFailed: string;\n saveFailed: string;\n incompatible: string;\n unavailable: string;\n version: (n: number) => string;\n}\n\nexport const DEFAULT_AI_FUNCTION_SLOTS_PANEL_LABELS: AiFunctionSlotsPanelLabels = {\n slot: 'Slot',\n usedFor: 'Used for',\n boundTo: 'Bound to',\n actions: 'Actions',\n notSet: 'Not set',\n reset: 'Reset',\n retry: 'Retry',\n loading: 'Loading…',\n empty: 'No AI function slots are declared.',\n loadFailed: 'Could not load AI function slots',\n saveFailed: 'Could not save',\n incompatible: 'Incompatible',\n unavailable: 'unavailable',\n version: (n) => `v${n}`,\n};\n\nexport type AiFunctionSlotsPanelPart =\n | 'root'\n | 'banner'\n | 'table'\n | 'headerRow'\n | 'headerCell'\n | 'row'\n | 'cell'\n | 'slotLabel'\n | 'slotKey'\n | 'select'\n | 'meta'\n | 'issues'\n | 'rowError'\n | 'button';\n\nexport interface AiFunctionSlotsPanelProps {\n api: AiFunctionSlotsPanelApi;\n /** Class on the root element. */\n className?: string;\n /** Classes per part, for host styling. */\n classNames?: Partial<Record<AiFunctionSlotsPanelPart, string>>;\n /** Replace any visible text. */\n labels?: Partial<AiFunctionSlotsPanelLabels>;\n}\n\ntype LoadState =\n | { status: 'loading' }\n | { status: 'ready'; rows: AiFunctionSlotRow[] }\n | { status: 'error'; message: string };\n\nfunction messageOf(error: unknown): string {\n if (error && typeof error === 'object' && typeof (error as { message?: unknown }).message === 'string') {\n return (error as { message: string }).message;\n }\n return typeof error === 'string' ? error : '';\n}\n\nexport function AiFunctionSlotsPanel(props: AiFunctionSlotsPanelProps): React.ReactElement {\n const { api, className, classNames = {} } = props;\n const L: AiFunctionSlotsPanelLabels = { ...DEFAULT_AI_FUNCTION_SLOTS_PANEL_LABELS, ...props.labels };\n\n const [state, setState] = React.useState<LoadState>({ status: 'loading' });\n const [pendingKey, setPendingKey] = React.useState<string | null>(null);\n const [rowErrors, setRowErrors] = React.useState<Record<string, string>>({});\n const mounted = React.useRef(true);\n\n const load = React.useCallback(async () => {\n try {\n const rows = await api.listSlots();\n if (mounted.current) setState({ status: 'ready', rows });\n } catch (error) {\n if (mounted.current) setState({ status: 'error', message: messageOf(error) });\n }\n }, [api]);\n\n React.useEffect(() => {\n mounted.current = true;\n void load();\n return () => {\n mounted.current = false;\n };\n }, [load]);\n\n const mutate = async (key: string, action: () => Promise<unknown>) => {\n setPendingKey(key);\n setRowErrors((e) => {\n const next = { ...e };\n delete next[key];\n return next;\n });\n try {\n await action();\n await load();\n } catch (error) {\n if (mounted.current) {\n const msg = messageOf(error);\n setRowErrors((e) => ({ ...e, [key]: msg ? `${L.saveFailed}: ${msg}` : L.saveFailed }));\n }\n } finally {\n if (mounted.current) setPendingKey(null);\n }\n };\n\n const rootClass = [classNames.root, className].filter(Boolean).join(' ') || undefined;\n\n if (state.status === 'loading') {\n return (\n <div className={rootClass} aria-busy=\"true\" data-state=\"loading\">\n {L.loading}\n </div>\n );\n }\n\n if (state.status === 'error') {\n return (\n <div className={rootClass} data-state=\"error\">\n <div role=\"alert\" className={classNames.banner}>\n <span>{state.message ? `${L.loadFailed}: ${state.message}` : L.loadFailed}</span>{' '}\n <button\n type=\"button\"\n className={classNames.button}\n onClick={() => {\n setState({ status: 'loading' });\n void load();\n }}\n >\n {L.retry}\n </button>\n </div>\n </div>\n );\n }\n\n const { rows } = state;\n if (rows.length === 0) {\n return (\n <div className={rootClass} data-state=\"empty\">\n {L.empty}\n </div>\n );\n }\n\n return (\n <div className={rootClass} data-state=\"ready\">\n <table className={classNames.table}>\n <thead>\n <tr className={classNames.headerRow}>\n <th scope=\"col\" className={classNames.headerCell}>{L.slot}</th>\n <th scope=\"col\" className={classNames.headerCell}>{L.usedFor}</th>\n <th scope=\"col\" className={classNames.headerCell}>{L.boundTo}</th>\n <th scope=\"col\" className={classNames.headerCell}>{L.actions}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => {\n const bound = row.binding?.functionKey ?? '';\n const boundIsOption = row.options.some((o) => o.functionKey === bound);\n const busy = pendingKey === row.key;\n const incompatible = row.compat && !row.compat.compatible;\n return (\n <tr key={row.key} className={classNames.row} data-slot={row.key}>\n <td className={classNames.cell}>\n <div className={classNames.slotLabel}>{row.label}</div>\n <div className={classNames.slotKey}>{row.key}</div>\n </td>\n <td className={classNames.cell}>{row.usedFor ?? ''}</td>\n <td className={classNames.cell}>\n <select\n aria-label={`${L.boundTo}: ${row.label}`}\n className={classNames.select}\n value={bound}\n disabled={busy}\n onChange={(e) => {\n const functionKey = e.target.value;\n if (functionKey && functionKey !== bound) {\n void mutate(row.key, () => api.setSlot({ key: row.key, functionKey }));\n }\n }}\n >\n <option value=\"\" disabled={Boolean(bound)}>\n {L.notSet}\n </option>\n {bound && !boundIsOption ? (\n <option value={bound} disabled>\n {`${row.boundFunction?.title ?? bound} (${incompatible ? L.incompatible : L.unavailable})`}\n </option>\n ) : null}\n {row.options.map((o) => (\n <option key={o.functionKey} value={o.functionKey}>\n {`${o.title} · ${L.version(o.liveVersion)}`}\n </option>\n ))}\n </select>\n {row.boundFunction ? (\n <div className={classNames.meta}>\n {`${row.boundFunction.functionKey} · ${L.version(row.boundFunction.liveVersion)}`}\n </div>\n ) : null}\n {incompatible ? (\n <ul role=\"alert\" className={classNames.issues} aria-label={L.incompatible}>\n {row.compat!.issues.map((issue, i) => (\n <li key={i}>{issue.message}</li>\n ))}\n </ul>\n ) : null}\n {rowErrors[row.key] ? (\n <div role=\"alert\" className={classNames.rowError}>\n {rowErrors[row.key]}\n </div>\n ) : null}\n </td>\n <td className={classNames.cell}>\n <button\n type=\"button\"\n className={classNames.button}\n disabled={busy || !row.binding}\n onClick={() => void mutate(row.key, () => api.resetSlot({ key: row.key }))}\n >\n {L.reset}\n </button>\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/ai-functions/server/slots.ts
|
|
4
|
+
var AiFunctionSlotError = class extends Error {
|
|
5
|
+
code;
|
|
6
|
+
slotKey;
|
|
7
|
+
constructor(code, slotKey, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "AiFunctionSlotError";
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.slotKey = slotKey;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var AiFunctionSlotUnsetError = class extends AiFunctionSlotError {
|
|
15
|
+
constructor(slotKey) {
|
|
16
|
+
super("slot_unset", slotKey, `AI function slot "${slotKey}" is not bound to a function.`);
|
|
17
|
+
this.name = "AiFunctionSlotUnsetError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var AiFunctionUnknownSlotError = class extends AiFunctionSlotError {
|
|
21
|
+
constructor(slotKey) {
|
|
22
|
+
super("unknown_slot", slotKey, `Unknown AI function slot "${slotKey}".`);
|
|
23
|
+
this.name = "AiFunctionUnknownSlotError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var AiFunctionSlotIncompatibleError = class extends AiFunctionSlotError {
|
|
27
|
+
functionKey;
|
|
28
|
+
issues;
|
|
29
|
+
constructor(slotKey, functionKey, issues) {
|
|
30
|
+
super(
|
|
31
|
+
"binding_incompatible",
|
|
32
|
+
slotKey,
|
|
33
|
+
`"${functionKey}" does not fit slot "${slotKey}": ` + issues.map((i) => i.message).join("; ")
|
|
34
|
+
);
|
|
35
|
+
this.name = "AiFunctionSlotIncompatibleError";
|
|
36
|
+
this.functionKey = functionKey;
|
|
37
|
+
this.issues = issues;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var SLOT_KEY_RE = /^[a-z][a-z0-9_]{1,63}$/;
|
|
41
|
+
function defineAiFunctionSlots(slots) {
|
|
42
|
+
const seen = /* @__PURE__ */ new Set();
|
|
43
|
+
for (const slot of slots) {
|
|
44
|
+
if (!SLOT_KEY_RE.test(slot.key)) {
|
|
45
|
+
throw new Error(`Invalid AI function slot key "${slot.key}" (use lower_snake_case, 2\u201364 chars).`);
|
|
46
|
+
}
|
|
47
|
+
if (seen.has(slot.key)) throw new Error(`Duplicate AI function slot key "${slot.key}".`);
|
|
48
|
+
seen.add(slot.key);
|
|
49
|
+
const names = /* @__PURE__ */ new Set();
|
|
50
|
+
for (const p of slot.parameters) {
|
|
51
|
+
if (!p.name || names.has(p.name)) {
|
|
52
|
+
throw new Error(`Slot "${slot.key}": parameter names must be non-empty and unique ("${p.name}").`);
|
|
53
|
+
}
|
|
54
|
+
names.add(p.name);
|
|
55
|
+
if (p.type === "enum" && (!p.options || p.options.length === 0)) {
|
|
56
|
+
throw new Error(`Slot "${slot.key}": enum parameter "${p.name}" needs options.`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return Object.freeze(slots);
|
|
61
|
+
}
|
|
62
|
+
function parseAiFunctionSlotBinding(raw) {
|
|
63
|
+
let value = raw;
|
|
64
|
+
if (typeof value === "string") {
|
|
65
|
+
const t = value.trim();
|
|
66
|
+
if (!t.startsWith("{")) return null;
|
|
67
|
+
try {
|
|
68
|
+
value = JSON.parse(t);
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (value && typeof value === "object") {
|
|
74
|
+
const fk = value.functionKey;
|
|
75
|
+
if (typeof fk === "string" && fk.trim()) return { functionKey: fk.trim() };
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function createAiFunctionSlotResolver(options) {
|
|
80
|
+
const { store } = options;
|
|
81
|
+
const ttlMs = options.ttlMs ?? 6e4;
|
|
82
|
+
const cache = /* @__PURE__ */ new Map();
|
|
83
|
+
return {
|
|
84
|
+
async get(key) {
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
const hit = cache.get(key);
|
|
87
|
+
if (hit && hit.expiresAt > now) return hit.value;
|
|
88
|
+
const value = parseAiFunctionSlotBinding(await store.get(key));
|
|
89
|
+
cache.set(key, { value, expiresAt: now + ttlMs });
|
|
90
|
+
return value;
|
|
91
|
+
},
|
|
92
|
+
async set(key, binding) {
|
|
93
|
+
await store.set(key, { functionKey: binding.functionKey });
|
|
94
|
+
cache.delete(key);
|
|
95
|
+
},
|
|
96
|
+
async delete(key) {
|
|
97
|
+
await store.delete(key);
|
|
98
|
+
cache.delete(key);
|
|
99
|
+
},
|
|
100
|
+
async list() {
|
|
101
|
+
const rows = await store.list();
|
|
102
|
+
const out = /* @__PURE__ */ new Map();
|
|
103
|
+
for (const row of rows) {
|
|
104
|
+
const b = parseAiFunctionSlotBinding(row.value);
|
|
105
|
+
if (b) out.set(row.key, b);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
},
|
|
109
|
+
clearCache(key) {
|
|
110
|
+
if (key) cache.delete(key);
|
|
111
|
+
else cache.clear();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
var DEFAULT_MAX_LENGTH = { string: 2e3, text: 24e3 };
|
|
116
|
+
function checkAiFunctionSlotCompatibility(slot, descriptor) {
|
|
117
|
+
const issues = [];
|
|
118
|
+
if (descriptor.outputKind !== slot.outputKind) {
|
|
119
|
+
issues.push({
|
|
120
|
+
code: "output_kind",
|
|
121
|
+
message: `returns ${descriptor.outputKind}, the slot expects ${slot.outputKind}`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const fnParams = new Map(descriptor.parameters.map((p) => [p.name, p]));
|
|
125
|
+
const slotParams = new Map(slot.parameters.map((p) => [p.name, p]));
|
|
126
|
+
for (const sp of slot.parameters) {
|
|
127
|
+
const fp = fnParams.get(sp.name);
|
|
128
|
+
if (!fp) {
|
|
129
|
+
issues.push({ code: "parameter_missing", parameter: sp.name, message: `has no parameter '${sp.name}'` });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (fp.type !== sp.type) {
|
|
133
|
+
issues.push({
|
|
134
|
+
code: "parameter_type",
|
|
135
|
+
parameter: sp.name,
|
|
136
|
+
message: `parameter '${sp.name}' is ${fp.type}, the slot passes ${sp.type}`
|
|
137
|
+
});
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (sp.options && sp.options.length > 0) {
|
|
141
|
+
const have = new Set(fp.options ?? []);
|
|
142
|
+
const missing = sp.options.filter((o) => !have.has(o));
|
|
143
|
+
if (missing.length > 0) {
|
|
144
|
+
issues.push({
|
|
145
|
+
code: "options",
|
|
146
|
+
parameter: sp.name,
|
|
147
|
+
message: `parameter '${sp.name}' does not accept ${missing.map((m) => `'${m}'`).join(", ")}`
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (typeof sp.maxLength === "number" && (sp.type === "string" || sp.type === "text")) {
|
|
152
|
+
const effective = fp.maxLength ?? DEFAULT_MAX_LENGTH[fp.type];
|
|
153
|
+
if (typeof effective === "number" && effective < sp.maxLength) {
|
|
154
|
+
issues.push({
|
|
155
|
+
code: "max_length",
|
|
156
|
+
parameter: sp.name,
|
|
157
|
+
message: `parameter '${sp.name}' allows ${effective} characters, the slot may pass ${sp.maxLength}`
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const fp of descriptor.parameters) {
|
|
163
|
+
if (!fp.required) continue;
|
|
164
|
+
const sp = slotParams.get(fp.name);
|
|
165
|
+
if (!sp || !sp.required) {
|
|
166
|
+
issues.push({
|
|
167
|
+
code: "parameter_required",
|
|
168
|
+
parameter: fp.name,
|
|
169
|
+
message: sp ? `requires '${fp.name}', which the slot passes only optionally` : `requires '${fp.name}', which the slot does not pass`
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return { compatible: issues.length === 0, issues };
|
|
174
|
+
}
|
|
175
|
+
function findSlot(slots, key) {
|
|
176
|
+
const slot = slots.find((s) => s.key === key);
|
|
177
|
+
if (!slot) throw new AiFunctionUnknownSlotError(key);
|
|
178
|
+
return slot;
|
|
179
|
+
}
|
|
180
|
+
function toOption(d) {
|
|
181
|
+
return { functionKey: d.functionKey, title: d.title, liveVersion: d.liveVersion };
|
|
182
|
+
}
|
|
183
|
+
function createAiFunctionSlotHandlers(deps) {
|
|
184
|
+
const { client, slots, resolver } = deps;
|
|
185
|
+
return {
|
|
186
|
+
async listSlots() {
|
|
187
|
+
const [functions, bindings] = await Promise.all([client.aiFunctions.list(), resolver.list()]);
|
|
188
|
+
const byKey = new Map(functions.map((f) => [f.functionKey, f]));
|
|
189
|
+
return slots.map((slot) => {
|
|
190
|
+
const binding = bindings.get(slot.key) ?? null;
|
|
191
|
+
const bound = binding ? byKey.get(binding.functionKey) : void 0;
|
|
192
|
+
let compat = null;
|
|
193
|
+
if (binding) {
|
|
194
|
+
compat = bound ? checkAiFunctionSlotCompatibility(slot, bound) : {
|
|
195
|
+
compatible: false,
|
|
196
|
+
issues: [
|
|
197
|
+
{
|
|
198
|
+
code: "function_unavailable",
|
|
199
|
+
message: `'${binding.functionKey}' is not published or no longer exists`
|
|
200
|
+
}
|
|
201
|
+
]
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
key: slot.key,
|
|
206
|
+
label: slot.label,
|
|
207
|
+
usedFor: slot.usedFor ?? null,
|
|
208
|
+
outputKind: slot.outputKind,
|
|
209
|
+
parameters: slot.parameters.map((p) => ({ ...p })),
|
|
210
|
+
binding,
|
|
211
|
+
boundFunction: bound ? { ...toOption(bound), outputKind: bound.outputKind } : null,
|
|
212
|
+
compat,
|
|
213
|
+
options: functions.filter((f) => checkAiFunctionSlotCompatibility(slot, f).compatible).map(toOption)
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
async setSlot({ key, functionKey }) {
|
|
218
|
+
const slot = findSlot(slots, key);
|
|
219
|
+
const descriptor = await client.aiFunctions.get(functionKey);
|
|
220
|
+
const verdict = checkAiFunctionSlotCompatibility(slot, descriptor);
|
|
221
|
+
if (!verdict.compatible) {
|
|
222
|
+
throw new AiFunctionSlotIncompatibleError(key, functionKey, verdict.issues);
|
|
223
|
+
}
|
|
224
|
+
await resolver.set(key, { functionKey });
|
|
225
|
+
return { key, functionKey, liveVersion: descriptor.liveVersion };
|
|
226
|
+
},
|
|
227
|
+
async resetSlot({ key }) {
|
|
228
|
+
findSlot(slots, key);
|
|
229
|
+
await resolver.delete(key);
|
|
230
|
+
return { key };
|
|
231
|
+
},
|
|
232
|
+
async listFunctions(input) {
|
|
233
|
+
const functions = await client.aiFunctions.list();
|
|
234
|
+
return input?.outputKind ? functions.filter((f) => f.outputKind === input.outputKind) : functions;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async function runSlot(deps, key, parameters, opts) {
|
|
239
|
+
findSlot(deps.slots, key);
|
|
240
|
+
const binding = await deps.resolver.get(key);
|
|
241
|
+
if (!binding) throw new AiFunctionSlotUnsetError(key);
|
|
242
|
+
return deps.client.aiFunctions.run(binding.functionKey, parameters, opts);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// src/ai-functions/server/classify.ts
|
|
246
|
+
function numberAt(source, key) {
|
|
247
|
+
if (!source || typeof source !== "object") return void 0;
|
|
248
|
+
const raw = source[key];
|
|
249
|
+
const n = typeof raw === "string" ? Number(raw) : raw;
|
|
250
|
+
return typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
251
|
+
}
|
|
252
|
+
function stringAt(source, key) {
|
|
253
|
+
if (!source || typeof source !== "object") return void 0;
|
|
254
|
+
const raw = source[key];
|
|
255
|
+
return typeof raw === "string" && raw ? raw : void 0;
|
|
256
|
+
}
|
|
257
|
+
function classifyAiFunctionError(error) {
|
|
258
|
+
const status = numberAt(error, "status");
|
|
259
|
+
const name = stringAt(error, "name");
|
|
260
|
+
const serverCode = stringAt(error, "serverCode");
|
|
261
|
+
const message = stringAt(error, "message") ?? (typeof error === "string" ? error : "AI function call failed");
|
|
262
|
+
const details = error && typeof error === "object" ? error.details : void 0;
|
|
263
|
+
const base = {
|
|
264
|
+
...status !== void 0 ? { status } : {},
|
|
265
|
+
...serverCode ? { serverCode } : {},
|
|
266
|
+
message
|
|
267
|
+
};
|
|
268
|
+
if (status === 429 || name === "RateLimitError" || serverCode === "RATE_LIMITED") {
|
|
269
|
+
const retryAfterSeconds = numberAt(error, "retryAfter") ?? numberAt(details, "retryAfter") ?? numberAt(details, "retry_after");
|
|
270
|
+
return { kind: "rate_limited", ...base, ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {} };
|
|
271
|
+
}
|
|
272
|
+
if (status === 401 || name === "AuthenticationError") return { kind: "unauthorized", ...base };
|
|
273
|
+
if (status === 403 || name === "AuthorizationError") return { kind: "forbidden", ...base };
|
|
274
|
+
if (status === 404 || name === "NotFoundError") return { kind: "not_found", ...base };
|
|
275
|
+
if (name === "TimeoutError" || name === "AbortError" || status === 408 || serverCode === "TIMEOUT") {
|
|
276
|
+
return { kind: "timeout", ...base };
|
|
277
|
+
}
|
|
278
|
+
if (name === "NetworkError" || status === 0 || status === void 0 && name === "TypeError") {
|
|
279
|
+
return { kind: "network", ...base };
|
|
280
|
+
}
|
|
281
|
+
return { kind: "upstream", ...base };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
exports.AiFunctionSlotError = AiFunctionSlotError;
|
|
285
|
+
exports.AiFunctionSlotIncompatibleError = AiFunctionSlotIncompatibleError;
|
|
286
|
+
exports.AiFunctionSlotUnsetError = AiFunctionSlotUnsetError;
|
|
287
|
+
exports.AiFunctionUnknownSlotError = AiFunctionUnknownSlotError;
|
|
288
|
+
exports.checkAiFunctionSlotCompatibility = checkAiFunctionSlotCompatibility;
|
|
289
|
+
exports.classifyAiFunctionError = classifyAiFunctionError;
|
|
290
|
+
exports.createAiFunctionSlotHandlers = createAiFunctionSlotHandlers;
|
|
291
|
+
exports.createAiFunctionSlotResolver = createAiFunctionSlotResolver;
|
|
292
|
+
exports.defineAiFunctionSlots = defineAiFunctionSlots;
|
|
293
|
+
exports.parseAiFunctionSlotBinding = parseAiFunctionSlotBinding;
|
|
294
|
+
exports.runSlot = runSlot;
|
|
295
|
+
//# sourceMappingURL=index.cjs.map
|
|
296
|
+
//# sourceMappingURL=index.cjs.map
|