diff --git a/src/api/aiEngine.js b/src/api/aiEngine.ts similarity index 94% rename from src/api/aiEngine.js rename to src/api/aiEngine.ts index 32268a6..fdd0f29 100644 --- a/src/api/aiEngine.js +++ b/src/api/aiEngine.ts @@ -547,7 +547,11 @@ function matchTalent(prompt) { /* ── 7. Owliver profile builder ────────────────────────────────────────── */ /** Job titles a worker might name for themselves, most specific first. */ -const ROLE_TITLES = [ +/* Explicit tuple, not `as const`: the entries are read as a pair — `.find(([re]) => re.test(…))` + then `role[1]` — and without it the element widens to `string | RegExp`, which has no `.test`. + `as const` would also work and would additionally make the array and its entries `readonly`, + a change to the type this module publishes for no benefit it uses. */ +const ROLE_TITLES: [RegExp, string][] = [ [/banquet captain|captain/, 'Banquet Captain'], [/executive chef/, 'Executive Chef'], [/sous chef/, 'Sous Chef'], @@ -563,7 +567,8 @@ const ROLE_TITLES = [ [/manager/, 'Manager'], ]; -const AVAILABILITY_TOKENS = [ +/** Same pairing, same reason — matched by regex, collected by label. */ +const AVAILABILITY_TOKENS: [RegExp, string][] = [ [/weekend|saturday|sunday|sábado|domingo/i, 'Weekends'], [/weekday|monday|tuesday|wednesday|thursday|friday|semana/i, 'Weekdays'], [/evening|night|noche|tarde/i, 'Evenings'], @@ -768,7 +773,15 @@ function evaluateChallenge(prompt) { * Recognizes each workflow by the phrase its prompt opens with. Order matters * only in that every branch is mutually exclusive by design. */ -const ROUTES = [ +/* `[pattern, handler, latencyMs]`. Spelled out because all three positions are used for what + they are: `.test()` on the first, a call on the second, and `think(ms)` on the third. Left to + inference the element becomes the union of all three and none of those work. + + The handler returns `any` deliberately. The ten handlers below return ten different shapes, + which is the point of a router — and `krowAi.js` relies on it: one caller does + `typeof res === 'string' ? res : res.text || String(res)`, which a precise union would + reject on every branch that has no `.text`. */ +const ROUTES: [RegExp, (prompt: string) => any, number][] = [ [/^You are KROW's AI screening engine/m, screenCandidate, 1100], [/^You are an expert hiring copywriter/m, generateJobDescription, 1400], [/Extract and infer a structured resume/m, buildResume, 1200], @@ -782,8 +795,32 @@ const ROUTES = [ () => 'Walk me through the exact order you would do that in — what happens first, and who do you tell?', 700], ]; -/** Drop-in replacement for `integrations.Core.InvokeLLM`. */ -export async function invokeLLM({ prompt = '', response_json_schema: schema } = {}) { +/** + * What a caller may send. Every field optional, because the destructuring default `= {}` + * says a call with no argument at all is legal, and it is one this module answers. + * + * `model` and `file_urls` are not read here — the local engine routes on the prompt and + * ignores both — but callers send them, and a parameter type that omitted them would reject + * eight call sites in `krowAi.js` and one in `provingGround.ts` for passing a field the real + * integration accepts. Declaring them describes the call this function is a drop-in for. + */ +export interface InvokeLLMOptions { + prompt?: string; + response_json_schema?: any; + model?: string; + file_urls?: string[]; +} + +/** + * Drop-in replacement for `integrations.Core.InvokeLLM`. + * + * The return stays `any` on purpose. See the note on `ROUTES`: the shape depends on which + * handler matched, and narrowing it here would break callers that branch on the result at + * run time rather than by type. + */ +export async function invokeLLM( + { prompt = '', response_json_schema: schema }: InvokeLLMOptions = {}, +): Promise { const route = ROUTES.find(([pattern]) => pattern.test(prompt)); if (!route) { @@ -800,7 +837,7 @@ export async function invokeLLM({ prompt = '', response_json_schema: schema } = } /** Drop-in replacement for `integrations.Core.UploadFile`. */ -export async function uploadFile({ file } = {}) { +export async function uploadFile({ file }: { file?: File } = {}) { await think(500); if (!file) return { file_url: '' }; // A blob URL keeps uploaded media viewable for the rest of the session