chore(ts-migration): migrate ai engine to TypeScript

Phase 4b-i. Type-only, and the production bundle proves it: built from this
commit's parent and from this commit, all six chunk hashes match. The plain
emitted JavaScript is byte-identical at 34,095 bytes.

Four annotation sites, no logic touched:

  - `ROLE_TITLES` and `AVAILABILITY_TOKENS` get `[RegExp, string][]`. Left to
    inference the element widens to `string | RegExp`, which has no `.test`.
    Explicit tuples rather than `as const`, which would also have worked and
    would additionally have made the arrays readonly - a change to the type this
    module publishes for no benefit it uses.

  - `ROUTES` gets `[RegExp, (prompt: string) => any, number][]`, because all
    three positions are used for what they are: `.test()` on the first, a call
    on the second, `think(ms)` on the third.

  - `invokeLLM` gets a real parameter type. It inferred `{ prompt?: string }`
    from its own destructuring defaults, and that single inference was
    responsible for nine errors in files this commit does not touch - eight in
    `krowAi.js`, one in `provingGround.ts` - every one of them a caller passing
    `response_json_schema` or `model`, which the real integration accepts.
    Naming the options type fixes all nine from here.

  - `uploadFile` gets `{ file?: File }`.

The return type of `invokeLLM` stays `Promise<any>`, deliberately. The ten
handlers behind the router return ten different shapes, and `krowAi.js` branches
on the result at run time - `typeof res === 'string' ? res : res.text || String(res)`
- which a precise union would reject on every branch without a `.text`. The
looseness is the contract, not an omission.

`InvokeLLMOptions` is exported as a type only; the runtime exports are still
exactly `invokeLLM` and `uploadFile`.

Verified in isolation from the parallel feature work (f96f128 plus this file):
tsc 53 -> 37, a set-difference against the baseline showing sixteen removed and
none added; skill-check 1641/1642 with only the known stale-fixture failure;
Owliver baseline 59/59; lint 0 errors; build succeeds with the API origin
inlined; the nine Owliver baseline artifacts unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
2026-09-17 17:23:28 +05:30
parent f96f128839
commit 64140c7add

View File

@@ -547,7 +547,11 @@ function matchTalent(prompt) {
/* ── 7. Owliver profile builder ────────────────────────────────────────── */ /* ── 7. Owliver profile builder ────────────────────────────────────────── */
/** Job titles a worker might name for themselves, most specific first. */ /** 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'], [/banquet captain|captain/, 'Banquet Captain'],
[/executive chef/, 'Executive Chef'], [/executive chef/, 'Executive Chef'],
[/sous chef/, 'Sous Chef'], [/sous chef/, 'Sous Chef'],
@@ -563,7 +567,8 @@ const ROLE_TITLES = [
[/manager/, 'Manager'], [/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'], [/weekend|saturday|sunday|sábado|domingo/i, 'Weekends'],
[/weekday|monday|tuesday|wednesday|thursday|friday|semana/i, 'Weekdays'], [/weekday|monday|tuesday|wednesday|thursday|friday|semana/i, 'Weekdays'],
[/evening|night|noche|tarde/i, 'Evenings'], [/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 * Recognizes each workflow by the phrase its prompt opens with. Order matters
* only in that every branch is mutually exclusive by design. * 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 KROW's AI screening engine/m, screenCandidate, 1100],
[/^You are an expert hiring copywriter/m, generateJobDescription, 1400], [/^You are an expert hiring copywriter/m, generateJobDescription, 1400],
[/Extract and infer a structured resume/m, buildResume, 1200], [/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], () => '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<any> {
const route = ROUTES.find(([pattern]) => pattern.test(prompt)); const route = ROUTES.find(([pattern]) => pattern.test(prompt));
if (!route) { if (!route) {
@@ -800,7 +837,7 @@ export async function invokeLLM({ prompt = '', response_json_schema: schema } =
} }
/** Drop-in replacement for `integrations.Core.UploadFile`. */ /** Drop-in replacement for `integrations.Core.UploadFile`. */
export async function uploadFile({ file } = {}) { export async function uploadFile({ file }: { file?: File } = {}) {
await think(500); await think(500);
if (!file) return { file_url: '' }; if (!file) return { file_url: '' };
// A blob URL keeps uploaded media viewable for the rest of the session // A blob URL keeps uploaded media viewable for the rest of the session