graphile-llm 0.7.3 → 0.8.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 (53) hide show
  1. package/__tests__/graphile-llm.test.js +6 -4
  2. package/chat.d.ts +5 -5
  3. package/chat.js +8 -16
  4. package/config-cache.d.ts +77 -0
  5. package/config-cache.js +148 -0
  6. package/embedder.d.ts +5 -5
  7. package/embedder.js +8 -16
  8. package/env.d.ts +31 -0
  9. package/env.js +52 -0
  10. package/esm/__tests__/graphile-llm.test.js +6 -4
  11. package/esm/chat.d.ts +5 -5
  12. package/esm/chat.js +8 -16
  13. package/esm/config-cache.d.ts +77 -0
  14. package/esm/config-cache.js +143 -0
  15. package/esm/embedder.d.ts +5 -5
  16. package/esm/embedder.js +8 -16
  17. package/esm/env.d.ts +31 -0
  18. package/esm/env.js +49 -0
  19. package/esm/index.d.ts +10 -1
  20. package/esm/index.js +11 -1
  21. package/esm/metering.d.ts +114 -0
  22. package/esm/metering.js +358 -0
  23. package/esm/plugins/agent-discovery-plugin.d.ts +29 -0
  24. package/esm/plugins/agent-discovery-plugin.js +65 -0
  25. package/esm/plugins/llm-module-plugin.d.ts +10 -1
  26. package/esm/plugins/llm-module-plugin.js +11 -3
  27. package/esm/plugins/metering-plugin.d.ts +42 -0
  28. package/esm/plugins/metering-plugin.js +175 -0
  29. package/esm/plugins/text-mutation-plugin.d.ts +4 -0
  30. package/esm/plugins/text-mutation-plugin.js +11 -1
  31. package/esm/plugins/text-search-plugin.d.ts +4 -0
  32. package/esm/plugins/text-search-plugin.js +13 -1
  33. package/esm/preset.d.ts +21 -1
  34. package/esm/preset.js +29 -2
  35. package/esm/types.d.ts +47 -6
  36. package/index.d.ts +10 -1
  37. package/index.js +23 -2
  38. package/metering.d.ts +114 -0
  39. package/metering.js +365 -0
  40. package/package.json +15 -15
  41. package/plugins/agent-discovery-plugin.d.ts +29 -0
  42. package/plugins/agent-discovery-plugin.js +69 -0
  43. package/plugins/llm-module-plugin.d.ts +10 -1
  44. package/plugins/llm-module-plugin.js +11 -3
  45. package/plugins/metering-plugin.d.ts +42 -0
  46. package/plugins/metering-plugin.js +178 -0
  47. package/plugins/text-mutation-plugin.d.ts +4 -0
  48. package/plugins/text-mutation-plugin.js +11 -1
  49. package/plugins/text-search-plugin.d.ts +4 -0
  50. package/plugins/text-search-plugin.js +13 -1
  51. package/preset.d.ts +21 -1
  52. package/preset.js +29 -2
  53. package/types.d.ts +47 -6
