@hawkeyexl/inference 0.0.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/providers/anthropic.ts","../src/providers/openai-compat.ts","../src/exec.ts","../src/providers/claude-cli.ts","../src/providers/mock.ts","../src/providers/index.ts","../src/complete.ts","../src/cache.ts","../src/cost.ts","../src/judge/verdict-schema.json","../src/judge/types.ts","../src/judge/consensus.ts","../src/judge/zones.ts","../src/judge/ensemble.ts"],"sourcesContent":["/**\n * Shared error type. Consumers catch this to distinguish an inference-layer\n * operational failure (missing API key, unknown provider) from their own\n * domain errors, and typically map it to their own exit code.\n */\nexport class InferenceError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InferenceError\";\n }\n}\n","/**\n * Anthropic provider: structured output via a single forced tool call whose\n * input schema is the caller's schema — the model cannot answer any other way.\n */\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { InferenceError } from \"../types.js\";\nimport type {\n CompleteJSONRequest,\n CompleteJSONResponse,\n InferenceProvider,\n} from \"./types.js\";\n\nconst DEFAULT_TOOL_NAME = \"record_result\";\n\nexport interface AnthropicProviderOptions {\n /**\n * Name of the forced tool. Purely cosmetic to the model, but a descriptive\n * name (\"record_verdict\", \"record_proposal\") measurably steers output, so\n * consumers may set their own.\n */\n toolName?: string;\n /** Tool description shown to the model. */\n toolDescription?: string;\n maxTokens?: number;\n}\n\nexport class AnthropicProvider implements InferenceProvider {\n private readonly client: Anthropic;\n private readonly toolName: string;\n private readonly toolDescription: string;\n private readonly maxTokens: number;\n\n constructor(\n private readonly model: string,\n apiKeyEnv: string,\n options: AnthropicProviderOptions = {},\n ) {\n const apiKey = process.env[apiKeyEnv];\n if (!apiKey) {\n throw new InferenceError(\n `Anthropic provider needs ${apiKeyEnv} set (or choose another provider)`,\n );\n }\n this.client = new Anthropic({ apiKey });\n this.toolName = options.toolName ?? DEFAULT_TOOL_NAME;\n this.toolDescription =\n options.toolDescription ?? \"Record the structured result.\";\n this.maxTokens = options.maxTokens ?? 1024;\n }\n\n provider(): string {\n return \"anthropic\";\n }\n\n modelName(): string {\n return this.model;\n }\n\n async completeJSON(req: CompleteJSONRequest): Promise<CompleteJSONResponse> {\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: this.maxTokens,\n temperature: req.temperature,\n system: req.system,\n messages: [{ role: \"user\", content: req.user }],\n tools: [\n {\n name: this.toolName,\n description: this.toolDescription,\n input_schema: req.schema as Anthropic.Tool[\"input_schema\"],\n },\n ],\n tool_choice: { type: \"tool\", name: this.toolName },\n });\n\n // A truncated tool call still arrives as a tool_use block, just with\n // partial input. Without this check it fails schema validation instead,\n // burning the retry and reporting a misleading validation error rather\n // than the actionable \"raise maxTokens\".\n if (response.stop_reason === \"max_tokens\") {\n throw new Error(\n `Anthropic response hit max_tokens (${this.maxTokens}) before completing the tool call — raise the anthropic.maxTokens option.`,\n );\n }\n\n const toolUse = response.content.find(\n (block): block is Anthropic.ToolUseBlock => block.type === \"tool_use\",\n );\n if (!toolUse) {\n throw new Error(\"Anthropic response contained no tool_use block\");\n }\n return {\n json: toolUse.input,\n usage: {\n inputTokens: response.usage.input_tokens,\n outputTokens: response.usage.output_tokens,\n },\n };\n }\n}\n","/**\n * OpenAI-compatible provider: works against OpenAI, Azure, Ollama, Groq,\n * Together, or any server speaking /chat/completions. Prefers strict\n * json_schema response_format; falls back to json_object + schema-in-prompt\n * when the server rejects it (older Ollama, some proxies).\n */\nimport { InferenceError } from \"../types.js\";\nimport type {\n CompleteJSONRequest,\n CompleteJSONResponse,\n InferenceProvider,\n} from \"./types.js\";\n\ninterface ChatResponse {\n choices?: { message?: { content?: string } }[];\n usage?: { prompt_tokens?: number; completion_tokens?: number };\n error?: { message?: string };\n}\n\n/** Extract a JSON object from content that may carry markdown fences. */\nexport function extractJson(content: string): unknown {\n const trimmed = content\n .replace(/^\\s*```(?:json)?\\s*/i, \"\")\n .replace(/\\s*```\\s*$/, \"\")\n .trim();\n try {\n return JSON.parse(trimmed);\n } catch {\n const start = trimmed.indexOf(\"{\");\n const end = trimmed.lastIndexOf(\"}\");\n if (start >= 0 && end > start) {\n return JSON.parse(trimmed.slice(start, end + 1));\n }\n throw new Error(\"Response contained no parseable JSON object\");\n }\n}\n\n/**\n * OpenAI strict mode requires `required` to list EVERY property (optionality\n * is expressed as a `null` type union) and rejects keywords outside its\n * subset (minLength, uniqueItems). Transform an all-optional schema into a\n * strict-compatible equivalent; null values are stripped from the response.\n */\nexport function toStrictSchema(\n schema: Record<string, unknown>,\n): Record<string, unknown> {\n const clone = structuredClone(schema);\n const walk = (node: unknown): void => {\n if (node === null || typeof node !== \"object\") return;\n if (Array.isArray(node)) {\n for (const item of node) walk(item);\n return;\n }\n const obj = node as Record<string, unknown>;\n delete obj[\"minLength\"];\n delete obj[\"uniqueItems\"];\n const properties = obj[\"properties\"];\n if (properties && typeof properties === \"object\") {\n obj[\"required\"] = Object.keys(properties);\n // Strict mode requires additionalProperties:false on EVERY object, not\n // just the root. A nested object without it is rejected as a schema\n // error, which permanently downgrades this provider to the weaker\n // json_object fallback for the rest of its life.\n obj[\"additionalProperties\"] = false;\n for (const prop of Object.values(properties as Record<string, unknown>)) {\n walk(prop);\n if (prop && typeof prop === \"object\" && !Array.isArray(prop)) {\n const p = prop as Record<string, unknown>;\n if (typeof p[\"type\"] === \"string\" && p[\"type\"] !== \"null\") {\n p[\"type\"] = [p[\"type\"], \"null\"];\n }\n }\n }\n }\n walk(obj[\"items\"]);\n };\n walk(clone);\n return clone;\n}\n\n/** Remove null-valued keys (strict-mode \"omitted\" marker) from a response object. */\nexport function stripNulls(value: unknown): unknown {\n if (value === null || typeof value !== \"object\" || Array.isArray(value)) {\n return value;\n }\n return Object.fromEntries(\n Object.entries(value as Record<string, unknown>).filter(\n ([, v]) => v !== null,\n ),\n );\n}\n\nexport interface OpenAICompatProviderOptions {\n /** `json_schema.name` sent to the server. Cosmetic; steers some models. */\n schemaName?: string;\n}\n\nexport class OpenAICompatProvider implements InferenceProvider {\n private supportsJsonSchema = true;\n private readonly schemaName: string;\n\n constructor(\n private readonly baseUrl: string,\n private readonly model: string,\n apiKeyEnv: string,\n private readonly apiKey: string | undefined = process.env[apiKeyEnv],\n options: OpenAICompatProviderOptions = {},\n ) {\n // Local servers (Ollama) often need no key; only insist for api.openai.com.\n if (!this.apiKey && baseUrl.includes(\"api.openai.com\")) {\n throw new InferenceError(\n `OpenAI provider needs ${apiKeyEnv} set (or point baseUrl at a local server)`,\n );\n }\n this.schemaName = options.schemaName ?? \"result\";\n }\n\n provider(): string {\n return \"openai\";\n }\n\n modelName(): string {\n return this.model;\n }\n\n private async chat(body: Record<string, unknown>): Promise<ChatResponse> {\n const response = await fetch(\n `${this.baseUrl.replace(/\\/$/, \"\")}/chat/completions`,\n {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n ...(this.apiKey ? { authorization: `Bearer ${this.apiKey}` } : {}),\n },\n body: JSON.stringify(body),\n },\n );\n const json = (await response.json().catch(() => ({}))) as ChatResponse;\n if (!response.ok) {\n const message = json.error?.message ?? `HTTP ${response.status}`;\n throw new Error(`${message}`);\n }\n return json;\n }\n\n async completeJSON(req: CompleteJSONRequest): Promise<CompleteJSONResponse> {\n const base = {\n model: this.model,\n temperature: req.temperature,\n messages: [\n { role: \"system\", content: req.system },\n { role: \"user\", content: req.user },\n ],\n };\n\n let response: ChatResponse;\n if (this.supportsJsonSchema) {\n try {\n response = await this.chat({\n ...base,\n response_format: {\n type: \"json_schema\",\n json_schema: {\n name: this.schemaName,\n strict: true,\n schema: toStrictSchema(req.schema),\n },\n },\n });\n } catch (e) {\n const message = e instanceof Error ? e.message : String(e);\n // Fall back on schema/format complaints AND on opaque 400s (gateways\n // that reject response_format without a parseable error body).\n if (\n !/response_format|json_schema|schema/i.test(message) &&\n message !== \"HTTP 400\"\n ) {\n throw e;\n }\n this.supportsJsonSchema = false;\n response = await this.jsonObjectFallback(base, req);\n }\n } else {\n response = await this.jsonObjectFallback(base, req);\n }\n\n const content = response.choices?.[0]?.message?.content;\n if (!content) throw new Error(\"Empty completion response\");\n return {\n json: stripNulls(extractJson(content)),\n usage:\n response.usage?.prompt_tokens != null\n ? {\n inputTokens: response.usage.prompt_tokens ?? 0,\n outputTokens: response.usage.completion_tokens ?? 0,\n }\n : undefined,\n };\n }\n\n private jsonObjectFallback(\n base: Record<string, unknown>,\n req: CompleteJSONRequest,\n ): Promise<ChatResponse> {\n return this.chat({\n ...base,\n messages: [\n {\n role: \"system\",\n content: `${req.system}\\n\\nRespond with ONLY a JSON object conforming to this JSON Schema:\\n${JSON.stringify(req.schema)}`,\n },\n { role: \"user\", content: req.user },\n ],\n response_format: { type: \"json_object\" },\n });\n }\n}\n","/**\n * Process execution wrapper for subprocess-backed providers. Uses cross-spawn\n * so npm shims resolve on Windows without `shell: true` and its quoting\n * hazards. Large payloads go through `input` (piped stdin) rather than argv —\n * Windows caps the command line at ~32K characters.\n */\nimport spawn from \"cross-spawn\";\nimport type { ExecFn, ExecResult } from \"./providers/types.js\";\n\nexport const realExec: ExecFn = (cmd, opts = {}) => {\n const [bin, ...args] = cmd;\n if (!bin) {\n return Promise.resolve<ExecResult>({\n code: null,\n stdout: \"\",\n stderr: \"\",\n timedOut: false,\n spawnError: \"Empty command\",\n });\n }\n return new Promise<ExecResult>((resolvePromise) => {\n const child = spawn(bin, args, {\n cwd: opts.cwd,\n env: { ...process.env, ...(opts.env ?? {}) },\n stdio: [opts.input != null ? \"pipe\" : \"ignore\", \"pipe\", \"pipe\"],\n });\n\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n let settled = false;\n\n const timeoutMs = opts.timeoutMs ?? 60000;\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill();\n // Settle on the timeout itself rather than waiting for 'close'. On POSIX\n // a child that handles SIGTERM survives kill(), so 'close' would never\n // fire and the caller would hang forever with no timeout error. (On\n // Windows kill() is forceful, so this is belt-and-braces there.)\n settle({ code: null, stdout, stderr, timedOut: true });\n }, timeoutMs);\n\n const settle = (result: ExecResult): void => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolvePromise(result);\n };\n\n if (opts.input != null && child.stdin) {\n // EPIPE from a child that exits before reading is not our failure.\n child.stdin.on(\"error\", () => {});\n child.stdin.end(opts.input);\n }\n\n // setEncoding routes chunks through a StringDecoder, so multi-byte UTF-8\n // characters straddling pipe-chunk boundaries decode correctly; a raw\n // per-chunk Buffer.toString() would corrupt them nondeterministically.\n child.stdout?.setEncoding(\"utf8\");\n child.stderr?.setEncoding(\"utf8\");\n child.stdout?.on(\"data\", (d: string) => (stdout += d));\n child.stderr?.on(\"data\", (d: string) => (stderr += d));\n child.on(\"error\", (e) =>\n settle({ code: null, stdout, stderr, timedOut, spawnError: e.message }),\n );\n child.on(\"close\", (code) => settle({ code, stdout, stderr, timedOut }));\n });\n};\n","/**\n * Claude CLI provider: shells out to the `claude` CLI using its local\n * authentication — no API key needed. Token usage is not reported, so cost\n * shows as unknown.\n */\nimport { realExec } from \"../exec.js\";\nimport { extractJson } from \"./openai-compat.js\";\nimport type {\n CompleteJSONRequest,\n CompleteJSONResponse,\n ExecFn,\n InferenceProvider,\n} from \"./types.js\";\n\nexport class ClaudeCliProvider implements InferenceProvider {\n constructor(\n private readonly model: string,\n private readonly command: string = \"claude\",\n private readonly exec: ExecFn = realExec,\n private readonly timeoutMs: number = 180000,\n ) {}\n\n provider(): string {\n return \"claude-cli\";\n }\n\n modelName(): string {\n return this.model;\n }\n\n async completeJSON(req: CompleteJSONRequest): Promise<CompleteJSONResponse> {\n const prompt = [\n req.user,\n \"\",\n \"Respond with ONLY a JSON object conforming to this JSON Schema — no\",\n \"prose, no markdown fences:\",\n JSON.stringify(req.schema),\n ].join(\"\\n\");\n\n // The prompt is piped via stdin: user content routinely exceeds the ~32K\n // Windows command-line limit when passed as an argument.\n const result = await this.exec(\n [\n this.command,\n \"-p\",\n \"--append-system-prompt\",\n req.system,\n \"--output-format\",\n \"json\",\n \"--model\",\n this.model,\n ],\n { timeoutMs: this.timeoutMs, input: prompt },\n );\n\n if (result.spawnError) {\n throw new Error(\n `Failed to run ${this.command}: ${result.spawnError} (is the Claude CLI installed?)`,\n );\n }\n if (result.timedOut) throw new Error(\"Claude CLI timed out\");\n if (result.code !== 0) {\n throw new Error(\n `Claude CLI exited ${result.code}: ${result.stderr.trim().slice(-300)}`,\n );\n }\n\n // --output-format json wraps the answer: { result: \"...\", ... }\n const wrapper = JSON.parse(result.stdout) as { result?: string };\n if (typeof wrapper.result !== \"string\") {\n throw new Error(\"Claude CLI returned no result field\");\n }\n return { json: extractJson(wrapper.result) };\n }\n}\n","/**\n * Mock provider for tests and offline development. Responds with scripted\n * results in order, cycling when exhausted. Exported from the public API so\n * downstream consumers can test their own pipelines without a live provider.\n */\nimport type {\n CompleteJSONRequest,\n CompleteJSONResponse,\n InferenceProvider,\n TokenUsage,\n} from \"./types.js\";\n\nexport type MockResponse =\n | { json: unknown; usage?: TokenUsage }\n | { error: string };\n\nexport class MockProvider implements InferenceProvider {\n private calls = 0;\n /** Every request seen, in order — assert against this in tests. */\n public readonly requests: CompleteJSONRequest[] = [];\n\n constructor(\n private readonly responses: MockResponse[],\n private readonly model = \"mock-model\",\n ) {\n if (responses.length === 0) {\n throw new Error(\"MockProvider needs at least one scripted response\");\n }\n }\n\n provider(): string {\n return \"mock\";\n }\n\n modelName(): string {\n return this.model;\n }\n\n completeJSON(req: CompleteJSONRequest): Promise<CompleteJSONResponse> {\n this.requests.push(req);\n const response = this.responses[this.calls % this.responses.length]!;\n this.calls += 1;\n if (\"error\" in response) {\n return Promise.reject(new Error(response.error));\n }\n return Promise.resolve({\n json: response.json,\n usage: response.usage ?? { inputTokens: 500, outputTokens: 100 },\n });\n }\n}\n\n/** Convenience: a scripted response shaped like the canonical judge verdict. */\nexport function mockVerdict(\n match: \"pass\" | \"fail\" | \"partial\",\n confidence: number,\n overrides: Partial<{\n claim: string;\n observed: string;\n reasoning: string;\n }> = {},\n): { json: unknown } {\n return {\n json: {\n claim: overrides.claim ?? \"The assertion under test\",\n observed: overrides.observed ?? \"Observed content\",\n match,\n confidence,\n reasoning: overrides.reasoning ?? \"Scripted mock reasoning\",\n },\n };\n}\n","/**\n * Provider factory over a library-owned `ProviderSpec`.\n *\n * The spec is deliberately NOT any consumer's config type. Every consumer maps\n * its own config into this flat shape, so adding a provider here does not\n * require touching four config schemas, and no consumer has to model its\n * config on another's to reuse this layer (ADR 01000).\n */\nimport { InferenceError } from \"../types.js\";\nimport type { Pricing } from \"../cost.js\";\nimport { AnthropicProvider } from \"./anthropic.js\";\nimport { OpenAICompatProvider } from \"./openai-compat.js\";\nimport { ClaudeCliProvider } from \"./claude-cli.js\";\nimport { MockProvider } from \"./mock.js\";\nimport type { AnthropicProviderOptions } from \"./anthropic.js\";\nimport type { OpenAICompatProviderOptions } from \"./openai-compat.js\";\nimport type { MockResponse } from \"./mock.js\";\nimport type { ExecFn, InferenceProvider } from \"./types.js\";\n\nexport type ProviderName = \"anthropic\" | \"openai\" | \"claude-cli\" | \"mock\";\n\nexport interface ProviderSpec {\n provider: ProviderName;\n /** null/undefined selects the per-provider default. */\n model?: string | null;\n /** Env var NAME holding the API key; null/undefined selects the default. */\n apiKeyEnv?: string | null;\n /** openai only. */\n baseUrl?: string;\n /** claude-cli only: the executable to run. */\n command?: string;\n /** claude-cli only: subprocess timeout. */\n timeoutMs?: number;\n /**\n * Pricing override for this model. Not used to construct the provider —\n * carried here so a consumer passes one object to both `makeProvider` and\n * `pricingFor`.\n */\n pricing?: Pricing;\n /** Provider-specific tuning, ignored by the other providers. */\n anthropic?: AnthropicProviderOptions;\n openai?: OpenAICompatProviderOptions;\n /** Test seam for the claude-cli provider. */\n exec?: ExecFn;\n /** Scripted responses for the mock provider; defaults to a single empty object. */\n mockResponses?: MockResponse[];\n}\n\nexport const DEFAULT_MODELS: Record<ProviderName, string> = {\n anthropic: \"claude-sonnet-4-5\",\n openai: \"gpt-4o-mini\",\n \"claude-cli\": \"claude-sonnet-4-5\",\n mock: \"mock-model\",\n};\n\nconst DEFAULT_API_KEY_ENV: Record<string, string> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n};\n\nexport const DEFAULT_OPENAI_BASE_URL = \"https://api.openai.com/v1\";\n\n/**\n * Resolve the provider name and model WITHOUT constructing the provider —\n * cache keys and pricing need the identity, but construction may require an\n * API key that a fully-cached run never uses.\n */\nexport function resolveProviderIdentity(spec: ProviderSpec): {\n provider: string;\n model: string;\n} {\n return {\n provider: spec.provider,\n model: spec.model ?? DEFAULT_MODELS[spec.provider] ?? \"unknown\",\n };\n}\n\nexport function makeProvider(spec: ProviderSpec): InferenceProvider {\n const { model } = resolveProviderIdentity(spec);\n\n switch (spec.provider) {\n case \"anthropic\":\n return new AnthropicProvider(\n model,\n spec.apiKeyEnv ?? DEFAULT_API_KEY_ENV[\"anthropic\"]!,\n spec.anthropic ?? {},\n );\n case \"openai\":\n return new OpenAICompatProvider(\n spec.baseUrl ?? DEFAULT_OPENAI_BASE_URL,\n model,\n spec.apiKeyEnv ?? DEFAULT_API_KEY_ENV[\"openai\"]!,\n undefined,\n spec.openai ?? {},\n );\n case \"claude-cli\":\n return new ClaudeCliProvider(\n model,\n spec.command ?? \"claude\",\n spec.exec,\n spec.timeoutMs,\n );\n case \"mock\":\n // Offline smoke-testing seam: proposes nothing unless scripted.\n return new MockProvider(spec.mockResponses ?? [{ json: {} }], model);\n default:\n throw new InferenceError(\n `Unknown provider \"${String(spec.provider)}\". Available: ${Object.keys(\n DEFAULT_MODELS,\n ).join(\", \")}.`,\n );\n }\n}\n","/**\n * One schema-validated completion, with a single retry.\n *\n * The invariant every consumer depends on: a run that cannot produce\n * schema-valid JSON after the retry is recorded as an ERROR, not dropped and\n * not coerced. Downstream, an errored run counts against consensus — it can\n * push a result toward human review, but it can never produce a silent pass.\n */\nimport { Ajv2020 } from \"ajv/dist/2020.js\";\nimport type { ValidateFunction } from \"ajv\";\nimport type { InferenceProvider, TokenUsage } from \"./providers/types.js\";\n\n/** One attempt at a schema-constrained completion. */\nexport interface InferenceRun<T = unknown> {\n /** Absent when the run errored (invalid JSON after retry, API failure). */\n result?: T;\n error?: string;\n provider: string;\n model: string;\n cached: boolean;\n usage?: TokenUsage;\n durationMs: number;\n}\n\nexport interface CompleteValidatedOptions {\n provider: InferenceProvider;\n system: string;\n user: string;\n schema: Record<string, unknown>;\n temperature?: number;\n /**\n * Attempts before recording an error. Default 2 (one initial call plus one\n * retry) — matches the behavior all three source projects shipped.\n */\n attempts?: number;\n /**\n * Pre-compiled validator. Compiling Ajv per call is wasteful in an ensemble\n * loop, so `runEnsemble` compiles once and passes it down.\n */\n validate?: ValidateFunction;\n}\n\nconst validatorCache = new WeakMap<object, ValidateFunction>();\n\n/** Compile once per schema object identity — Ajv compilation is not cheap. */\nexport function validatorFor(\n schema: Record<string, unknown>,\n): ValidateFunction {\n const cached = validatorCache.get(schema);\n if (cached) return cached;\n // A fresh Ajv per distinct schema object, not one shared instance: Ajv keeps\n // a registry keyed by `$id`, so a caller that rebuilds an equal schema object\n // per call (spreading VERDICT_SCHEMA to override descriptions, say) misses\n // the identity cache above and would hit \"schema with key or id ... already\n // exists\" on the second compile. Instances are held only by this WeakMap, so\n // they are collected with the schemas that own them.\n const compiled = new Ajv2020({ allErrors: true }).compile(schema);\n validatorCache.set(schema, compiled);\n return compiled;\n}\n\nexport async function completeValidatedJSON<T = unknown>(\n options: CompleteValidatedOptions,\n): Promise<InferenceRun<T>> {\n const {\n provider,\n system,\n user,\n schema,\n temperature = 0,\n attempts = 2,\n } = options;\n const validate = options.validate ?? validatorFor(schema);\n\n const start = Date.now();\n const base = {\n provider: provider.provider(),\n model: provider.modelName(),\n cached: false,\n };\n\n let lastError = \"unknown error\";\n for (let attempt = 0; attempt < attempts; attempt++) {\n try {\n const response = await provider.completeJSON({\n system,\n user,\n schema,\n temperature,\n });\n if (validate(response.json)) {\n return {\n ...base,\n result: response.json as T,\n usage: response.usage,\n durationMs: Date.now() - start,\n };\n }\n lastError = `Response failed schema validation: ${(validate.errors ?? [])\n .map((e) => `${e.instancePath} ${e.message}`)\n .join(\"; \")}`;\n } catch (e) {\n lastError = e instanceof Error ? e.message : String(e);\n }\n }\n return { ...base, error: lastError, durationMs: Date.now() - start };\n}\n","/**\n * Content-addressed JSON cache for inference results, so a cached run replays\n * identically instead of re-billing a nondeterministic call.\n *\n * Key composition stays with the caller: each consumer has a different notion\n * of what should invalidate an entry (page body, prompt version, ensemble\n * size, requested fields). `buildCacheKey` just hashes the parts you name.\n */\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport function sha256(text: string): string {\n return createHash(\"sha256\").update(text, \"utf8\").digest(\"hex\");\n}\n\n/**\n * Hash an ordered list of key parts into a cache key. Long parts (page bodies,\n * rendered traces) should be pre-hashed with `sha256` by the caller so the\n * joined string stays small.\n */\nexport function buildCacheKey(parts: string[]): string {\n // Length-prefixed, not plain-joined: a part that itself contains the\n // separator would otherwise let two different compositions — [\"a|b\", \"c\"]\n // and [\"a\", \"b|c\"] — share one cached result.\n return sha256(parts.map((p) => `${p.length}:${p}`).join(\"|\"));\n}\n\nexport class JsonCache<T> {\n /** Cache-write failures warn once per process, not once per entry. */\n private warned = false;\n\n constructor(\n private readonly dir: string,\n private readonly enabled: boolean = true,\n /** Prefix for the one-time write-failure warning. */\n private readonly label: string = \"inference\",\n ) {}\n\n get(key: string): T | undefined {\n if (!this.enabled) return undefined;\n const path = join(this.dir, `${key}.json`);\n if (!existsSync(path)) return undefined;\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as T;\n } catch {\n return undefined; // Corrupt cache entry — treat as a miss.\n }\n }\n\n set(key: string, value: T): void {\n if (!this.enabled) return;\n // The cache is an optimization: a write failure (read-only workspace, full\n // disk, long path) must never abort a run whose inference already\n // succeeded and was already paid for.\n try {\n mkdirSync(this.dir, { recursive: true });\n writeFileSync(\n join(this.dir, `${key}.json`),\n JSON.stringify(value, null, 2),\n );\n } catch (e) {\n if (!this.warned) {\n this.warned = true;\n console.warn(\n `${this.label}: could not write the cache at ${this.dir} (${\n e instanceof Error ? e.message : String(e)\n }). Continuing without caching.`,\n );\n }\n }\n }\n}\n","/**\n * Cost tracking: token usage priced from a small static table, overridable per\n * model by the caller. Unknown models cost 0 (unknown), never a guess — a\n * fabricated price is worse than an absent one when a budget gate depends on it.\n */\nimport type { TokenUsage } from \"./providers/types.js\";\n\nexport interface Pricing {\n inputPerMTok: number;\n outputPerMTok: number;\n}\n\n/**\n * USD per million tokens. Entries are base names; pinned variants\n * (`claude-sonnet-4-5-20250929`) resolve by prefix.\n */\nexport const PRICE_TABLE: Record<string, Pricing> = {\n \"claude-sonnet-4-5\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-sonnet-4-6\": { inputPerMTok: 3, outputPerMTok: 15 },\n \"claude-haiku-4-5\": { inputPerMTok: 1, outputPerMTok: 5 },\n \"claude-opus-4-8\": { inputPerMTok: 15, outputPerMTok: 75 },\n \"gpt-4o-mini\": { inputPerMTok: 0.15, outputPerMTok: 0.6 },\n \"gpt-4o\": { inputPerMTok: 2.5, outputPerMTok: 10 },\n};\n\nexport function pricingFor(\n model: string,\n override?: Pricing,\n): Pricing | undefined {\n if (override) return override;\n if (PRICE_TABLE[model]) return PRICE_TABLE[model];\n // Match pinned variants like claude-sonnet-4-5-20250929. Longest prefix\n // wins so `claude-sonnet-4-5` never shadows a more specific future entry.\n const base = Object.keys(PRICE_TABLE)\n .filter((k) => model.startsWith(k))\n .sort((a, b) => b.length - a.length)[0];\n return base ? PRICE_TABLE[base] : undefined;\n}\n\nexport function costOfUsage(\n usage: TokenUsage | undefined,\n pricing: Pricing | undefined,\n): number {\n if (!usage || !pricing) return 0;\n return (\n (usage.inputTokens / 1_000_000) * pricing.inputPerMTok +\n (usage.outputTokens / 1_000_000) * pricing.outputPerMTok\n );\n}\n\n/** Sum the cost of a set of runs. Cached runs cost nothing — they made no call. */\nexport function costOfRuns(\n runs: { usage?: TokenUsage; cached?: boolean }[],\n pricing: Pricing | undefined,\n): number {\n if (!pricing) return 0;\n let usd = 0;\n for (const run of runs) {\n if (!run.usage || run.cached) continue;\n usd += costOfUsage(run.usage, pricing);\n }\n return usd;\n}\n","{\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"$id\": \"inference:verdict:0.1\",\n \"title\": \"LLM-as-judge verdict\",\n \"type\": \"object\",\n \"required\": [\"claim\", \"observed\", \"match\", \"confidence\", \"reasoning\"],\n \"properties\": {\n \"claim\": {\n \"type\": \"string\",\n \"description\": \"The specific assertion under evaluation.\"\n },\n \"observed\": {\n \"type\": \"string\",\n \"description\": \"What was actually observed in the subject under evaluation, quoting evidence where possible.\"\n },\n \"match\": {\n \"enum\": [\"pass\", \"fail\", \"partial\"],\n \"description\": \"pass only if the assertion is fully satisfied; partial for partial compliance.\"\n },\n \"confidence\": {\n \"type\": \"number\",\n \"minimum\": 0,\n \"maximum\": 1,\n \"description\": \"Self-reported confidence in this verdict.\"\n },\n \"reasoning\": {\n \"type\": \"string\",\n \"description\": \"Why this conclusion was reached.\"\n }\n },\n \"additionalProperties\": false\n}\n","/**\n * LLM-as-judge types. The verdict shape is the one all consuming projects\n * already share; the canonical schema lives in verdict-schema.json and is\n * overridable per consumer so domain-specific field descriptions survive\n * (ADR 01001).\n */\nimport verdictSchemaJson from \"./verdict-schema.json\" with { type: \"json\" };\nimport type { TokenUsage } from \"../providers/types.js\";\n\nexport const VERDICT_SCHEMA = verdictSchemaJson as Record<string, unknown>;\n\nexport type Match = \"pass\" | \"fail\" | \"partial\";\n\n/** Confidence-zone routing for LLM-judged evals. */\nexport type Zone = \"auto-pass\" | \"auto-fail\" | \"human-review\";\n\nexport interface JudgeVerdict {\n /** The specific assertion under evaluation. */\n claim: string;\n /** What the judge actually observed. */\n observed: string;\n match: Match;\n /** 0.0–1.0 self-reported confidence. */\n confidence: number;\n reasoning: string;\n}\n\n/** One run within an ensemble. */\nexport interface JudgeRun {\n /** Absent when the run errored (invalid JSON after retry, API failure). */\n verdict?: JudgeVerdict;\n error?: string;\n provider: string;\n model: string;\n cached: boolean;\n usage?: TokenUsage;\n durationMs: number;\n}\n\n/** Aggregated outcome of an ensemble of judge runs for one subject. */\nexport interface ConsensusResult {\n runs: JudgeRun[];\n votes: { pass: number; fail: number; partial: number; error: number };\n /** Majority verdict; `partial` counts as fail for the binary outcome. */\n verdict: Match;\n /** Fraction of non-errored runs agreeing with the majority verdict. */\n agreement: number;\n /** Mean confidence across non-errored runs. */\n meanConfidence: number;\n zone: Zone;\n}\n","/**\n * Ensemble consensus math. `partial` counts as fail for the binary outcome\n * but stays visible in the vote counts. Errored runs count against consensus —\n * they can only push a result toward human review, never toward a silent pass.\n */\nimport type { ConsensusResult, JudgeRun, Match } from \"./types.js\";\n\nexport function computeConsensus(\n runs: JudgeRun[],\n): Omit<ConsensusResult, \"zone\"> {\n const votes = { pass: 0, fail: 0, partial: 0, error: 0 };\n let confidenceSum = 0;\n let confidenceCount = 0;\n for (const run of runs) {\n if (!run.verdict) {\n votes.error += 1;\n continue;\n }\n votes[run.verdict.match] += 1;\n confidenceSum += run.verdict.confidence;\n confidenceCount += 1;\n }\n\n const passVotes = votes.pass;\n const failVotes = votes.fail + votes.partial;\n // Binary majority; a tie is not a pass.\n const verdict: Match = passVotes > failVotes ? \"pass\" : \"fail\";\n const graded = passVotes + failVotes;\n const agreement = graded > 0 ? Math.max(passVotes, failVotes) / graded : 0;\n const meanConfidence =\n confidenceCount > 0 ? confidenceSum / confidenceCount : 0;\n\n return { runs, votes, verdict, agreement, meanConfidence };\n}\n","/**\n * Confidence-zone routing: only unanimous, high-confidence ensembles\n * auto-resolve; everything else goes to a human.\n */\nimport type { ConsensusResult, Zone } from \"./types.js\";\n\nexport interface ZoneThresholds {\n autoPass: number;\n autoFail: number;\n}\n\nexport const DEFAULT_ZONES: ZoneThresholds = { autoPass: 0.8, autoFail: 0.8 };\n\nexport function zoneFor(\n consensus: Omit<ConsensusResult, \"zone\">,\n thresholds: ZoneThresholds = DEFAULT_ZONES,\n): Zone {\n const { votes, meanConfidence } = consensus;\n const unanimousPass =\n votes.pass > 0 &&\n votes.fail === 0 &&\n votes.partial === 0 &&\n votes.error === 0;\n const unanimousFail =\n votes.pass === 0 && votes.error === 0 && votes.fail + votes.partial > 0;\n\n if (unanimousPass && meanConfidence >= thresholds.autoPass) return \"auto-pass\";\n if (unanimousFail && meanConfidence >= thresholds.autoFail) return \"auto-fail\";\n return \"human-review\";\n}\n","/**\n * The ensemble judge: N independent runs for one subject, each a fresh request\n * with no shared context (eval isolation), aggregated by consensus and routed\n * through confidence zones.\n *\n * Runs within one ensemble stay sequential on purpose — they are meant to be\n * independent samples, and interleaving them buys nothing. Concurrency belongs\n * one level up, across subjects, where the consumer owns the pool.\n */\nimport { completeValidatedJSON, validatorFor } from \"../complete.js\";\nimport type { JsonCache } from \"../cache.js\";\nimport type { InferenceProvider } from \"../providers/types.js\";\nimport { computeConsensus } from \"./consensus.js\";\nimport { DEFAULT_ZONES, zoneFor, type ZoneThresholds } from \"./zones.js\";\nimport {\n VERDICT_SCHEMA,\n type ConsensusResult,\n type JudgeRun,\n type JudgeVerdict,\n} from \"./types.js\";\n\nexport interface EnsembleOptions {\n provider: InferenceProvider;\n system: string;\n user: string;\n /** Ensemble size; default 3. */\n runs?: number;\n /** Default 0. Nonzero adds noise to verdicts and warns once. */\n temperature?: number;\n /**\n * Verdict schema. Defaults to the canonical one; pass your own to keep\n * domain-specific field descriptions (they measurably steer the model).\n * Must still produce objects matching `JudgeVerdict`.\n */\n schema?: Record<string, unknown>;\n /** Optional result cache. Requires `cacheKey`. */\n cache?: JsonCache<JudgeRun[]>;\n /** Content-addressed key; build it with `buildCacheKey`. */\n cacheKey?: string;\n /** Prefix for warnings, e.g. your tool's name. */\n label?: string;\n}\n\nlet warnedTemperature = false;\n\n/**\n * Run the ensemble and return every run. Cached ensembles replay identically,\n * with each run flagged `cached: true` so cost accounting skips them.\n */\nexport async function runEnsemble(\n options: EnsembleOptions,\n): Promise<JudgeRun[]> {\n const {\n provider,\n system,\n user,\n runs: runCount = 3,\n temperature = 0,\n schema = VERDICT_SCHEMA,\n cache,\n cacheKey,\n label = \"inference\",\n } = options;\n\n if (temperature > 0 && !warnedTemperature) {\n warnedTemperature = true;\n console.warn(\n `${label}: judge temperature is ${temperature} — nonzero temperature adds noise to verdicts; 0 is strongly recommended.`,\n );\n }\n\n if (cache && cacheKey) {\n const hit = cache.get(cacheKey);\n // A cache file can be valid JSON and still be the wrong shape — a\n // truncated write, or an entry from an older cache generation. JsonCache\n // only guards parse failures, so the shape check belongs here; a bad entry\n // is a miss, never a crash.\n if (Array.isArray(hit)) return hit.map((r) => ({ ...r, cached: true }));\n }\n\n // Compile the schema once for the whole ensemble rather than per run.\n const validate = validatorFor(schema);\n\n const results: JudgeRun[] = [];\n for (let i = 0; i < runCount; i++) {\n const run = await completeValidatedJSON<JudgeVerdict>({\n provider,\n system,\n user,\n schema,\n temperature,\n validate,\n });\n // `result` is the generic name at the completion layer; the judge layer\n // calls it `verdict`, which is what consumers persist in their caches.\n const { result, ...rest } = run;\n results.push(result === undefined ? rest : { ...rest, verdict: result });\n }\n\n if (cache && cacheKey) cache.set(cacheKey, results);\n return results;\n}\n\n/** Run the ensemble and aggregate it into a zoned consensus. */\nexport async function judge(\n options: EnsembleOptions & { zones?: ZoneThresholds },\n): Promise<ConsensusResult> {\n const runs = await runEnsemble(options);\n const base = computeConsensus(runs);\n return { ...base, zone: zoneFor(base, options.zones ?? DEFAULT_ZONES) };\n}\n\n/** Test seam: reset the once-per-process temperature warning. */\nexport function resetTemperatureWarning(): void {\n warnedTemperature = false;\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@hawkeyexl/inference",
3
+ "version": "0.0.1",
4
+ "description": "Shared TypeScript LLM inference layer: schema-constrained completion across Anthropic, OpenAI-compatible, and Claude CLI providers, with caching, cost accounting, and an LLM-as-judge ensemble.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/hawkeyexl/inference.git"
22
+ },
23
+ "homepage": "https://github.com/hawkeyexl/inference#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/hawkeyexl/inference/issues"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "engines": {
31
+ "node": ">=24"
32
+ },
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "dev": "tsup --watch",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "typecheck": "tsc --noEmit",
39
+ "commitlint": "commitlint --edit",
40
+ "semantic-release": "semantic-release",
41
+ "prepare": "husky",
42
+ "prepublishOnly": "npm run build"
43
+ },
44
+ "keywords": [
45
+ "llm",
46
+ "inference",
47
+ "anthropic",
48
+ "openai",
49
+ "claude",
50
+ "structured-output",
51
+ "json-schema",
52
+ "llm-as-judge",
53
+ "evals"
54
+ ],
55
+ "license": "MIT",
56
+ "dependencies": {
57
+ "@anthropic-ai/sdk": "^0.70.0",
58
+ "ajv": "^8.17.1",
59
+ "cross-spawn": "^7.0.6"
60
+ },
61
+ "devDependencies": {
62
+ "@commitlint/cli": "^21.2.1",
63
+ "@commitlint/config-conventional": "^21.2.0",
64
+ "@semantic-release/changelog": "^6.0.3",
65
+ "@semantic-release/commit-analyzer": "^13.0.1",
66
+ "@semantic-release/git": "^10.0.1",
67
+ "@semantic-release/github": "^12.0.9",
68
+ "@semantic-release/npm": "^13.1.5",
69
+ "@semantic-release/release-notes-generator": "^14.1.1",
70
+ "@types/cross-spawn": "^6.0.6",
71
+ "@types/node": "^26.0.1",
72
+ "husky": "^9.1.7",
73
+ "semantic-release": "^25.0.8",
74
+ "tsup": "^8.3.5",
75
+ "typescript": "^6.0.3",
76
+ "vitest": "^4.1.9"
77
+ }
78
+ }