@cadraos/sdk 0.3.14-p46.3 → 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.
Files changed (52) hide show
  1. package/README.md +104 -53
  2. package/dist/ai-functions/react/index.cjs +176 -0
  3. package/dist/ai-functions/react/index.cjs.map +1 -0
  4. package/dist/ai-functions/react/index.d.cts +129 -0
  5. package/dist/ai-functions/react/index.d.ts +129 -0
  6. package/dist/ai-functions/react/index.js +153 -0
  7. package/dist/ai-functions/react/index.js.map +1 -0
  8. package/dist/ai-functions/server/index.cjs +296 -0
  9. package/dist/ai-functions/server/index.cjs.map +1 -0
  10. package/dist/ai-functions/server/index.d.cts +334 -0
  11. package/dist/ai-functions/server/index.d.ts +334 -0
  12. package/dist/ai-functions/server/index.js +284 -0
  13. package/dist/ai-functions/server/index.js.map +1 -0
  14. package/dist/board-host/index.cjs +217 -129
  15. package/dist/board-host/index.cjs.map +1 -1
  16. package/dist/board-host/index.d.cts +96 -1
  17. package/dist/board-host/index.d.ts +96 -1
  18. package/dist/board-host/index.js +218 -131
  19. package/dist/board-host/index.js.map +1 -1
  20. package/dist/board-host/server/index.d.cts +44 -0
  21. package/dist/board-host/server/index.d.ts +44 -0
  22. package/dist/chat/index.cjs +299 -17
  23. package/dist/chat/index.cjs.map +1 -1
  24. package/dist/chat/index.d.cts +225 -0
  25. package/dist/chat/index.d.ts +225 -0
  26. package/dist/chat/index.js +299 -17
  27. package/dist/chat/index.js.map +1 -1
  28. package/dist/index.cjs +221 -3
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +286 -1
  31. package/dist/index.d.ts +286 -1
  32. package/dist/index.js +215 -4
  33. package/dist/index.js.map +1 -1
  34. package/dist/kanban/engine/index.cjs +774 -683
  35. package/dist/kanban/engine/index.cjs.map +1 -1
  36. package/dist/kanban/engine/index.d.cts +90 -1
  37. package/dist/kanban/engine/index.d.ts +90 -1
  38. package/dist/kanban/engine/index.js +774 -684
  39. package/dist/kanban/engine/index.js.map +1 -1
  40. package/dist/kanban/index.cjs +214 -3
  41. package/dist/kanban/index.cjs.map +1 -1
  42. package/dist/kanban/index.d.cts +252 -0
  43. package/dist/kanban/index.d.ts +252 -0
  44. package/dist/kanban/index.js +214 -3
  45. package/dist/kanban/index.js.map +1 -1
  46. package/dist/teams/index.cjs +214 -3
  47. package/dist/teams/index.cjs.map +1 -1
  48. package/dist/teams/index.d.cts +225 -0
  49. package/dist/teams/index.d.ts +225 -0
  50. package/dist/teams/index.js +214 -3
  51. package/dist/teams/index.js.map +1 -1
  52. package/package.json +21 -1
