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:
@@ -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
|
||||||
Reference in New Issue
Block a user