@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.
- 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/board-host/index.cjs +217 -129
- package/dist/board-host/index.cjs.map +1 -1
- package/dist/board-host/index.d.cts +96 -1
- package/dist/board-host/index.d.ts +96 -1
- package/dist/board-host/index.js +218 -131
- package/dist/board-host/index.js.map +1 -1
- package/dist/board-host/server/index.d.cts +44 -0
- package/dist/board-host/server/index.d.ts +44 -0
- package/dist/chat/index.cjs +299 -17
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +225 -0
- package/dist/chat/index.d.ts +225 -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 +286 -1
- package/dist/index.d.ts +286 -1
- package/dist/index.js +215 -4
- package/dist/index.js.map +1 -1
- package/dist/kanban/engine/index.cjs +774 -683
- package/dist/kanban/engine/index.cjs.map +1 -1
- package/dist/kanban/engine/index.d.cts +90 -1
- package/dist/kanban/engine/index.d.ts +90 -1
- package/dist/kanban/engine/index.js +774 -684
- package/dist/kanban/engine/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 +252 -0
- package/dist/kanban/index.d.ts +252 -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 +225 -0
- package/dist/teams/index.d.ts +225 -0
- package/dist/teams/index.js +214 -3
- package/dist/teams/index.js.map +1 -1
- 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
|