@@ -0,0 +1,334 @@
1
+ /**
2
+ * AI function types (p93 — specs §4 run contract, D27 discovery, §6.5 run reads).
3
+ *
4
+ * An AI function is a published prompt in the Cadra Prompts library that makes
5
+ * ONE direct AI-provider call (never an agent). Callers reference it by its
6
+ * `functionKey` (unique per org), by prompt uuid, or — for platform features —
7
+ * by slot key.
8
+ */
9
+ /** Output kind of an AI function. */
10
+ type AiFunctionOutputKind = 'text' | 'json' | 'image';
11
+ /** Parameter (prompt variable) types — specs D10 + D26d. */
12
+ type AiFunctionParameterType = 'string' | 'text' | 'number' | 'boolean' | 'enum' | 'image';
13
+ /** A value passed for one parameter. `image` parameters take an artifact uuid (string). */
14
+ type AiFunctionVariableValue = string | number | boolean;
15
+ /** Parameters (variables) for one run, keyed by parameter name. */
16
+ type AiFunctionVariables = Record<string, AiFunctionVariableValue>;
17
+ /** One declared parameter of a function's live (golden) version — D27a. */
18
+ interface AiFunctionParameter {
19
+ name: string;
20
+ type: AiFunctionParameterType;
21
+ required: boolean;
22
+ defaultValue?: AiFunctionVariableValue;
23
+ maxLength?: number;
24
+ options?: string[];
25
+ description?: string;
26
+ }
27
+ /**
28
+ * The discovery "index" entry for one callable function — D27a. Built from the
29
+ * golden version snapshot; never carries prompt or system text.
30
+ */
31
+ interface AiFunctionDescriptor {
32
+ functionKey: string;
33
+ title: string;
34
+ description?: string | null;
35
+ outputKind: AiFunctionOutputKind;
36
+ /** The live (golden) version number. */
37
+ liveVersion: number;
38
+ parameters: AiFunctionParameter[];
39
+ /** JSON schema of the output, for `json` functions. */
40
+ outputSchema?: Record<string, unknown> | null;
41
+ }
42
+ /** Who paid for a run (D4). */
43
+ type AiFunctionBilledTo = 'own_key' | 'credits' | 'platform_funded';
44
+ /** Runtime params a slot may accept per run (D26d3). */
45
+ interface AiFunctionRuntimeParams {
46
+ aspect_ratio?: string;
47
+ image_size?: string;
48
+ }
49
+ /** Options for the object form `run(ref, { variables, … })`. */
50
+ interface AiFunctionRunOptions {
51
+ /** Parameters for this run (sent as `variables`). */
52
+ variables: AiFunctionVariables;
53
+ /** Pin a published version number (D3d). Skips the client-side check. */
54
+ version?: number;
55
+ /** Idempotency key (uuid). Generated when omitted (D15). */
56
+ idempotencyKey?: string;
57
+ /** Slot runtime params (D26d3). */
58
+ params?: AiFunctionRuntimeParams;
59
+ /** `false` skips the client-side parameter check (D27c). Default true. */
60
+ validate?: boolean;
61
+ }
62
+ /** Options for the shorthand `run('function_key', parameters, opts)`. */
63
+ type AiFunctionRunShorthandOptions = Omit<AiFunctionRunOptions, 'variables'>;
64
+ /** Image produced by an `image` function. Over HTTP only `url` (presigned, 1 h) is set. */
65
+ interface AiFunctionImage {
66
+ mimeType: string;
67
+ url?: string;
68
+ bytes?: Uint8Array;
69
+ }
70
+ /** Output of a run, by kind (§4). `TOutput` types `json`. */
71
+ type AiFunctionOutput<TOutput = unknown> = {
72
+ type: 'text';
73
+ text: string;
74
+ } | {
75
+ type: 'json';
76
+ json: TOutput;
77
+ } | {
78
+ type: 'image';
79
+ images: AiFunctionImage[];
80
+ };
81
+ /** Token and billing usage of a run (§4). */
82
+ interface AiFunctionUsage {
83
+ promptTokens: number;
84
+ completionTokens: number;
85
+ imageCount: number;
86
+ billedTo: AiFunctionBilledTo;
87
+ }
88
+ /** Result of a successful run (§4). */
89
+ interface AiFunctionRunResult<TOutput = unknown> {
90
+ /** = the run's execution uuid (usage-ledger executionUuid). */
91
+ executionId: string;
92
+ functionUuid: string;
93
+ version: number;
94
+ output: AiFunctionOutput<TOutput>;
95
+ usage: AiFunctionUsage;
96
+ latencyMs: number;
97
+ }
98
+
99
+ /**
100
+ * Host-app AI function slots — shared types (p93 AIF-017, specs D28).
101
+ *
102
+ * Type-only module: imported by both `@cadraos/sdk/ai-functions/server` and
103
+ * `@cadraos/sdk/ai-functions/react` (the panel renders the rows the server
104
+ * handlers return), so it must stay free of runtime code and of React.
105
+ */
106
+
107
+ type MaybePromise<T> = T | Promise<T>;
108
+ /** One parameter a host slot passes when it calls its function. */
109
+ interface AiFunctionSlotParameter {
110
+ name: string;
111
+ type: AiFunctionParameterType;
112
+ required: boolean;
113
+ maxLength?: number;
114
+ options?: string[];
115
+ }
116
+ /** A place in the host app that calls an AI function (declared in host code). */
117
+ interface AiFunctionSlot {
118
+ /** Stable key, e.g. `campaign_subject`. Also the store key. */
119
+ key: string;
120
+ /** Human label for the admin panel. */
121
+ label: string;
122
+ /** What the host uses it for (panel "Used for" column). */
123
+ usedFor?: string;
124
+ /** The output kind the host code expects. */
125
+ outputKind: AiFunctionOutputKind;
126
+ /** Parameters the host code passes. */
127
+ parameters: readonly AiFunctionSlotParameter[];
128
+ }
129
+ /** What a slot is bound to: a function of the host's Cadra org. */
130
+ interface AiFunctionSlotBinding {
131
+ functionKey: string;
132
+ }
133
+ /**
134
+ * 4-method persistence adapter supplied by the host (e.g. Yobo: `system_config`,
135
+ * category `cadra_ai_functions`). Values may come back as the stored object or
136
+ * as its JSON string; anything unparseable reads as unset. There is NO env
137
+ * fallback — unset means unavailable.
138
+ */
139
+ interface AiFunctionSlotStore {
140
+ get(key: string): MaybePromise<AiFunctionSlotBinding | string | null | undefined>;
141
+ set(key: string, value: AiFunctionSlotBinding): MaybePromise<void>;
142
+ delete(key: string): MaybePromise<void>;
143
+ list(): MaybePromise<Array<{
144
+ key: string;
145
+ value: AiFunctionSlotBinding | string | null | undefined;
146
+ }>>;
147
+ }
148
+ /** Why a function does not fit a slot (D16 rule applied to a D27 descriptor). */
149
+ type AiFunctionSlotCompatIssueCode = 'function_unavailable' | 'output_kind' | 'parameter_missing' | 'parameter_type' | 'parameter_required' | 'options' | 'max_length';
150
+ interface AiFunctionSlotCompatIssue {
151
+ code: AiFunctionSlotCompatIssueCode;
152
+ /** The parameter concerned, when there is one. */
153
+ parameter?: string;
154
+ message: string;
155
+ }
156
+ interface AiFunctionSlotCompatVerdict {
157
+ compatible: boolean;
158
+ issues: AiFunctionSlotCompatIssue[];
159
+ }
160
+ /** A function offered in a slot's picker. */
161
+ interface AiFunctionSlotOption {
162
+ functionKey: string;
163
+ title: string;
164
+ liveVersion: number;
165
+ }
166
+ /** One row of `listSlots()` — catalog + binding + live descriptor + verdict. */
167
+ interface AiFunctionSlotRow {
168
+ key: string;
169
+ label: string;
170
+ usedFor: string | null;
171
+ outputKind: AiFunctionOutputKind;
172
+ parameters: AiFunctionSlotParameter[];
173
+ /** The stored binding, or null when the slot is unset. */
174
+ binding: AiFunctionSlotBinding | null;
175
+ /** The bound function's live descriptor summary; null when unset or unavailable. */
176
+ boundFunction: (AiFunctionSlotOption & {
177
+ outputKind: AiFunctionOutputKind;
178
+ }) | null;
179
+ /** Compatibility of the bound function; null when the slot is unset. */
180
+ compat: AiFunctionSlotCompatVerdict | null;
181
+ /** Functions compatible with this slot (the picker's choices). */
182
+ options: AiFunctionSlotOption[];
183
+ }
184
+ /** Result of a successful `setSlot`. */
185
+ interface AiFunctionSetSlotResult {
186
+ key: string;
187
+ functionKey: string;
188
+ liveVersion: number;
189
+ }
190
+ /**
191
+ * The slice of `CadraClient` the module needs — structural, so a host can pass
192
+ * its own `CadraClient` (any SDK copy) or a test double.
193
+ */
194
+ interface AiFunctionsClientLike {
195
+ aiFunctions: {
196
+ list(): Promise<AiFunctionDescriptor[]>;
197
+ get(functionKey: string): Promise<AiFunctionDescriptor>;
198
+ run<TOutput = unknown>(functionKey: string, parameters: AiFunctionVariables, opts?: AiFunctionRunShorthandOptions): Promise<AiFunctionRunResult<TOutput>>;
199
+ };
200
+ }
201
+ /** Kinds a thrown SDK error maps to (D28). */
202
+ type AiFunctionErrorKind = 'rate_limited' | 'unauthorized' | 'forbidden' | 'not_found' | 'timeout' | 'network' | 'upstream';
203
+ interface AiFunctionErrorClassification {
204
+ kind: AiFunctionErrorKind;
205
+ /** HTTP status when the error carried one. */
206
+ status?: number;
207
+ /** The server's own `error.code` when present (e.g. `NO_CREDITS`). */
208
+ serverCode?: string;
209
+ /** Seconds until a rate limit resets, when reported. */
210
+ retryAfterSeconds?: number;
211
+ message: string;
212
+ }
213
+
214
+ /**
215
+ * Slot catalog, resolver, compatibility and handlers (p93 AIF-017, specs D28).
216
+ *
217
+ * Port of Yobo's app-local `lib/cadra/function-config.ts` semantics (60 s
218
+ * process-local Map cache, cleared on write) MINUS the env fallback: an unset
219
+ * slot is unavailable, never silently served from `process.env`.
220
+ */
221
+
222
+ /** Base class for slot-module errors. `code` is stable; branch on it, not `instanceof`. */
223
+ declare class AiFunctionSlotError extends Error {
224
+ readonly code: string;
225
+ readonly slotKey: string;
226
+ constructor(code: string, slotKey: string, message: string);
227
+ }
228
+ /** `runSlot` on a slot with no binding. No request was sent. */
229
+ declare class AiFunctionSlotUnsetError extends AiFunctionSlotError {
230
+ constructor(slotKey: string);
231
+ }
232
+ /** A key that is not in the declared catalog. */
233
+ declare class AiFunctionUnknownSlotError extends AiFunctionSlotError {
234
+ constructor(slotKey: string);
235
+ }
236
+ /** `setSlot` refused: the function's live version does not fit the slot. */
237
+ declare class AiFunctionSlotIncompatibleError extends AiFunctionSlotError {
238
+ readonly functionKey: string;
239
+ readonly issues: AiFunctionSlotCompatIssue[];
240
+ constructor(slotKey: string, functionKey: string, issues: AiFunctionSlotCompatIssue[]);
241
+ }
242
+ /**
243
+ * Declare the host app's slots. Validates keys (lower_snake, unique) and
244
+ * parameter names, and returns the same array (frozen) so literal key types
245
+ * survive: `runSlot(deps, 'campaign_subject', …)`.
246
+ */
247
+ declare function defineAiFunctionSlots<const T extends readonly AiFunctionSlot[]>(slots: T): T;
248
+ /** Parse a stored value into a binding; anything else is "unset". */
249
+ declare function parseAiFunctionSlotBinding(raw: unknown): AiFunctionSlotBinding | null;
250
+ interface AiFunctionSlotResolver {
251
+ /** The binding for `key` (cached for `ttlMs`, including "unset"). */
252
+ get(key: string): Promise<AiFunctionSlotBinding | null>;
253
+ /** Store a binding and clear that key's cache. */
254
+ set(key: string, binding: AiFunctionSlotBinding): Promise<void>;
255
+ /** Remove a binding and clear that key's cache. */
256
+ delete(key: string): Promise<void>;
257
+ /** Every stored binding, read fresh from the store (admin view). */
258
+ list(): Promise<Map<string, AiFunctionSlotBinding>>;
259
+ /** Drop one key's cache entry, or all. */
260
+ clearCache(key?: string): void;
261
+ }
262
+ interface CreateAiFunctionSlotResolverOptions {
263
+ store: AiFunctionSlotStore;
264
+ /** Cache TTL in ms (default 60 000). The cache is per process. */
265
+ ttlMs?: number;
266
+ }
267
+ /**
268
+ * Resolve slot → binding with a process-local TTL cache. Writes through this
269
+ * resolver clear the cache so the writing process live-swaps; other processes
270
+ * pick the change up within `ttlMs`.
271
+ */
272
+ declare function createAiFunctionSlotResolver(options: CreateAiFunctionSlotResolverOptions): AiFunctionSlotResolver;
273
+ /**
274
+ * Does `descriptor` (a function's live version) fit `slot`? Passes iff: same
275
+ * output kind; every slot parameter exists with the same type (`string` ≠
276
+ * `text`); every REQUIRED function parameter is declared by the slot as
277
+ * required; enum options ⊇ the slot's; string/text effective maxLength ≥ the
278
+ * slot's.
279
+ */
280
+ declare function checkAiFunctionSlotCompatibility(slot: Pick<AiFunctionSlot, 'outputKind' | 'parameters'>, descriptor: Pick<AiFunctionDescriptor, 'outputKind' | 'parameters'>): AiFunctionSlotCompatVerdict;
281
+ interface AiFunctionSlotDeps {
282
+ client: AiFunctionsClientLike;
283
+ slots: readonly AiFunctionSlot[];
284
+ resolver: AiFunctionSlotResolver;
285
+ }
286
+ interface AiFunctionSlotHandlers {
287
+ /** Catalog + binding + live descriptor + compat verdict + compatible choices. */
288
+ listSlots(): Promise<AiFunctionSlotRow[]>;
289
+ /** Bind a slot; refuses an incompatible function (`AiFunctionSlotIncompatibleError`). */
290
+ setSlot(input: {
291
+ key: string;
292
+ functionKey: string;
293
+ }): Promise<AiFunctionSetSlotResult>;
294
+ /** Unbind a slot (it becomes unavailable). */
295
+ resetSlot(input: {
296
+ key: string;
297
+ }): Promise<{
298
+ key: string;
299
+ }>;
300
+ /** The org's callable functions, optionally by output kind. */
301
+ listFunctions(input?: {
302
+ outputKind?: AiFunctionDescriptor['outputKind'];
303
+ }): Promise<AiFunctionDescriptor[]>;
304
+ }
305
+ /**
306
+ * Framework-free async handlers. Wrap each in one router entry of the host
307
+ * (tRPC procedure, route handler, …) behind the host's admin permission.
308
+ * Errors from the SDK are rethrown untouched — map them with
309
+ * `classifyAiFunctionError` so a failed catalog read shows as an error, never
310
+ * as an empty list.
311
+ */
312
+ declare function createAiFunctionSlotHandlers(deps: AiFunctionSlotDeps): AiFunctionSlotHandlers;
313
+ /**
314
+ * Run the function bound to a slot. Unset ⇒ `AiFunctionSlotUnsetError` with no
315
+ * request. Calls use the host's Cadra API key (`public_api`, billed to that org).
316
+ */
317
+ declare function runSlot<TOutput = unknown>(deps: AiFunctionSlotDeps, key: string, parameters: AiFunctionVariables, opts?: AiFunctionRunShorthandOptions): Promise<AiFunctionRunResult<TOutput>>;
318
+
319
+ /**
320
+ * Structural SDK-error classifier (p93 AIF-017, specs D28).
321
+ *
322
+ * Reads `status` / `name` / `serverCode` off the thrown value — never
323
+ * `instanceof`: the SDK is often `link:`ed or duplicated in a host bundle, so
324
+ * class identity across module boundaries is a needless failure mode (Yobo
325
+ * `lib/cadra/upstream-error.ts` lesson, F55).
326
+ */
327
+
328
+ /**
329
+ * Map anything an AI-function call threw to one of
330
+ * `rate_limited | unauthorized | forbidden | not_found | timeout | network | upstream`.
331
+ */
332
+ declare function classifyAiFunctionError(error: unknown): AiFunctionErrorClassification;
333
+
334
+ export { type AiFunctionDescriptor, type AiFunctionErrorClassification, type AiFunctionErrorKind, type AiFunctionOutputKind, type AiFunctionParameter, type AiFunctionParameterType, type AiFunctionRunResult, type AiFunctionSetSlotResult, type AiFunctionSlot, type AiFunctionSlotBinding, type AiFunctionSlotCompatIssue, type AiFunctionSlotCompatIssueCode, type AiFunctionSlotCompatVerdict, type AiFunctionSlotDeps, AiFunctionSlotError, type AiFunctionSlotHandlers, AiFunctionSlotIncompatibleError, type AiFunctionSlotOption, type AiFunctionSlotParameter, type AiFunctionSlotResolver, type AiFunctionSlotRow, type AiFunctionSlotStore, AiFunctionSlotUnsetError, AiFunctionUnknownSlotError, type AiFunctionVariables, type AiFunctionsClientLike, type CreateAiFunctionSlotResolverOptions, checkAiFunctionSlotCompatibility, classifyAiFunctionError, createAiFunctionSlotHandlers, createAiFunctionSlotResolver, defineAiFunctionSlots, parseAiFunctionSlotBinding, runSlot };
@@ -0,0 +1,284 @@
1
+ // src/ai-functions/server/slots.ts
2
+ var AiFunctionSlotError = class extends Error {
3
+ code;
4
+ slotKey;
5
+ constructor(code, slotKey, message) {
6
+ super(message);
7
+ this.name = "AiFunctionSlotError";
8
+ this.code = code;
9
+ this.slotKey = slotKey;
10
+ }
11
+ };
12
+ var AiFunctionSlotUnsetError = class extends AiFunctionSlotError {
13
+ constructor(slotKey) {
14
+ super("slot_unset", slotKey, `AI function slot "${slotKey}" is not bound to a function.`);
15
+ this.name = "AiFunctionSlotUnsetError";
16
+ }
17
+ };
18
+ var AiFunctionUnknownSlotError = class extends AiFunctionSlotError {
19
+ constructor(slotKey) {
20
+ super("unknown_slot", slotKey, `Unknown AI function slot "${slotKey}".`);
21
+ this.name = "AiFunctionUnknownSlotError";
22
+ }
23
+ };
24
+ var AiFunctionSlotIncompatibleError = class extends AiFunctionSlotError {
25
+ functionKey;
26
+ issues;
27
+ constructor(slotKey, functionKey, issues) {
28
+ super(
29
+ "binding_incompatible",
30
+ slotKey,
31
+ `"${functionKey}" does not fit slot "${slotKey}": ` + issues.map((i) => i.message).join("; ")
32
+ );
33
+ this.name = "AiFunctionSlotIncompatibleError";
34
+ this.functionKey = functionKey;
35
+ this.issues = issues;
36
+ }
37
+ };
38
+ var SLOT_KEY_RE = /^[a-z][a-z0-9_]{1,63}$/;
39
+ function defineAiFunctionSlots(slots) {
40
+ const seen = /* @__PURE__ */ new Set();
41
+ for (const slot of slots) {
42
+ if (!SLOT_KEY_RE.test(slot.key)) {
43
+ throw new Error(`Invalid AI function slot key "${slot.key}" (use lower_snake_case, 2\u201364 chars).`);
44
+ }
45
+ if (seen.has(slot.key)) throw new Error(`Duplicate AI function slot key "${slot.key}".`);
46
+ seen.add(slot.key);
47
+ const names = /* @__PURE__ */ new Set();
48
+ for (const p of slot.parameters) {
49
+ if (!p.name || names.has(p.name)) {
50
+ throw new Error(`Slot "${slot.key}": parameter names must be non-empty and unique ("${p.name}").`);
51
+ }
52
+ names.add(p.name);
53
+ if (p.type === "enum" && (!p.options || p.options.length === 0)) {
54
+ throw new Error(`Slot "${slot.key}": enum parameter "${p.name}" needs options.`);
55
+ }
56
+ }
57
+ }
58
+ return Object.freeze(slots);
59
+ }
60
+ function parseAiFunctionSlotBinding(raw) {
61
+ let value = raw;
62
+ if (typeof value === "string") {
63
+ const t = value.trim();
64
+ if (!t.startsWith("{")) return null;
65
+ try {
66
+ value = JSON.parse(t);
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ if (value && typeof value === "object") {
72
+ const fk = value.functionKey;
73
+ if (typeof fk === "string" && fk.trim()) return { functionKey: fk.trim() };
74
+ }
75
+ return null;
76
+ }
77
+ function createAiFunctionSlotResolver(options) {
78
+ const { store } = options;
79
+ const ttlMs = options.ttlMs ?? 6e4;
80
+ const cache = /* @__PURE__ */ new Map();
81
+ return {
82
+ async get(key) {
83
+ const now = Date.now();
84
+ const hit = cache.get(key);
85
+ if (hit && hit.expiresAt > now) return hit.value;
86
+ const value = parseAiFunctionSlotBinding(await store.get(key));
87
+ cache.set(key, { value, expiresAt: now + ttlMs });
88
+ return value;
89
+ },
90
+ async set(key, binding) {
91
+ await store.set(key, { functionKey: binding.functionKey });
92
+ cache.delete(key);
93
+ },
94
+ async delete(key) {
95
+ await store.delete(key);
96
+ cache.delete(key);
97
+ },
98
+ async list() {
99
+ const rows = await store.list();
100
+ const out = /* @__PURE__ */ new Map();
101
+ for (const row of rows) {
102
+ const b = parseAiFunctionSlotBinding(row.value);
103
+ if (b) out.set(row.key, b);
104
+ }
105
+ return out;
106
+ },
107
+ clearCache(key) {
108
+ if (key) cache.delete(key);
109
+ else cache.clear();
110
+ }
111
+ };
112
+ }
113
+ var DEFAULT_MAX_LENGTH = { string: 2e3, text: 24e3 };
114
+ function checkAiFunctionSlotCompatibility(slot, descriptor) {
115
+ const issues = [];
116
+ if (descriptor.outputKind !== slot.outputKind) {
117
+ issues.push({
118
+ code: "output_kind",
119
+ message: `returns ${descriptor.outputKind}, the slot expects ${slot.outputKind}`
120
+ });
121
+ }
122
+ const fnParams = new Map(descriptor.parameters.map((p) => [p.name, p]));
123
+ const slotParams = new Map(slot.parameters.map((p) => [p.name, p]));
124
+ for (const sp of slot.parameters) {
125
+ const fp = fnParams.get(sp.name);
126
+ if (!fp) {
127
+ issues.push({ code: "parameter_missing", parameter: sp.name, message: `has no parameter '${sp.name}'` });
128
+ continue;
129
+ }
130
+ if (fp.type !== sp.type) {
131
+ issues.push({
132
+ code: "parameter_type",
133
+ parameter: sp.name,
134
+ message: `parameter '${sp.name}' is ${fp.type}, the slot passes ${sp.type}`
135
+ });
136
+ continue;
137
+ }
138
+ if (sp.options && sp.options.length > 0) {
139
+ const have = new Set(fp.options ?? []);
140
+ const missing = sp.options.filter((o) => !have.has(o));
141
+ if (missing.length > 0) {
142
+ issues.push({
143
+ code: "options",
144
+ parameter: sp.name,
145
+ message: `parameter '${sp.name}' does not accept ${missing.map((m) => `'${m}'`).join(", ")}`
146
+ });
147
+ }
148
+ }
149
+ if (typeof sp.maxLength === "number" && (sp.type === "string" || sp.type === "text")) {
150
+ const effective = fp.maxLength ?? DEFAULT_MAX_LENGTH[fp.type];
151
+ if (typeof effective === "number" && effective < sp.maxLength) {
152
+ issues.push({
153
+ code: "max_length",
154
+ parameter: sp.name,
155
+ message: `parameter '${sp.name}' allows ${effective} characters, the slot may pass ${sp.maxLength}`
156
+ });
157
+ }
158
+ }
159
+ }
160
+ for (const fp of descriptor.parameters) {
161
+ if (!fp.required) continue;
162
+ const sp = slotParams.get(fp.name);
163
+ if (!sp || !sp.required) {
164
+ issues.push({
165
+ code: "parameter_required",
166
+ parameter: fp.name,
167
+ message: sp ? `requires '${fp.name}', which the slot passes only optionally` : `requires '${fp.name}', which the slot does not pass`
168
+ });
169
+ }
170
+ }
171
+ return { compatible: issues.length === 0, issues };
172
+ }
173
+ function findSlot(slots, key) {
174
+ const slot = slots.find((s) => s.key === key);
175
+ if (!slot) throw new AiFunctionUnknownSlotError(key);
176
+ return slot;
177
+ }
178
+ function toOption(d) {
179
+ return { functionKey: d.functionKey, title: d.title, liveVersion: d.liveVersion };
180
+ }
181
+ function createAiFunctionSlotHandlers(deps) {
182
+ const { client, slots, resolver } = deps;
183
+ return {
184
+ async listSlots() {
185
+ const [functions, bindings] = await Promise.all([client.aiFunctions.list(), resolver.list()]);
186
+ const byKey = new Map(functions.map((f) => [f.functionKey, f]));
187
+ return slots.map((slot) => {
188
+ const binding = bindings.get(slot.key) ?? null;
189
+ const bound = binding ? byKey.get(binding.functionKey) : void 0;
190
+ let compat = null;
191
+ if (binding) {
192
+ compat = bound ? checkAiFunctionSlotCompatibility(slot, bound) : {
193
+ compatible: false,
194
+ issues: [
195
+ {
196
+ code: "function_unavailable",
197
+ message: `'${binding.functionKey}' is not published or no longer exists`
198
+ }
199
+ ]
200
+ };
201
+ }
202
+ return {
203
+ key: slot.key,
204
+ label: slot.label,
205
+ usedFor: slot.usedFor ?? null,
206
+ outputKind: slot.outputKind,
207
+ parameters: slot.parameters.map((p) => ({ ...p })),
208
+ binding,
209
+ boundFunction: bound ? { ...toOption(bound), outputKind: bound.outputKind } : null,
210
+ compat,
211
+ options: functions.filter((f) => checkAiFunctionSlotCompatibility(slot, f).compatible).map(toOption)
212
+ };
213
+ });
214
+ },
215
+ async setSlot({ key, functionKey }) {
216
+ const slot = findSlot(slots, key);
217
+ const descriptor = await client.aiFunctions.get(functionKey);
218
+ const verdict = checkAiFunctionSlotCompatibility(slot, descriptor);
219
+ if (!verdict.compatible) {
220
+ throw new AiFunctionSlotIncompatibleError(key, functionKey, verdict.issues);
221
+ }
222
+ await resolver.set(key, { functionKey });
223
+ return { key, functionKey, liveVersion: descriptor.liveVersion };
224
+ },
225
+ async resetSlot({ key }) {
226
+ findSlot(slots, key);
227
+ await resolver.delete(key);
228
+ return { key };
229
+ },
230
+ async listFunctions(input) {
231
+ const functions = await client.aiFunctions.list();
232
+ return input?.outputKind ? functions.filter((f) => f.outputKind === input.outputKind) : functions;
233
+ }
234
+ };
235
+ }
236
+ async function runSlot(deps, key, parameters, opts) {
237
+ findSlot(deps.slots, key);
238
+ const binding = await deps.resolver.get(key);
239
+ if (!binding) throw new AiFunctionSlotUnsetError(key);
240
+ return deps.client.aiFunctions.run(binding.functionKey, parameters, opts);
241
+ }
242
+
243
+ // src/ai-functions/server/classify.ts
244
+ function numberAt(source, key) {
245
+ if (!source || typeof source !== "object") return void 0;
246
+ const raw = source[key];
247
+ const n = typeof raw === "string" ? Number(raw) : raw;
248
+ return typeof n === "number" && Number.isFinite(n) && n >= 0 ? n : void 0;
249
+ }
250
+ function stringAt(source, key) {
251
+ if (!source || typeof source !== "object") return void 0;
252
+ const raw = source[key];
253
+ return typeof raw === "string" && raw ? raw : void 0;
254
+ }
255
+ function classifyAiFunctionError(error) {
256
+ const status = numberAt(error, "status");
257
+ const name = stringAt(error, "name");
258
+ const serverCode = stringAt(error, "serverCode");
259
+ const message = stringAt(error, "message") ?? (typeof error === "string" ? error : "AI function call failed");
260
+ const details = error && typeof error === "object" ? error.details : void 0;
261
+ const base = {
262
+ ...status !== void 0 ? { status } : {},
263
+ ...serverCode ? { serverCode } : {},
264
+ message
265
+ };
266
+ if (status === 429 || name === "RateLimitError" || serverCode === "RATE_LIMITED") {
267
+ const retryAfterSeconds = numberAt(error, "retryAfter") ?? numberAt(details, "retryAfter") ?? numberAt(details, "retry_after");
268
+ return { kind: "rate_limited", ...base, ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {} };
269
+ }
270
+ if (status === 401 || name === "AuthenticationError") return { kind: "unauthorized", ...base };
271
+ if (status === 403 || name === "AuthorizationError") return { kind: "forbidden", ...base };
272
+ if (status === 404 || name === "NotFoundError") return { kind: "not_found", ...base };
273
+ if (name === "TimeoutError" || name === "AbortError" || status === 408 || serverCode === "TIMEOUT") {
274
+ return { kind: "timeout", ...base };
275
+ }
276
+ if (name === "NetworkError" || status === 0 || status === void 0 && name === "TypeError") {
277
+ return { kind: "network", ...base };
278
+ }
279
+ return { kind: "upstream", ...base };
280
+ }
281
+
282
+ export { AiFunctionSlotError, AiFunctionSlotIncompatibleError, AiFunctionSlotUnsetError, AiFunctionUnknownSlotError, checkAiFunctionSlotCompatibility, classifyAiFunctionError, createAiFunctionSlotHandlers, createAiFunctionSlotResolver, defineAiFunctionSlots, parseAiFunctionSlotBinding, runSlot };
283
+ //# sourceMappingURL=index.js.map
284
+ //# sourceMappingURL=index.js.map