@@ -109,11 +109,12 @@ describe('Embedder abstraction', () => {
109
109
  afterEach(() => {
110
110
  process.env = originalEnv;
111
111
  });
112
- it('returns null when EMBEDDER_PROVIDER is not set', () => {
112
+ it('returns default ollama embedder when EMBEDDER_PROVIDER is not set', () => {
113
113
  process.env = { ...originalEnv };
114
114
  delete process.env.EMBEDDER_PROVIDER;
115
115
  const embedder = (0, embedder_1.buildEmbedderFromEnv)();
116
- expect(embedder).toBeNull();
116
+ expect(embedder).not.toBeNull();
117
+ expect(typeof embedder).toBe('function');
117
118
  });
118
119
  it('builds embedder from environment variables', () => {
119
120
  process.env = {
@@ -420,11 +421,12 @@ describe('Chat completion abstraction', () => {
420
421
  afterEach(() => {
421
422
  process.env = originalEnv;
422
423
  });
423
- it('returns null when CHAT_PROVIDER is not set', () => {
424
+ it('returns default ollama chat completer when CHAT_PROVIDER is not set', () => {
424
425
  process.env = { ...originalEnv };
425
426
  delete process.env.CHAT_PROVIDER;
426
427
  const chat = (0, chat_1.buildChatCompleterFromEnv)();
427
- expect(chat).toBeNull();
428
+ expect(chat).not.toBeNull();
429
+ expect(typeof chat).toBe('function');
428
430
  });
429
431
  it('builds chat completer from environment variables', () => {
430
432
  process.env = {
package/chat.d.ts CHANGED
@@ -26,12 +26,12 @@ export declare function buildChatCompleter(config: ChatConfig): ChatFunction | n
26
26
  */
27
27
  export declare function buildChatCompleterFromModule(data: LlmModuleData): ChatFunction | null;
28
28
  /**
29
- * Resolve a chat completer from environment variables via getEnvOptions().
29
+ * Resolve a chat completer from environment variables.
30
30
  * This is a fallback for development when no llm_module or defaultChatCompleter is configured.
31
31
  *
32
- * Environment variables (parsed by @constructive-io/graphql-env):
33
- * CHAT_PROVIDER - Provider name ('ollama')
34
- * CHAT_MODEL - Model identifier (e.g. 'llama3')
35
- * CHAT_BASE_URL - Provider base URL
32
+ * Environment variables (with defaults from env.ts):
33
+ * CHAT_PROVIDER - Provider name (default: 'ollama')
34
+ * CHAT_MODEL - Model identifier (default: 'llama3')
35
+ * CHAT_BASE_URL - Provider base URL (default: 'http://localhost:11434')
36
36
  */
37
37
  export declare function buildChatCompleterFromEnv(): ChatFunction | null;
package/chat.js CHANGED
@@ -20,7 +20,7 @@ exports.buildChatCompleter = buildChatCompleter;
20
20
  exports.buildChatCompleterFromModule = buildChatCompleterFromModule;
21
21
  exports.buildChatCompleterFromEnv = buildChatCompleterFromEnv;
22
22
  const ollama_1 = __importDefault(require("@agentic-kit/ollama"));
23
- const graphql_env_1 = require("@constructive-io/graphql-env");
23
+ const env_1 = require("./env");
24
24
  // ─── Built-in Providers ─────────────────────────────────────────────────────
25
25
  /**
26
26
  * Create an Ollama-based chat completion function.
@@ -82,26 +82,18 @@ function buildChatCompleterFromModule(data) {
82
82
  provider: data.chat_provider,
83
83
  model: data.chat_model,
84
84
  baseUrl: data.chat_base_url,
85
- apiKey: data.api_key_ref,
86
85
  });
87
86
  }
88
87
  /**
89
- * Resolve a chat completer from environment variables via getEnvOptions().
88
+ * Resolve a chat completer from environment variables.
90
89
  * This is a fallback for development when no llm_module or defaultChatCompleter is configured.
91
90
  *
92
- * Environment variables (parsed by @constructive-io/graphql-env):
93
- * CHAT_PROVIDER - Provider name ('ollama')
94
- * CHAT_MODEL - Model identifier (e.g. 'llama3')
95
- * CHAT_BASE_URL - Provider base URL
91
+ * Environment variables (with defaults from env.ts):
92
+ * CHAT_PROVIDER - Provider name (default: 'ollama')
93
+ * CHAT_MODEL - Model identifier (default: 'llama3')
94
+ * CHAT_BASE_URL - Provider base URL (default: 'http://localhost:11434')
96
95
  */
97
96
  function buildChatCompleterFromEnv() {
98
- const { llm } = (0, graphql_env_1.getEnvOptions)();
99
- const provider = llm?.chat?.provider;
100
- if (!provider)
101
- return null;
102
- return buildChatCompleter({
103
- provider,
104
- model: llm?.chat?.model,
105
- baseUrl: llm?.chat?.baseUrl,
106
- });
97
+ const { chat } = (0, env_1.getLlmEnvOptions)();
98
+ return buildChatCompleter(chat);
107
99
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * config-cache — Per-database LLM billing configuration cache
3
+ *
4
+ * Caches resolved billing function names per database_id.
5
+ * Uses an LRU cache with TTL so config changes propagate within a bounded window
6
+ * without requiring a server restart.
7
+ *
8
+ * Resolution flow:
9
+ * Billing config from `metaschema_modules_public.billing_module`
10
+ * (schema name + function names for record_usage, check_billing_quota)
11
+ *
12
+ * All queries run through the Graphile `withPgClient` callback, which gives us
13
+ * a client connected to the tenant database with proper role settings.
14
+ *
15
+ * The LLM module config (provider, model, etc.) is already resolved by the
16
+ * LlmModulePlugin at schema-build time. This cache handles the runtime-only
17
+ * billing piece.
18
+ */
19
+ /**
20
+ * Generic pg client interface matching what Graphile's withPgClient provides.
21
+ * Avoids a hard dependency on the `pg` package.
22
+ */
23
+ export interface PgClient {
24
+ query(sql: string, values?: unknown[]): Promise<{
25
+ rows: Record<string, unknown>[];
26
+ }>;
27
+ }
28
+ /**
29
+ * Billing function metadata resolved from the billing_module metaschema table.
30
+ */
31
+ export interface BillingConfig {
32
+ /** Private schema containing the billing functions */
33
+ privateSchema: string;
34
+ /** Name of the record_usage function */
35
+ recordUsageFunction: string;
36
+ /** Name of the check_billing_quota function */
37
+ checkBillingQuotaFunction: string;
38
+ /** Public schema containing meters table */
39
+ publicSchema: string;
40
+ }
41
+ /**
42
+ * Inference log table metadata resolved from the inference_log_module.
43
+ */
44
+ export interface InferenceLogConfig {
45
+ /** Schema containing the usage_log_inference table */
46
+ schema: string;
47
+ /** Name of the inference log table */
48
+ tableName: string;
49
+ }
50
+ /**
51
+ * Per-database cached configuration for the LLM billing integration.
52
+ */
53
+ export interface LlmBillingCacheEntry {
54
+ /** Billing function references (null if billing_module not provisioned) */
55
+ billing: BillingConfig | null;
56
+ /** Inference log table references (null if inference_log_module not provisioned) */
57
+ inferenceLog: InferenceLogConfig | null;
58
+ }
59
+ /**
60
+ * Resolve billing config for a database.
61
+ * Results are cached per database_id with a 5-minute TTL.
62
+ *
63
+ * @param pgClient - A client connected to the tenant database (from withPgClient)
64
+ * @param databaseId - The database UUID
65
+ */
66
+ export declare function getLlmBillingConfig(pgClient: PgClient, databaseId: string): Promise<LlmBillingCacheEntry>;
67
+ /**
68
+ * Invalidate the cached config for a specific database (or all).
69
+ */
70
+ export declare function invalidateLlmBillingConfig(databaseId?: string): void;
71
+ /**
72
+ * Get cache stats for diagnostics.
73
+ */
74
+ export declare function getLlmBillingCacheStats(): {
75
+ size: number;
76
+ max: number;
77
+ };
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ /**
3
+ * config-cache — Per-database LLM billing configuration cache
4
+ *
5
+ * Caches resolved billing function names per database_id.
6
+ * Uses an LRU cache with TTL so config changes propagate within a bounded window
7
+ * without requiring a server restart.
8
+ *
9
+ * Resolution flow:
10
+ * Billing config from `metaschema_modules_public.billing_module`
11
+ * (schema name + function names for record_usage, check_billing_quota)
12
+ *
13
+ * All queries run through the Graphile `withPgClient` callback, which gives us
14
+ * a client connected to the tenant database with proper role settings.
15
+ *
16
+ * The LLM module config (provider, model, etc.) is already resolved by the
17
+ * LlmModulePlugin at schema-build time. This cache handles the runtime-only
18
+ * billing piece.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.getLlmBillingConfig = getLlmBillingConfig;
22
+ exports.invalidateLlmBillingConfig = invalidateLlmBillingConfig;
23
+ exports.getLlmBillingCacheStats = getLlmBillingCacheStats;
24
+ const graphile_cache_1 = require("graphile-cache");
25
+ // ─── SQL Queries ────────────────────────────────────────────────────────────
26
+ /**
27
+ * Check if the billing_module table exists before querying it.
28
+ * This prevents hard errors on databases that don't have the billing
29
+ * module provisioned (the metaschema_modules_public schema or the
30
+ * billing_module table might not exist at all).
31
+ */
32
+ const BILLING_MODULE_SQL = `
33
+ SELECT
34
+ s.schema_name AS public_schema,
35
+ ps.schema_name AS private_schema,
36
+ bm.record_usage_function
37
+ FROM metaschema_modules_public.billing_module bm
38
+ JOIN metaschema_public.schema s ON bm.schema_id = s.id
39
+ JOIN metaschema_public.schema ps ON bm.private_schema_id = ps.id
40
+ WHERE bm.database_id = $1
41
+ LIMIT 1
42
+ `;
43
+ /**
44
+ * Resolve the inference log module's schema and table name.
45
+ */
46
+ const INFERENCE_LOG_MODULE_SQL = `
47
+ SELECT
48
+ s.schema_name AS schema,
49
+ ilm.inference_log_table_name AS table_name
50
+ FROM metaschema_modules_public.inference_log_module ilm
51
+ JOIN metaschema_public.schema s ON ilm.schema_id = s.id
52
+ WHERE ilm.database_id = $1
53
+ LIMIT 1
54
+ `;
55
+ // ─── Cache ──────────────────────────────────────────────────────────────────
56
+ const billingCache = new graphile_cache_1.ModuleConfigCache({
57
+ name: 'billing-config',
58
+ ttlMs: 5 * 60 * 1000, // 5 minutes
59
+ max: 50,
60
+ });
61
+ // ─── Resolution Functions ───────────────────────────────────────────────────
62
+ /**
63
+ * SQL to check if a schema exists. Used as a guard before querying
64
+ * metaschema tables that may not be provisioned.
65
+ */
66
+ const SCHEMA_EXISTS_SQL = `
67
+ SELECT 1 FROM information_schema.schemata WHERE schema_name = $1 LIMIT 1
68
+ `;
69
+ async function resolveInferenceLogConfig(pgClient, databaseId) {
70
+ try {
71
+ const schemaCheck = await pgClient.query(SCHEMA_EXISTS_SQL, ['metaschema_modules_public']);
72
+ if (schemaCheck.rows.length === 0)
73
+ return null;
74
+ const result = await pgClient.query(INFERENCE_LOG_MODULE_SQL, [databaseId]);
75
+ const row = result.rows[0];
76
+ if (!row?.schema || !row?.table_name)
77
+ return null;
78
+ return {
79
+ schema: row.schema,
80
+ tableName: row.table_name,
81
+ };
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
87
+ async function resolveBillingConfig(pgClient, databaseId) {
88
+ try {
89
+ // Guard: check if the metaschema_modules_public schema exists.
90
+ // If the database doesn't have the billing module provisioned,
91
+ // this schema (or the billing_module table) won't exist.
92
+ const schemaCheck = await pgClient.query(SCHEMA_EXISTS_SQL, ['metaschema_modules_public']);
93
+ if (schemaCheck.rows.length === 0)
94
+ return null;
95
+ const result = await pgClient.query(BILLING_MODULE_SQL, [databaseId]);
96
+ const row = result.rows[0];
97
+ if (!row?.record_usage_function)
98
+ return null;
99
+ return {
100
+ publicSchema: row.public_schema,
101
+ privateSchema: row.private_schema,
102
+ recordUsageFunction: row.record_usage_function,
103
+ // The check_billing_quota function name follows the inflection pattern
104
+ checkBillingQuotaFunction: 'check_billing_quota',
105
+ };
106
+ }
107
+ catch {
108
+ // Schema/table doesn't exist or query failed — billing not available
109
+ return null;
110
+ }
111
+ }
112
+ // ─── Public API ─────────────────────────────────────────────────────────────
113
+ /**
114
+ * Resolve billing config for a database.
115
+ * Results are cached per database_id with a 5-minute TTL.
116
+ *
117
+ * @param pgClient - A client connected to the tenant database (from withPgClient)
118
+ * @param databaseId - The database UUID
119
+ */
120
+ async function getLlmBillingConfig(pgClient, databaseId) {
121
+ const cached = billingCache.get(databaseId);
122
+ if (cached)
123
+ return cached;
124
+ const [billing, inferenceLog] = await Promise.all([
125
+ resolveBillingConfig(pgClient, databaseId),
126
+ resolveInferenceLogConfig(pgClient, databaseId),
127
+ ]);
128
+ const entry = { billing, inferenceLog };
129
+ billingCache.set(databaseId, entry);
130
+ return entry;
131
+ }
132
+ /**
133
+ * Invalidate the cached config for a specific database (or all).
134
+ */
135
+ function invalidateLlmBillingConfig(databaseId) {
136
+ if (databaseId) {
137
+ billingCache.delete(databaseId);
138
+ }
139
+ else {
140
+ billingCache.clear();
141
+ }
142
+ }
143
+ /**
144
+ * Get cache stats for diagnostics.
145
+ */
146
+ function getLlmBillingCacheStats() {
147
+ return { size: billingCache.size, max: 50 };
148
+ }
package/embedder.d.ts CHANGED
@@ -24,12 +24,12 @@ export declare function buildEmbedder(config: EmbedderConfig): EmbedderFunction
24
24
  */
25
25
  export declare function buildEmbedderFromModule(data: LlmModuleData): EmbedderFunction | null;
26
26
  /**
27
- * Resolve an embedder from environment variables via getEnvOptions().
27
+ * Resolve an embedder from environment variables.
28
28
  * This is a fallback for development when no llm_module or defaultEmbedder is configured.
29
29
  *
30
- * Environment variables (parsed by @constructive-io/graphql-env):
31
- * EMBEDDER_PROVIDER - Provider name ('ollama')
32
- * EMBEDDER_MODEL - Model identifier
33
- * EMBEDDER_BASE_URL - Provider base URL
30
+ * Environment variables (with defaults from env.ts):
31
+ * EMBEDDER_PROVIDER - Provider name (default: 'ollama')
32
+ * EMBEDDER_MODEL - Model identifier (default: 'nomic-embed-text')
33
+ * EMBEDDER_BASE_URL - Provider base URL (default: 'http://localhost:11434')
34
34
  */
35
35
  export declare function buildEmbedderFromEnv(): EmbedderFunction | null;
package/embedder.js CHANGED
@@ -18,7 +18,7 @@ exports.buildEmbedder = buildEmbedder;
18
18
  exports.buildEmbedderFromModule = buildEmbedderFromModule;
19
19
  exports.buildEmbedderFromEnv = buildEmbedderFromEnv;
20
20
  const ollama_1 = __importDefault(require("@agentic-kit/ollama"));
21
- const graphql_env_1 = require("@constructive-io/graphql-env");
21
+ const env_1 = require("./env");
22
22
  // ─── Built-in Providers ─────────────────────────────────────────────────────
23
23
  /**
24
24
  * Create an Ollama-based embedder function.
@@ -56,26 +56,18 @@ function buildEmbedderFromModule(data) {
56
56
  provider: data.embedding_provider,
57
57
  model: data.embedding_model,
58
58
  baseUrl: data.embedding_base_url,
59
- apiKey: data.api_key_ref,
60
59
  });
61
60
  }
62
61
  /**
63
- * Resolve an embedder from environment variables via getEnvOptions().
62
+ * Resolve an embedder from environment variables.
64
63
  * This is a fallback for development when no llm_module or defaultEmbedder is configured.
65
64
  *
66
- * Environment variables (parsed by @constructive-io/graphql-env):
67
- * EMBEDDER_PROVIDER - Provider name ('ollama')
68
- * EMBEDDER_MODEL - Model identifier
69
- * EMBEDDER_BASE_URL - Provider base URL
65
+ * Environment variables (with defaults from env.ts):
66
+ * EMBEDDER_PROVIDER - Provider name (default: 'ollama')
67
+ * EMBEDDER_MODEL - Model identifier (default: 'nomic-embed-text')
68
+ * EMBEDDER_BASE_URL - Provider base URL (default: 'http://localhost:11434')
70
69
  */
71
70
  function buildEmbedderFromEnv() {
72
- const { llm } = (0, graphql_env_1.getEnvOptions)();
73
- const provider = llm?.embedder?.provider;
74
- if (!provider)
75
- return null;
76
- return buildEmbedder({
77
- provider,
78
- model: llm?.embedder?.model,
79
- baseUrl: llm?.embedder?.baseUrl,
80
- });
71
+ const { embedding } = (0, env_1.getLlmEnvOptions)();
72
+ return buildEmbedder(embedding);
81
73
  }
package/env.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * LLM Environment Configuration
3
+ *
4
+ * Single source of truth for all LLM-related environment variables and defaults.
5
+ * Every other module in graphile-llm imports from here — no direct process.env
6
+ * reads or scattered null coalescing elsewhere.
7
+ *
8
+ * Environment variables:
9
+ * EMBEDDER_PROVIDER - Embedding provider name ('ollama')
10
+ * EMBEDDER_MODEL - Embedding model (default: 'nomic-embed-text')
11
+ * EMBEDDER_BASE_URL - Embedding provider URL (default: 'http://localhost:11434')
12
+ * CHAT_PROVIDER - Chat provider name ('ollama')
13
+ * CHAT_MODEL - Chat model (default: 'llama3')
14
+ * CHAT_BASE_URL - Chat provider URL (default: 'http://localhost:11434')
15
+ */
16
+ export interface LlmProviderConfig {
17
+ provider: string;
18
+ model: string;
19
+ baseUrl: string;
20
+ }
21
+ export interface LlmEnvOptions {
22
+ embedding: LlmProviderConfig;
23
+ chat: LlmProviderConfig;
24
+ }
25
+ /**
26
+ * Resolve LLM configuration from environment variables with sensible defaults.
27
+ *
28
+ * Call this once and pass the result around — never read process.env directly
29
+ * in plugin code.
30
+ */
31
+ export declare function getLlmEnvOptions(): LlmEnvOptions;
package/env.js ADDED
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * LLM Environment Configuration
4
+ *
5
+ * Single source of truth for all LLM-related environment variables and defaults.
6
+ * Every other module in graphile-llm imports from here — no direct process.env
7
+ * reads or scattered null coalescing elsewhere.
8
+ *
9
+ * Environment variables:
10
+ * EMBEDDER_PROVIDER - Embedding provider name ('ollama')
11
+ * EMBEDDER_MODEL - Embedding model (default: 'nomic-embed-text')
12
+ * EMBEDDER_BASE_URL - Embedding provider URL (default: 'http://localhost:11434')
13
+ * CHAT_PROVIDER - Chat provider name ('ollama')
14
+ * CHAT_MODEL - Chat model (default: 'llama3')
15
+ * CHAT_BASE_URL - Chat provider URL (default: 'http://localhost:11434')
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.getLlmEnvOptions = getLlmEnvOptions;
19
+ // ─── Defaults ───────────────────────────────────────────────────────────────
20
+ const LLM_DEFAULTS = {
21
+ embedding: {
22
+ provider: 'ollama',
23
+ model: 'nomic-embed-text',
24
+ baseUrl: 'http://localhost:11434',
25
+ },
26
+ chat: {
27
+ provider: 'ollama',
28
+ model: 'llama3',
29
+ baseUrl: 'http://localhost:11434',
30
+ },
31
+ };
32
+ // ─── Resolution ─────────────────────────────────────────────────────────────
33
+ /**
34
+ * Resolve LLM configuration from environment variables with sensible defaults.
35
+ *
36
+ * Call this once and pass the result around — never read process.env directly
37
+ * in plugin code.
38
+ */
39
+ function getLlmEnvOptions() {
40
+ return {
41
+ embedding: {
42
+ provider: process.env.EMBEDDER_PROVIDER ?? LLM_DEFAULTS.embedding.provider,
43
+ model: process.env.EMBEDDER_MODEL ?? LLM_DEFAULTS.embedding.model,
44
+ baseUrl: process.env.EMBEDDER_BASE_URL ?? LLM_DEFAULTS.embedding.baseUrl,
45
+ },
46
+ chat: {
47
+ provider: process.env.CHAT_PROVIDER ?? LLM_DEFAULTS.chat.provider,
48
+ model: process.env.CHAT_MODEL ?? LLM_DEFAULTS.chat.model,
49
+ baseUrl: process.env.CHAT_BASE_URL ?? LLM_DEFAULTS.chat.baseUrl,
50
+ },
51
+ };
52
+ }
@@ -71,11 +71,12 @@ describe('Embedder abstraction', () => {
71
71
  afterEach(() => {
72
72
  process.env = originalEnv;
73
73
  });
74
- it('returns null when EMBEDDER_PROVIDER is not set', () => {
74
+ it('returns default ollama embedder when EMBEDDER_PROVIDER is not set', () => {
75
75
  process.env = { ...originalEnv };
76
76
  delete process.env.EMBEDDER_PROVIDER;
77
77
  const embedder = buildEmbedderFromEnv();
78
- expect(embedder).toBeNull();
78
+ expect(embedder).not.toBeNull();
79
+ expect(typeof embedder).toBe('function');
79
80
  });
80
81
  it('builds embedder from environment variables', () => {
81
82
  process.env = {
@@ -382,11 +383,12 @@ describe('Chat completion abstraction', () => {
382
383
  afterEach(() => {
383
384
  process.env = originalEnv;
384
385
  });
385
- it('returns null when CHAT_PROVIDER is not set', () => {
386
+ it('returns default ollama chat completer when CHAT_PROVIDER is not set', () => {
386
387
  process.env = { ...originalEnv };
387
388
  delete process.env.CHAT_PROVIDER;
388
389
  const chat = buildChatCompleterFromEnv();
389
- expect(chat).toBeNull();
390
+ expect(chat).not.toBeNull();
391
+ expect(typeof chat).toBe('function');
390
392
  });
391
393
  it('builds chat completer from environment variables', () => {
392
394
  process.env = {
package/esm/chat.d.ts CHANGED
@@ -26,12 +26,12 @@ export declare function buildChatCompleter(config: ChatConfig): ChatFunction | n
26
26
  */
27
27
  export declare function buildChatCompleterFromModule(data: LlmModuleData): ChatFunction | null;
28
28
  /**
29
- * Resolve a chat completer from environment variables via getEnvOptions().
29
+ * Resolve a chat completer from environment variables.
30
30
  * This is a fallback for development when no llm_module or defaultChatCompleter is configured.
31
31
  *
32
- * Environment variables (parsed by @constructive-io/graphql-env):
33
- * CHAT_PROVIDER - Provider name ('ollama')
34
- * CHAT_MODEL - Model identifier (e.g. 'llama3')
35
- * CHAT_BASE_URL - Provider base URL
32
+ * Environment variables (with defaults from env.ts):
33
+ * CHAT_PROVIDER - Provider name (default: 'ollama')
34
+ * CHAT_MODEL - Model identifier (default: 'llama3')
35
+ * CHAT_BASE_URL - Provider base URL (default: 'http://localhost:11434')
36
36
  */
37
37
  export declare function buildChatCompleterFromEnv(): ChatFunction | null;
package/esm/chat.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * 3. Environment variables (CHAT_PROVIDER, CHAT_MODEL, CHAT_BASE_URL)
13
13
  */
14
14
  import OllamaClient from '@agentic-kit/ollama';
15
- import { getEnvOptions } from '@constructive-io/graphql-env';
15
+ import { getLlmEnvOptions } from './env';
16
16
  // ─── Built-in Providers ─────────────────────────────────────────────────────
17
17
  /**
18
18
  * Create an Ollama-based chat completion function.
@@ -74,26 +74,18 @@ export function buildChatCompleterFromModule(data) {
74
74
  provider: data.chat_provider,
75
75
  model: data.chat_model,
76
76
  baseUrl: data.chat_base_url,
77
- apiKey: data.api_key_ref,
78
77
  });
79
78
  }
80
79
  /**
81
- * Resolve a chat completer from environment variables via getEnvOptions().
80
+ * Resolve a chat completer from environment variables.
82
81
  * This is a fallback for development when no llm_module or defaultChatCompleter is configured.
83
82
  *
84
- * Environment variables (parsed by @constructive-io/graphql-env):
85
- * CHAT_PROVIDER - Provider name ('ollama')
86
- * CHAT_MODEL - Model identifier (e.g. 'llama3')
87
- * CHAT_BASE_URL - Provider base URL
83
+ * Environment variables (with defaults from env.ts):
84
+ * CHAT_PROVIDER - Provider name (default: 'ollama')
85
+ * CHAT_MODEL - Model identifier (default: 'llama3')
86
+ * CHAT_BASE_URL - Provider base URL (default: 'http://localhost:11434')
88
87
  */
89
88
  export function buildChatCompleterFromEnv() {
90
- const { llm } = getEnvOptions();
91
- const provider = llm?.chat?.provider;
92
- if (!provider)
93
- return null;
94
- return buildChatCompleter({
95
- provider,
96
- model: llm?.chat?.model,
97
- baseUrl: llm?.chat?.baseUrl,
98
- });
89
+ const { chat } = getLlmEnvOptions();
90
+ return buildChatCompleter(chat);
99
91
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * config-cache — Per-database LLM billing configuration cache
3
+ *
4
+ * Caches resolved billing function names per database_id.
5
+ * Uses an LRU cache with TTL so config changes propagate within a bounded window
6
+ * without requiring a server restart.
7
+ *
8
+ * Resolution flow:
9
+ * Billing config from `metaschema_modules_public.billing_module`
10
+ * (schema name + function names for record_usage, check_billing_quota)
11
+ *
12
+ * All queries run through the Graphile `withPgClient` callback, which gives us
13
+ * a client connected to the tenant database with proper role settings.
14
+ *
15
+ * The LLM module config (provider, model, etc.) is already resolved by the
16
+ * LlmModulePlugin at schema-build time. This cache handles the runtime-only
17
+ * billing piece.
18
+ */
19
+ /**
20
+ * Generic pg client interface matching what Graphile's withPgClient provides.
21
+ * Avoids a hard dependency on the `pg` package.
22
+ */
23
+ export interface PgClient {
24
+ query(sql: string, values?: unknown[]): Promise<{
25
+ rows: Record<string, unknown>[];
26
+ }>;
27
+ }
28
+ /**
29
+ * Billing function metadata resolved from the billing_module metaschema table.
30
+ */
31
+ export interface BillingConfig {
32
+ /** Private schema containing the billing functions */
33
+ privateSchema: string;
34
+ /** Name of the record_usage function */
35
+ recordUsageFunction: string;
36
+ /** Name of the check_billing_quota function */
37
+ checkBillingQuotaFunction: string;
38
+ /** Public schema containing meters table */
39
+ publicSchema: string;
40
+ }
41
+ /**
42
+ * Inference log table metadata resolved from the inference_log_module.
43
+ */
44
+ export interface InferenceLogConfig {
45
+ /** Schema containing the usage_log_inference table */
46
+ schema: string;
47
+ /** Name of the inference log table */
48
+ tableName: string;
49
+ }
50
+ /**
51
+ * Per-database cached configuration for the LLM billing integration.
52
+ */
53
+ export interface LlmBillingCacheEntry {
54
+ /** Billing function references (null if billing_module not provisioned) */
55
+ billing: BillingConfig | null;
56
+ /** Inference log table references (null if inference_log_module not provisioned) */
57
+ inferenceLog: InferenceLogConfig | null;
58
+ }
59
+ /**
60
+ * Resolve billing config for a database.
61
+ * Results are cached per database_id with a 5-minute TTL.
62
+ *
63
+ * @param pgClient - A client connected to the tenant database (from withPgClient)
64
+ * @param databaseId - The database UUID
65
+ */
66
+ export declare function getLlmBillingConfig(pgClient: PgClient, databaseId: string): Promise<LlmBillingCacheEntry>;
67
+ /**
68
+ * Invalidate the cached config for a specific database (or all).
69
+ */
70
+ export declare function invalidateLlmBillingConfig(databaseId?: string): void;
71
+ /**
72
+ * Get cache stats for diagnostics.
73
+ */
74
+ export declare function getLlmBillingCacheStats(): {
75
+ size: number;
76
+ max: number;
77
+ };