memhook 0.2.0

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/LICENSE +21 -0
  3. package/README.md +204 -0
  4. package/dist/bin/memhook.d.ts +16 -0
  5. package/dist/bin/memhook.d.ts.map +1 -0
  6. package/dist/bin/memhook.js +122 -0
  7. package/dist/bin/memhook.js.map +1 -0
  8. package/dist/src/cache.d.ts +30 -0
  9. package/dist/src/cache.d.ts.map +1 -0
  10. package/dist/src/cache.js +80 -0
  11. package/dist/src/cache.js.map +1 -0
  12. package/dist/src/catalog.d.ts +20 -0
  13. package/dist/src/catalog.d.ts.map +1 -0
  14. package/dist/src/catalog.js +152 -0
  15. package/dist/src/catalog.js.map +1 -0
  16. package/dist/src/config.d.ts +60 -0
  17. package/dist/src/config.d.ts.map +1 -0
  18. package/dist/src/config.js +172 -0
  19. package/dist/src/config.js.map +1 -0
  20. package/dist/src/configFile.d.ts +54 -0
  21. package/dist/src/configFile.d.ts.map +1 -0
  22. package/dist/src/configFile.js +51 -0
  23. package/dist/src/configFile.js.map +1 -0
  24. package/dist/src/index.d.ts +20 -0
  25. package/dist/src/index.d.ts.map +1 -0
  26. package/dist/src/index.js +19 -0
  27. package/dist/src/index.js.map +1 -0
  28. package/dist/src/preFilter.d.ts +16 -0
  29. package/dist/src/preFilter.d.ts.map +1 -0
  30. package/dist/src/preFilter.js +40 -0
  31. package/dist/src/preFilter.js.map +1 -0
  32. package/dist/src/providers/anthropic.d.ts +33 -0
  33. package/dist/src/providers/anthropic.d.ts.map +1 -0
  34. package/dist/src/providers/anthropic.js +98 -0
  35. package/dist/src/providers/anthropic.js.map +1 -0
  36. package/dist/src/providers/factory.d.ts +15 -0
  37. package/dist/src/providers/factory.d.ts.map +1 -0
  38. package/dist/src/providers/factory.js +37 -0
  39. package/dist/src/providers/factory.js.map +1 -0
  40. package/dist/src/providers/http.d.ts +34 -0
  41. package/dist/src/providers/http.d.ts.map +1 -0
  42. package/dist/src/providers/http.js +60 -0
  43. package/dist/src/providers/http.js.map +1 -0
  44. package/dist/src/providers/ollama.d.ts +30 -0
  45. package/dist/src/providers/ollama.d.ts.map +1 -0
  46. package/dist/src/providers/ollama.js +89 -0
  47. package/dist/src/providers/ollama.js.map +1 -0
  48. package/dist/src/providers/openai.d.ts +31 -0
  49. package/dist/src/providers/openai.d.ts.map +1 -0
  50. package/dist/src/providers/openai.js +94 -0
  51. package/dist/src/providers/openai.js.map +1 -0
  52. package/dist/src/providers/types.d.ts +48 -0
  53. package/dist/src/providers/types.d.ts.map +1 -0
  54. package/dist/src/providers/types.js +18 -0
  55. package/dist/src/providers/types.js.map +1 -0
  56. package/dist/src/router.d.ts +32 -0
  57. package/dist/src/router.d.ts.map +1 -0
  58. package/dist/src/router.js +342 -0
  59. package/dist/src/router.js.map +1 -0
  60. package/dist/src/version.d.ts +13 -0
  61. package/dist/src/version.d.ts.map +1 -0
  62. package/dist/src/version.js +13 -0
  63. package/dist/src/version.js.map +1 -0
  64. package/package.json +88 -0
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Anthropic provider — Haiku 4.5 default for memhook.
3
+ *
4
+ * Wire format:
5
+ * POST https://api.anthropic.com/v1/messages
6
+ * Headers: x-api-key, anthropic-version
7
+ * Body: { model, max_tokens, system: [{type, text, cache_control}], messages }
8
+ *
9
+ * Anthropic-specific concepts (ephemeral prompt caching, `anthropic-beta`
10
+ * headers) are passed via `AnthropicProviderOptions` at construction time, NOT
11
+ * through the shared `SelectionRequest` — so the OpenAI and Ollama adapters
12
+ * never see them. Cache control TTL "1h" is GA in 2026; no beta header is
13
+ * required, but a non-empty `betaHeaders` list still maps to `anthropic-beta`
14
+ * for forward-compat.
15
+ *
16
+ * Retry: single retry on 429/503 with 500ms backoff (max 2 attempts), via the
17
+ * shared `postJsonWithRetry` transport.
18
+ */
19
+ import type { Provider, ProviderConfig, SelectionRequest, SelectionResponse } from "./types.js";
20
+ /** Anthropic-only knobs, kept off the shared provider interface. */
21
+ export interface AnthropicProviderOptions {
22
+ betaHeaders?: string[];
23
+ cacheControlTtl?: "5m" | "1h";
24
+ }
25
+ export declare class AnthropicProvider implements Provider {
26
+ private readonly config;
27
+ private readonly options;
28
+ readonly name = "anthropic";
29
+ private readonly apiKey;
30
+ constructor(config: ProviderConfig, options?: AnthropicProviderOptions);
31
+ select(req: SelectionRequest): Promise<SelectionResponse>;
32
+ }
33
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/providers/anthropic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAGpB,oEAAoE;AACpE,MAAM,WAAW,wBAAwB;IACvC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;CAC/B;AAOD,qBAAa,iBAAkB,YAAW,QAAQ;IAK9C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,QAAQ,CAAC,IAAI,eAAe;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAGb,MAAM,EAAE,cAAc,EACtB,OAAO,GAAE,wBAA6B;IAOnD,MAAM,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAuChE"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Anthropic provider — Haiku 4.5 default for memhook.
3
+ *
4
+ * Wire format:
5
+ * POST https://api.anthropic.com/v1/messages
6
+ * Headers: x-api-key, anthropic-version
7
+ * Body: { model, max_tokens, system: [{type, text, cache_control}], messages }
8
+ *
9
+ * Anthropic-specific concepts (ephemeral prompt caching, `anthropic-beta`
10
+ * headers) are passed via `AnthropicProviderOptions` at construction time, NOT
11
+ * through the shared `SelectionRequest` — so the OpenAI and Ollama adapters
12
+ * never see them. Cache control TTL "1h" is GA in 2026; no beta header is
13
+ * required, but a non-empty `betaHeaders` list still maps to `anthropic-beta`
14
+ * for forward-compat.
15
+ *
16
+ * Retry: single retry on 429/503 with 500ms backoff (max 2 attempts), via the
17
+ * shared `postJsonWithRetry` transport.
18
+ */
19
+ import { postJsonWithRetry } from "./http.js";
20
+ const ANTHROPIC_VERSION = "2023-06-01";
21
+ const DEFAULT_BASE_URL = "https://api.anthropic.com/v1/messages";
22
+ const RETRY_BACKOFF_MS = 500;
23
+ const RETRY_STATUSES = [429, 503];
24
+ export class AnthropicProvider {
25
+ config;
26
+ options;
27
+ name = "anthropic";
28
+ apiKey;
29
+ constructor(config, options = {}) {
30
+ this.config = config;
31
+ this.options = options;
32
+ if (!config.apiKey)
33
+ throw new Error("AnthropicProvider: apiKey is required");
34
+ if (!config.model)
35
+ throw new Error("AnthropicProvider: model is required");
36
+ this.apiKey = config.apiKey;
37
+ }
38
+ async select(req) {
39
+ const ttl = this.options.cacheControlTtl;
40
+ const body = JSON.stringify({
41
+ model: this.config.model,
42
+ max_tokens: req.maxOutputTokens,
43
+ system: [
44
+ {
45
+ type: "text",
46
+ text: req.systemPrompt,
47
+ cache_control: ttl ? { type: "ephemeral", ttl } : { type: "ephemeral" },
48
+ },
49
+ ],
50
+ messages: [{ role: "user", content: req.userPrompt }],
51
+ });
52
+ const headers = {
53
+ "Content-Type": "application/json",
54
+ "x-api-key": this.apiKey,
55
+ "anthropic-version": ANTHROPIC_VERSION,
56
+ };
57
+ const betas = this.options.betaHeaders ?? [];
58
+ if (betas.length > 0)
59
+ headers["anthropic-beta"] = betas.join(",");
60
+ const { json, httpStatus, latencyMs } = await postJsonWithRetry({
61
+ url: this.config.baseUrl ?? DEFAULT_BASE_URL,
62
+ headers,
63
+ body,
64
+ timeoutMs: req.timeoutMs,
65
+ retryStatuses: RETRY_STATUSES,
66
+ backoffMs: RETRY_BACKOFF_MS,
67
+ });
68
+ return {
69
+ rawText: extractText(json),
70
+ usage: extractUsage(json),
71
+ latencyMs,
72
+ httpStatus,
73
+ };
74
+ }
75
+ }
76
+ function extractText(json) {
77
+ if (!json)
78
+ return "";
79
+ const content = json["content"];
80
+ if (!Array.isArray(content))
81
+ return "";
82
+ const first = content[0];
83
+ return typeof first?.text === "string" ? first.text : "";
84
+ }
85
+ function extractUsage(json) {
86
+ const usage = json?.["usage"] ?? {};
87
+ const num = (key) => {
88
+ const v = usage[key];
89
+ return typeof v === "number" ? v : 0;
90
+ };
91
+ return {
92
+ inputTokens: num("input_tokens"),
93
+ outputTokens: num("output_tokens"),
94
+ cacheCreateTokens: num("cache_creation_input_tokens"),
95
+ cacheReadTokens: num("cache_read_input_tokens"),
96
+ };
97
+ }
98
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/providers/anthropic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Provider factory — builds the configured provider from `MemhookConfig`.
3
+ *
4
+ * The router imports only this factory, never the concrete adapter classes, so
5
+ * provider selection lives in exactly one place. The `never` default arm gives
6
+ * a compile error if a new `provider.type` union member is added without a
7
+ * matching case here.
8
+ *
9
+ * Construction may throw (e.g. a required field missing) — the router wraps the
10
+ * call in try/catch and falls back to empty `additionalContext` (fail-soft).
11
+ */
12
+ import type { Provider } from "./types.js";
13
+ import type { MemhookConfig } from "../config.js";
14
+ export declare function createProvider(cfg: MemhookConfig, apiKey: string | undefined): Provider;
15
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/providers/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAKlD,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,CAsBvF"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Provider factory — builds the configured provider from `MemhookConfig`.
3
+ *
4
+ * The router imports only this factory, never the concrete adapter classes, so
5
+ * provider selection lives in exactly one place. The `never` default arm gives
6
+ * a compile error if a new `provider.type` union member is added without a
7
+ * matching case here.
8
+ *
9
+ * Construction may throw (e.g. a required field missing) — the router wraps the
10
+ * call in try/catch and falls back to empty `additionalContext` (fail-soft).
11
+ */
12
+ import { AnthropicProvider } from "./anthropic.js";
13
+ import { OpenAIProvider } from "./openai.js";
14
+ import { OllamaProvider } from "./ollama.js";
15
+ export function createProvider(cfg, apiKey) {
16
+ const base = {
17
+ model: cfg.provider.model,
18
+ ...(apiKey !== undefined && { apiKey }),
19
+ ...(cfg.provider.baseUrl !== undefined && { baseUrl: cfg.provider.baseUrl }),
20
+ };
21
+ switch (cfg.provider.type) {
22
+ case "anthropic":
23
+ return new AnthropicProvider(base, {
24
+ betaHeaders: cfg.provider.betaHeaders,
25
+ cacheControlTtl: cfg.selection.cacheControlTtl,
26
+ });
27
+ case "openai":
28
+ return new OpenAIProvider(base);
29
+ case "ollama":
30
+ return new OllamaProvider(base);
31
+ default: {
32
+ const exhaustive = cfg.provider.type;
33
+ throw new Error(`createProvider: unknown provider type ${String(exhaustive)}`);
34
+ }
35
+ }
36
+ }
37
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/providers/factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,UAAU,cAAc,CAAC,GAAkB,EAAE,MAA0B;IAC3E,MAAM,IAAI,GAAmB;QAC3B,KAAK,EAAE,GAAG,CAAC,QAAQ,CAAC,KAAK;QACzB,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;QACvC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;KAC7E,CAAC;IAEF,QAAQ,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC1B,KAAK,WAAW;YACd,OAAO,IAAI,iBAAiB,CAAC,IAAI,EAAE;gBACjC,WAAW,EAAE,GAAG,CAAC,QAAQ,CAAC,WAAW;gBACrC,eAAe,EAAE,GAAG,CAAC,SAAS,CAAC,eAAe;aAC/C,CAAC,CAAC;QACL,KAAK,QAAQ;YACX,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;QAClC,KAAK,QAAQ;YACX,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;QAClC,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,UAAU,GAAU,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,yCAAyC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shared HTTP transport for providers — one audited network path.
3
+ *
4
+ * Every provider POSTs JSON and wants the same behaviour: an AbortController
5
+ * timeout, a single retry on a transient failure (network error or a
6
+ * retryable status), latency measurement, and a tolerant JSON parse that
7
+ * never throws on a malformed body. Centralising it keeps the fail-soft
8
+ * contract verifiable in one place (failsoft-auditor rules 4 + 7) instead of
9
+ * being re-implemented per adapter.
10
+ *
11
+ * Semantics (identical to memhook v0.1's Anthropic path):
12
+ * - max 2 attempts.
13
+ * - attempt 1 throws (network error) -> backoff, retry.
14
+ * - attempt 1 returns a retryable status -> backoff, retry.
15
+ * - attempt 2 throws -> rethrow (caller's try/catch -> fail-soft).
16
+ * - otherwise return the parsed body (or null if the body wasn't JSON).
17
+ */
18
+ export interface PostJsonOptions {
19
+ url: string;
20
+ headers: Record<string, string>;
21
+ body: string;
22
+ timeoutMs: number;
23
+ /** HTTP statuses worth a single retry (e.g. 429, 503). */
24
+ retryStatuses: readonly number[];
25
+ backoffMs: number;
26
+ }
27
+ export interface RawHttpResult {
28
+ /** Parsed JSON body, or null when the body was absent/not valid JSON. */
29
+ json: Record<string, unknown> | null;
30
+ httpStatus: number;
31
+ latencyMs: number;
32
+ }
33
+ export declare function postJsonWithRetry(opts: PostJsonOptions): Promise<RawHttpResult>;
34
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/providers/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC,CAuCrF"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Shared HTTP transport for providers — one audited network path.
3
+ *
4
+ * Every provider POSTs JSON and wants the same behaviour: an AbortController
5
+ * timeout, a single retry on a transient failure (network error or a
6
+ * retryable status), latency measurement, and a tolerant JSON parse that
7
+ * never throws on a malformed body. Centralising it keeps the fail-soft
8
+ * contract verifiable in one place (failsoft-auditor rules 4 + 7) instead of
9
+ * being re-implemented per adapter.
10
+ *
11
+ * Semantics (identical to memhook v0.1's Anthropic path):
12
+ * - max 2 attempts.
13
+ * - attempt 1 throws (network error) -> backoff, retry.
14
+ * - attempt 1 returns a retryable status -> backoff, retry.
15
+ * - attempt 2 throws -> rethrow (caller's try/catch -> fail-soft).
16
+ * - otherwise return the parsed body (or null if the body wasn't JSON).
17
+ */
18
+ export async function postJsonWithRetry(opts) {
19
+ let last = null;
20
+ for (let attempt = 1; attempt <= 2; attempt++) {
21
+ const started = Date.now();
22
+ const controller = new AbortController();
23
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs);
24
+ let resp;
25
+ try {
26
+ resp = await fetch(opts.url, {
27
+ method: "POST",
28
+ headers: opts.headers,
29
+ body: opts.body,
30
+ signal: controller.signal,
31
+ });
32
+ }
33
+ catch (err) {
34
+ clearTimeout(timer);
35
+ if (attempt === 1) {
36
+ await sleep(opts.backoffMs);
37
+ continue;
38
+ }
39
+ throw err;
40
+ }
41
+ clearTimeout(timer);
42
+ const latencyMs = Date.now() - started;
43
+ const status = resp.status;
44
+ if (attempt === 1 && opts.retryStatuses.includes(status)) {
45
+ await sleep(opts.backoffMs);
46
+ continue;
47
+ }
48
+ const json = (await resp.json().catch(() => null));
49
+ last = { json, httpStatus: status, latencyMs };
50
+ break;
51
+ }
52
+ if (!last) {
53
+ throw new Error("postJsonWithRetry: no response after retries");
54
+ }
55
+ return last;
56
+ }
57
+ function sleep(ms) {
58
+ return new Promise((resolve) => setTimeout(resolve, ms));
59
+ }
60
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/providers/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAmBH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,IAAqB;IAC3D,IAAI,IAAI,GAAyB,IAAI,CAAC;IACtC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACnE,IAAI,IAAc,CAAC;QACnB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;gBAClB,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC5B,SAAS;YACX,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QACD,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAE3B,IAAI,OAAO,KAAK,CAAC,IAAI,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACzD,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5B,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAmC,CAAC;QACrF,IAAI,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QAC/C,MAAM;IACR,CAAC;IACD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Ollama provider — local models, `llama3.1` default.
3
+ *
4
+ * Wire format (native chat endpoint, NOT the OpenAI-compat layer):
5
+ * POST http://localhost:11434/api/chat
6
+ * No Authorization header (local Ollama needs no key).
7
+ * Body: { model, messages, stream:false, format:"json", options:{temperature,num_predict} }
8
+ *
9
+ * The native `/api/chat` is preferred over `/v1/chat/completions` because it
10
+ * exposes deterministic `options` first-class, returns a flat
11
+ * `message.content` + top-level token counts (matching memhook's response
12
+ * shape), and requires no dummy `api_key` the OpenAI-compat clients demand.
13
+ * `stream:false` forces a single JSON body (otherwise the response is
14
+ * newline-delimited chunks that would break parsing). `format:"json"` nudges
15
+ * weaker local models toward parseable output; the router's `extractJsonArray`
16
+ * still pulls the basename array out tolerantly.
17
+ *
18
+ * Fail-soft notes: a missing model (404 `model 'x' not found`) or a stopped
19
+ * daemon (ECONNREFUSED) surfaces as empty `rawText` / a thrown fetch, both of
20
+ * which the router treats as an empty selection. Cold model load can be slow,
21
+ * so the config layer gives Ollama a more generous default timeout.
22
+ */
23
+ import type { Provider, ProviderConfig, SelectionRequest, SelectionResponse } from "./types.js";
24
+ export declare class OllamaProvider implements Provider {
25
+ private readonly config;
26
+ readonly name = "ollama";
27
+ constructor(config: ProviderConfig);
28
+ select(req: SelectionRequest): Promise<SelectionResponse>;
29
+ }
30
+ //# sourceMappingURL=ollama.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.d.ts","sourceRoot":"","sources":["../../../src/providers/ollama.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAUpB,qBAAa,cAAe,YAAW,QAAQ;IAGjC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFnC,QAAQ,CAAC,IAAI,YAAY;gBAEI,MAAM,EAAE,cAAc;IAI7C,MAAM,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAmChE"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Ollama provider — local models, `llama3.1` default.
3
+ *
4
+ * Wire format (native chat endpoint, NOT the OpenAI-compat layer):
5
+ * POST http://localhost:11434/api/chat
6
+ * No Authorization header (local Ollama needs no key).
7
+ * Body: { model, messages, stream:false, format:"json", options:{temperature,num_predict} }
8
+ *
9
+ * The native `/api/chat` is preferred over `/v1/chat/completions` because it
10
+ * exposes deterministic `options` first-class, returns a flat
11
+ * `message.content` + top-level token counts (matching memhook's response
12
+ * shape), and requires no dummy `api_key` the OpenAI-compat clients demand.
13
+ * `stream:false` forces a single JSON body (otherwise the response is
14
+ * newline-delimited chunks that would break parsing). `format:"json"` nudges
15
+ * weaker local models toward parseable output; the router's `extractJsonArray`
16
+ * still pulls the basename array out tolerantly.
17
+ *
18
+ * Fail-soft notes: a missing model (404 `model 'x' not found`) or a stopped
19
+ * daemon (ECONNREFUSED) surfaces as empty `rawText` / a thrown fetch, both of
20
+ * which the router treats as an empty selection. Cold model load can be slow,
21
+ * so the config layer gives Ollama a more generous default timeout.
22
+ */
23
+ import { postJsonWithRetry } from "./http.js";
24
+ const DEFAULT_BASE_URL = "http://localhost:11434/api/chat";
25
+ const RETRY_BACKOFF_MS = 500;
26
+ // No HTTP status is worth retrying for a local daemon (404 model-not-found /
27
+ // 400 won't fix on retry); the shared transport still retries once on a thrown
28
+ // network error, covering a transient connection blip.
29
+ const RETRY_STATUSES = [];
30
+ export class OllamaProvider {
31
+ config;
32
+ name = "ollama";
33
+ constructor(config) {
34
+ this.config = config;
35
+ if (!config.model)
36
+ throw new Error("OllamaProvider: model is required");
37
+ }
38
+ async select(req) {
39
+ const body = JSON.stringify({
40
+ model: this.config.model,
41
+ messages: [
42
+ { role: "system", content: req.systemPrompt },
43
+ { role: "user", content: req.userPrompt },
44
+ ],
45
+ stream: false,
46
+ format: "json",
47
+ options: {
48
+ temperature: 0,
49
+ num_predict: req.maxOutputTokens,
50
+ },
51
+ });
52
+ const headers = {
53
+ "Content-Type": "application/json",
54
+ };
55
+ const { json, httpStatus, latencyMs } = await postJsonWithRetry({
56
+ url: this.config.baseUrl ?? DEFAULT_BASE_URL,
57
+ headers,
58
+ body,
59
+ timeoutMs: req.timeoutMs,
60
+ retryStatuses: RETRY_STATUSES,
61
+ backoffMs: RETRY_BACKOFF_MS,
62
+ });
63
+ return {
64
+ rawText: extractText(json),
65
+ usage: extractUsage(json),
66
+ latencyMs,
67
+ httpStatus,
68
+ };
69
+ }
70
+ }
71
+ function extractText(json) {
72
+ if (!json)
73
+ return "";
74
+ const message = json["message"];
75
+ return typeof message?.content === "string" ? message.content : "";
76
+ }
77
+ function extractUsage(json) {
78
+ const num = (key) => {
79
+ const v = json?.[key];
80
+ return typeof v === "number" ? v : 0;
81
+ };
82
+ return {
83
+ inputTokens: num("prompt_eval_count"),
84
+ outputTokens: num("eval_count"),
85
+ cacheCreateTokens: 0,
86
+ cacheReadTokens: 0,
87
+ };
88
+ }
89
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../../src/providers/ollama.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AASH,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE9C,MAAM,gBAAgB,GAAG,iCAAiC,CAAC;AAC3D,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,6EAA6E;AAC7E,+EAA+E;AAC/E,uDAAuD;AACvD,MAAM,cAAc,GAAG,EAAW,CAAC;AAEnC,MAAM,OAAO,cAAc;IAGI;IAFpB,IAAI,GAAG,QAAQ,CAAC;IAEzB,YAA6B,MAAsB;QAAtB,WAAM,GAAN,MAAM,CAAgB;QACjD,IAAI,CAAC,MAAM,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IAC1E,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAqB;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YAC1B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;YACxB,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,CAAC,YAAY,EAAE;gBAC7C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,UAAU,EAAE;aAC1C;YACD,MAAM,EAAE,KAAK;YACb,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,WAAW,EAAE,CAAC;gBACd,WAAW,EAAE,GAAG,CAAC,eAAe;aACjC;SACF,CAAC,CAAC;QAEH,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;SACnC,CAAC;QAEF,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,iBAAiB,CAAC;YAC9D,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,gBAAgB;YAC5C,OAAO;YACP,IAAI;YACJ,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,aAAa,EAAE,cAAc;YAC7B,SAAS,EAAE,gBAAgB;SAC5B,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC;YAC1B,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC;YACzB,SAAS;YACT,UAAU;SACX,CAAC;IACJ,CAAC;CACF;AAED,SAAS,WAAW,CAAC,IAAoC;IACvD,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAsC,CAAC;IACrE,OAAO,OAAO,OAAO,EAAE,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;AACrE,CAAC;AAED,SAAS,YAAY,CAAC,IAAoC;IACxD,MAAM,GAAG,GAAG,CAAC,GAAW,EAAU,EAAE;QAClC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC;IACF,OAAO;QACL,WAAW,EAAE,GAAG,CAAC,mBAAmB,CAAC;QACrC,YAAY,EAAE,GAAG,CAAC,YAAY,CAAC;QAC/B,iBAAiB,EAAE,CAAC;QACpB,eAAe,EAAE,CAAC;KACnB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * OpenAI provider — `gpt-4o-mini` default.
3
+ *
4
+ * Wire format:
5
+ * POST https://api.openai.com/v1/chat/completions
6
+ * Headers: Authorization: Bearer <key>
7
+ * Body: { model, messages: [{role:system},{role:user}], max_tokens, temperature }
8
+ *
9
+ * The static catalog is sent as the leading `system` message and the variable
10
+ * user prompt last, so OpenAI's automatic prompt caching (exact-prefix match,
11
+ * ≥1024 tokens) can engage on a large catalog with no per-request flag.
12
+ *
13
+ * We deliberately do NOT set `response_format`: the router's `extractJsonArray`
14
+ * already tolerantly pulls the basename array out of the raw text (and the
15
+ * shared system prompt asks for a bare JSON array, which JSON-object mode would
16
+ * fight). This keeps the adapter symmetric with Anthropic/Ollama — every
17
+ * provider returns `rawText` and the router owns parsing. `gpt-4o-mini` is a
18
+ * non-reasoning model, so plain `max_tokens` + `temperature: 0` apply (reasoning
19
+ * models would require `max_completion_tokens` and reject `temperature`).
20
+ *
21
+ * Retry: single retry on 429/5xx with 500ms backoff, via the shared transport.
22
+ */
23
+ import type { Provider, ProviderConfig, SelectionRequest, SelectionResponse } from "./types.js";
24
+ export declare class OpenAIProvider implements Provider {
25
+ private readonly config;
26
+ readonly name = "openai";
27
+ private readonly apiKey;
28
+ constructor(config: ProviderConfig);
29
+ select(req: SelectionRequest): Promise<SelectionResponse>;
30
+ }
31
+ //# sourceMappingURL=openai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../../../src/providers/openai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EAElB,MAAM,YAAY,CAAC;AAOpB,qBAAa,cAAe,YAAW,QAAQ;IAIjC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHnC,QAAQ,CAAC,IAAI,YAAY;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEH,MAAM,EAAE,cAAc;IAM7C,MAAM,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC;CAgChE"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * OpenAI provider — `gpt-4o-mini` default.
3
+ *
4
+ * Wire format:
5
+ * POST https://api.openai.com/v1/chat/completions
6
+ * Headers: Authorization: Bearer <key>
7
+ * Body: { model, messages: [{role:system},{role:user}], max_tokens, temperature }
8
+ *
9
+ * The static catalog is sent as the leading `system` message and the variable
10
+ * user prompt last, so OpenAI's automatic prompt caching (exact-prefix match,
11
+ * ≥1024 tokens) can engage on a large catalog with no per-request flag.
12
+ *
13
+ * We deliberately do NOT set `response_format`: the router's `extractJsonArray`
14
+ * already tolerantly pulls the basename array out of the raw text (and the
15
+ * shared system prompt asks for a bare JSON array, which JSON-object mode would
16
+ * fight). This keeps the adapter symmetric with Anthropic/Ollama — every
17
+ * provider returns `rawText` and the router owns parsing. `gpt-4o-mini` is a
18
+ * non-reasoning model, so plain `max_tokens` + `temperature: 0` apply (reasoning
19
+ * models would require `max_completion_tokens` and reject `temperature`).
20
+ *
21
+ * Retry: single retry on 429/5xx with 500ms backoff, via the shared transport.
22
+ */
23
+ import { postJsonWithRetry } from "./http.js";
24
+ const DEFAULT_BASE_URL = "https://api.openai.com/v1/chat/completions";
25
+ const RETRY_BACKOFF_MS = 500;
26
+ const RETRY_STATUSES = [429, 500, 502, 503, 504];
27
+ export class OpenAIProvider {
28
+ config;
29
+ name = "openai";
30
+ apiKey;
31
+ constructor(config) {
32
+ this.config = config;
33
+ if (!config.apiKey)
34
+ throw new Error("OpenAIProvider: apiKey is required");
35
+ if (!config.model)
36
+ throw new Error("OpenAIProvider: model is required");
37
+ this.apiKey = config.apiKey;
38
+ }
39
+ async select(req) {
40
+ const body = JSON.stringify({
41
+ model: this.config.model,
42
+ messages: [
43
+ { role: "system", content: req.systemPrompt },
44
+ { role: "user", content: req.userPrompt },
45
+ ],
46
+ max_tokens: req.maxOutputTokens,
47
+ temperature: 0,
48
+ });
49
+ const headers = {
50
+ "Content-Type": "application/json",
51
+ Authorization: `Bearer ${this.apiKey}`,
52
+ };
53
+ const { json, httpStatus, latencyMs } = await postJsonWithRetry({
54
+ url: this.config.baseUrl ?? DEFAULT_BASE_URL,
55
+ headers,
56
+ body,
57
+ timeoutMs: req.timeoutMs,
58
+ retryStatuses: RETRY_STATUSES,
59
+ backoffMs: RETRY_BACKOFF_MS,
60
+ });
61
+ return {
62
+ rawText: extractText(json),
63
+ usage: extractUsage(json),
64
+ latencyMs,
65
+ httpStatus,
66
+ };
67
+ }
68
+ }
69
+ function extractText(json) {
70
+ if (!json)
71
+ return "";
72
+ const choices = json["choices"];
73
+ if (!Array.isArray(choices))
74
+ return "";
75
+ const first = choices[0];
76
+ const content = first?.message?.content;
77
+ return typeof content === "string" ? content : "";
78
+ }
79
+ function extractUsage(json) {
80
+ const usage = json?.["usage"] ?? {};
81
+ const num = (key) => {
82
+ const v = usage[key];
83
+ return typeof v === "number" ? v : 0;
84
+ };
85
+ const details = usage["prompt_tokens_details"] ?? {};
86
+ const cachedTokens = typeof details["cached_tokens"] === "number" ? details["cached_tokens"] : 0;
87
+ return {
88
+ inputTokens: num("prompt_tokens"),
89
+ outputTokens: num("completion_tokens"),
90
+ cacheCreateTokens: 0,
91
+ cacheReadTokens: cachedTokens,
92
+ };
93
+ }
94
+ //# sourceMappingURL=openai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src/providers/openai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Provider interface — memhook abstracts the LLM call behind a common shape.
3
+ *
4
+ * The contract is deliberately provider-agnostic: a provider takes a
5
+ * `SelectionRequest` (system prompt + user prompt + caps) and returns a
6
+ * normalised `SelectionResponse` (raw text + token usage + latency). The
7
+ * router owns parsing the raw text into a basename array, so every provider
8
+ * looks identical from the pipeline's point of view.
9
+ *
10
+ * Provider-specific concepts must NOT leak here. Anthropic's ephemeral cache
11
+ * control and `anthropic-beta` headers live in `AnthropicProviderOptions`
12
+ * (passed to the Anthropic adapter's constructor), never on this interface.
13
+ *
14
+ * v0.2 ships AnthropicProvider, OpenAIProvider and OllamaProvider, built via
15
+ * `createProvider()` in `factory.ts`.
16
+ */
17
+ export interface ProviderConfig {
18
+ /** API key. Optional — local providers (Ollama) require none. */
19
+ apiKey?: string;
20
+ model: string;
21
+ /** Override the provider's default API endpoint. */
22
+ baseUrl?: string;
23
+ }
24
+ export interface SelectionRequest {
25
+ systemPrompt: string;
26
+ userPrompt: string;
27
+ maxOutputTokens: number;
28
+ timeoutMs: number;
29
+ }
30
+ export interface UsageBreakdown {
31
+ inputTokens: number;
32
+ outputTokens: number;
33
+ /** Cache-write tokens. 0 for providers without explicit cache control. */
34
+ cacheCreateTokens: number;
35
+ /** Cache-read tokens (Anthropic `cache_read`, OpenAI `cached_tokens`). */
36
+ cacheReadTokens: number;
37
+ }
38
+ export interface SelectionResponse {
39
+ rawText: string;
40
+ usage: UsageBreakdown;
41
+ latencyMs: number;
42
+ httpStatus: number;
43
+ }
44
+ export interface Provider {
45
+ readonly name: string;
46
+ select(req: SelectionRequest): Promise<SelectionResponse>;
47
+ }
48
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,0EAA0E;IAC1E,iBAAiB,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,cAAc,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC3D"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Provider interface — memhook abstracts the LLM call behind a common shape.
3
+ *
4
+ * The contract is deliberately provider-agnostic: a provider takes a
5
+ * `SelectionRequest` (system prompt + user prompt + caps) and returns a
6
+ * normalised `SelectionResponse` (raw text + token usage + latency). The
7
+ * router owns parsing the raw text into a basename array, so every provider
8
+ * looks identical from the pipeline's point of view.
9
+ *
10
+ * Provider-specific concepts must NOT leak here. Anthropic's ephemeral cache
11
+ * control and `anthropic-beta` headers live in `AnthropicProviderOptions`
12
+ * (passed to the Anthropic adapter's constructor), never on this interface.
13
+ *
14
+ * v0.2 ships AnthropicProvider, OpenAIProvider and OllamaProvider, built via
15
+ * `createProvider()` in `factory.ts`.
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=types.js.map