@link-assistant/agent 0.5.2 → 0.6.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.
@@ -36,7 +36,7 @@ export namespace FileWatcher {
36
36
  const state = Instance.state(
37
37
  async () => {
38
38
  if (Instance.project.vcs !== 'git') return {};
39
- log.info('init');
39
+ log.info(() => ({ message: 'init' }));
40
40
  const cfg = await Config.get();
41
41
  const backend = (() => {
42
42
  if (process.platform === 'win32') return 'windows';
@@ -44,18 +44,23 @@ export namespace FileWatcher {
44
44
  if (process.platform === 'linux') return 'inotify';
45
45
  })();
46
46
  if (!backend) {
47
- log.error('watcher backend not supported', {
47
+ log.error(() => ({
48
+ message: 'watcher backend not supported',
48
49
  platform: process.platform,
49
- });
50
+ }));
50
51
  return {};
51
52
  }
52
- log.info('watcher backend', { platform: process.platform, backend });
53
+ log.info(() => ({
54
+ message: 'watcher backend',
55
+ platform: process.platform,
56
+ backend,
57
+ }));
53
58
  const sub = await watcher().subscribe(
54
59
  Instance.directory,
55
60
  (err, evts) => {
56
61
  if (err) return;
57
62
  for (const evt of evts) {
58
- log.info('event', evt);
63
+ log.info(() => ({ message: 'event', ...evt }));
59
64
  if (evt.type === 'create')
60
65
  Bus.publish(Event.Updated, { file: evt.path, event: 'add' });
61
66
  if (evt.type === 'update')
@@ -29,7 +29,7 @@ export namespace Format {
29
29
 
30
30
  const formatters: Record<string, Formatter.Info> = {};
31
31
  if (cfg.formatter === false) {
32
- log.info('all formatters are disabled');
32
+ log.info(() => ({ message: 'all formatters are disabled' }));
33
33
  return {
34
34
  enabled,
35
35
  formatters,
@@ -77,10 +77,10 @@ export namespace Format {
77
77
  const formatters = await state().then((x) => x.formatters);
78
78
  const result = [];
79
79
  for (const item of Object.values(formatters)) {
80
- log.info('checking', { name: item.name, ext });
80
+ log.info(() => ({ message: 'checking', name: item.name, ext }));
81
81
  if (!item.extensions.includes(ext)) continue;
82
82
  if (!(await isEnabled(item))) continue;
83
- log.info('enabled', { name: item.name, ext });
83
+ log.info(() => ({ message: 'enabled', name: item.name, ext }));
84
84
  result.push(item);
85
85
  }
86
86
  return result;
@@ -101,14 +101,14 @@ export namespace Format {
101
101
  }
102
102
 
103
103
  export function init() {
104
- log.info('init');
104
+ log.info(() => ({ message: 'init' }));
105
105
  Bus.subscribe(File.Event.Edited, async (payload) => {
106
106
  const file = payload.properties.file;
107
- log.info('formatting', { file });
107
+ log.info(() => ({ message: 'formatting', file }));
108
108
  const ext = path.extname(file);
109
109
 
110
110
  for (const item of await getFormatter(ext)) {
111
- log.info('running', { command: item.command });
111
+ log.info(() => ({ message: 'running', command: item.command }));
112
112
  try {
113
113
  const proc = Bun.spawn({
114
114
  cmd: item.command.map((x) => x.replace('$FILE', file)),
@@ -119,17 +119,19 @@ export namespace Format {
119
119
  });
120
120
  const exit = await proc.exited;
121
121
  if (exit !== 0)
122
- log.error('failed', {
122
+ log.error(() => ({
123
+ message: 'failed',
123
124
  command: item.command,
124
125
  ...item.environment,
125
- });
126
+ }));
126
127
  } catch (error) {
127
- log.error('failed to format file', {
128
+ log.error(() => ({
129
+ message: 'failed to format file',
128
130
  error,
129
131
  command: item.command,
130
132
  ...item.environment,
131
133
  file,
132
- });
134
+ }));
133
135
  }
134
136
  }
135
137
  });
package/src/index.js CHANGED
@@ -246,22 +246,19 @@ async function readSystemMessages(argv) {
246
246
  }
247
247
 
248
248
  async function runAgentMode(argv, request) {
249
- // Note: verbose flag and logging are now initialized in middleware
250
- // See main() function for the middleware that sets up Flag and Log.init()
251
-
252
- // Log version and command info in verbose mode
253
- if (Flag.OPENCODE_VERBOSE) {
254
- console.error(`Agent version: ${pkg.version}`);
255
- console.error(`Command: ${process.argv.join(' ')}`);
256
- console.error(`Working directory: ${process.cwd()}`);
257
- console.error(`Script path: ${import.meta.path}`);
258
- }
259
-
260
- // Log dry-run mode if enabled
249
+ // Log version and command info in verbose mode using lazy logging
250
+ Log.Default.lazy.info(() => ({
251
+ message: 'Agent started',
252
+ version: pkg.version,
253
+ command: process.argv.join(' '),
254
+ workingDirectory: process.cwd(),
255
+ scriptPath: import.meta.path,
256
+ }));
261
257
  if (Flag.OPENCODE_DRY_RUN) {
262
- console.error(
263
- `[DRY RUN MODE] No actual API calls or package installations will be made`
264
- );
258
+ Log.Default.lazy.info(() => ({
259
+ message: 'Dry run mode enabled',
260
+ mode: 'dry-run',
261
+ }));
265
262
  }
266
263
 
267
264
  const { providerID, modelID } = await parseModelConfig(argv);
@@ -269,9 +266,11 @@ async function runAgentMode(argv, request) {
269
266
  // Validate and get JSON standard
270
267
  const jsonStandard = argv['json-standard'];
271
268
  if (!isValidJsonStandard(jsonStandard)) {
272
- console.error(
273
- `Invalid JSON standard: ${jsonStandard}. Use "opencode" or "claude".`
274
- );
269
+ outputStatus({
270
+ type: 'error',
271
+ errorType: 'ValidationError',
272
+ message: `Invalid JSON standard: ${jsonStandard}. Use "opencode" or "claude".`,
273
+ });
275
274
  process.exit(1);
276
275
  }
277
276
 
@@ -317,24 +316,20 @@ async function runAgentMode(argv, request) {
317
316
  * @param {object} argv - Command line arguments
318
317
  */
319
318
  async function runContinuousAgentMode(argv) {
320
- // Note: verbose flag and logging are now initialized in middleware
321
- // See main() function for the middleware that sets up Flag and Log.init()
322
-
323
319
  const compactJson = argv['compact-json'] === true;
324
-
325
- // Log version and command info in verbose mode
326
- if (Flag.OPENCODE_VERBOSE) {
327
- console.error(`Agent version: ${pkg.version}`);
328
- console.error(`Command: ${process.argv.join(' ')}`);
329
- console.error(`Working directory: ${process.cwd()}`);
330
- console.error(`Script path: ${import.meta.path}`);
331
- }
332
-
333
- // Log dry-run mode if enabled
320
+ // Log version and command info in verbose mode using lazy logging
321
+ Log.Default.lazy.info(() => ({
322
+ message: 'Agent started (continuous mode)',
323
+ version: pkg.version,
324
+ command: process.argv.join(' '),
325
+ workingDirectory: process.cwd(),
326
+ scriptPath: import.meta.path,
327
+ }));
334
328
  if (Flag.OPENCODE_DRY_RUN) {
335
- console.error(
336
- `[DRY RUN MODE] No actual API calls or package installations will be made`
337
- );
329
+ Log.Default.lazy.info(() => ({
330
+ message: 'Dry run mode enabled',
331
+ mode: 'dry-run',
332
+ }));
338
333
  }
339
334
 
340
335
  const { providerID, modelID } = await parseModelConfig(argv);
@@ -935,7 +930,7 @@ async function main() {
935
930
  }
936
931
 
937
932
  // Initialize logging system
938
- // - If verbose: print logs to stderr for debugging
933
+ // - If verbose: print logs to stderr for debugging in JSON format
939
934
  // - Otherwise: write logs to file to keep CLI output clean
940
935
  await Log.init({
941
936
  print: Flag.OPENCODE_VERBOSE,
package/src/mcp/index.ts CHANGED
@@ -81,9 +81,10 @@ export namespace MCP {
81
81
  await Promise.all(
82
82
  Object.values(state.clients).map((client) =>
83
83
  client.close().catch((error) => {
84
- log.error('Failed to close MCP client', {
84
+ log.error(() => ({
85
+ message: 'Failed to close MCP client',
85
86
  error,
86
- });
87
+ }));
87
88
  })
88
89
  )
89
90
  );
@@ -119,10 +120,10 @@ export namespace MCP {
119
120
 
120
121
  async function create(key: string, mcp: Config.Mcp) {
121
122
  if (mcp.enabled === false) {
122
- log.info('mcp server disabled', { key });
123
+ log.info(() => ({ message: 'mcp server disabled', key }));
123
124
  return;
124
125
  }
125
- log.info('found', { key, type: mcp.type });
126
+ log.info(() => ({ message: 'found', key, type: mcp.type }));
126
127
  let mcpClient: MCPClient | undefined;
127
128
  let status: Status | undefined = undefined;
128
129
 
@@ -152,7 +153,11 @@ export namespace MCP {
152
153
  transport,
153
154
  })
154
155
  .then((client) => {
155
- log.info('connected', { key, transport: name });
156
+ log.info(() => ({
157
+ message: 'connected',
158
+ key,
159
+ transport: name,
160
+ }));
156
161
  mcpClient = client;
157
162
  status = { status: 'connected' };
158
163
  return true;
@@ -160,12 +165,13 @@ export namespace MCP {
160
165
  .catch((error) => {
161
166
  lastError =
162
167
  error instanceof Error ? error : new Error(String(error));
163
- log.debug('transport connection failed', {
168
+ log.debug(() => ({
169
+ message: 'transport connection failed',
164
170
  key,
165
171
  transport: name,
166
172
  url: mcp.url,
167
173
  error: lastError.message,
168
- });
174
+ }));
169
175
  status = {
170
176
  status: 'failed' as const,
171
177
  error: lastError.message,
@@ -198,11 +204,12 @@ export namespace MCP {
198
204
  };
199
205
  })
200
206
  .catch((error) => {
201
- log.error('local mcp startup failed', {
207
+ log.error(() => ({
208
+ message: 'local mcp startup failed',
202
209
  key,
203
210
  command: mcp.command,
204
211
  error: error instanceof Error ? error.message : String(error),
205
- });
212
+ }));
206
213
  status = {
207
214
  status: 'failed' as const,
208
215
  error: error instanceof Error ? error.message : String(error),
@@ -228,14 +235,19 @@ export namespace MCP {
228
235
  mcpClient.tools(),
229
236
  mcp.timeout ?? 5000
230
237
  ).catch((err) => {
231
- log.error('failed to get tools from client', { key, error: err });
238
+ log.error(() => ({
239
+ message: 'failed to get tools from client',
240
+ key,
241
+ error: err,
242
+ }));
232
243
  return undefined;
233
244
  });
234
245
  if (!result) {
235
246
  await mcpClient.close().catch((error) => {
236
- log.error('Failed to close MCP client', {
247
+ log.error(() => ({
248
+ message: 'Failed to close MCP client',
237
249
  error,
238
- });
250
+ }));
239
251
  });
240
252
  status = {
241
253
  status: 'failed',
@@ -250,10 +262,11 @@ export namespace MCP {
250
262
  };
251
263
  }
252
264
 
253
- log.info('create() successfully created client', {
265
+ log.info(() => ({
266
+ message: 'create() successfully created client',
254
267
  key,
255
268
  toolCount: Object.keys(result).length,
256
- });
269
+ }));
257
270
  return {
258
271
  mcpClient,
259
272
  status,
@@ -274,7 +287,11 @@ export namespace MCP {
274
287
  const clientsSnapshot = await clients();
275
288
  for (const [clientName, client] of Object.entries(clientsSnapshot)) {
276
289
  const tools = await client.tools().catch((e) => {
277
- log.error('failed to get tools', { clientName, error: e.message });
290
+ log.error(() => ({
291
+ message: 'failed to get tools',
292
+ clientName,
293
+ error: e.message,
294
+ }));
278
295
  const failedStatus = {
279
296
  status: 'failed' as const,
280
297
  error: e instanceof Error ? e.message : String(e),
@@ -532,13 +532,13 @@ export namespace Patch {
532
532
 
533
533
  await fs.writeFile(hunk.path, hunk.contents, 'utf-8');
534
534
  added.push(hunk.path);
535
- log.info(`Added file: ${hunk.path}`);
535
+ log.info(() => ({ message: 'Added file', path: hunk.path }));
536
536
  break;
537
537
 
538
538
  case 'delete':
539
539
  await fs.unlink(hunk.path);
540
540
  deleted.push(hunk.path);
541
- log.info(`Deleted file: ${hunk.path}`);
541
+ log.info(() => ({ message: 'Deleted file', path: hunk.path }));
542
542
  break;
543
543
 
544
544
  case 'update':
@@ -557,12 +557,16 @@ export namespace Patch {
557
557
  await fs.writeFile(hunk.move_path, fileUpdate.content, 'utf-8');
558
558
  await fs.unlink(hunk.path);
559
559
  modified.push(hunk.move_path);
560
- log.info(`Moved file: ${hunk.path} -> ${hunk.move_path}`);
560
+ log.info(() => ({
561
+ message: 'Moved file',
562
+ from: hunk.path,
563
+ to: hunk.move_path,
564
+ }));
561
565
  } else {
562
566
  // Regular update
563
567
  await fs.writeFile(hunk.path, fileUpdate.content, 'utf-8');
564
568
  modified.push(hunk.path);
565
- log.info(`Updated file: ${hunk.path}`);
569
+ log.info(() => ({ message: 'Updated file', path: hunk.path }));
566
570
  }
567
571
  break;
568
572
  }
@@ -24,7 +24,7 @@ export namespace Project {
24
24
  export type Info = z.infer<typeof Info>;
25
25
 
26
26
  export async function fromDirectory(directory: string) {
27
- log.info('fromDirectory', { directory });
27
+ log.info(() => ({ message: 'fromDirectory', directory }));
28
28
  const matches = Filesystem.up({ targets: ['.git'], start: directory });
29
29
  const git = await matches.next().then((x) => x.value);
30
30
  await matches.return();
@@ -36,16 +36,20 @@ export namespace State {
36
36
  const entries = recordsByKey.get(key);
37
37
  if (!entries) return;
38
38
 
39
- log.info('waiting for state disposal to complete', { key });
39
+ log.info(() => ({
40
+ message: 'waiting for state disposal to complete',
41
+ key,
42
+ }));
40
43
 
41
44
  let disposalFinished = false;
42
45
 
43
46
  setTimeout(() => {
44
47
  if (!disposalFinished) {
45
- log.warn(
46
- 'state disposal is taking an unusually long time - if it does not complete in a reasonable time, please report this as a bug',
47
- { key }
48
- );
48
+ log.warn(() => ({
49
+ message:
50
+ 'state disposal is taking an unusually long time - if it does not complete in a reasonable time, please report this as a bug',
51
+ key,
52
+ }));
49
53
  }
50
54
  }, 10000).unref();
51
55
 
@@ -56,7 +60,11 @@ export namespace State {
56
60
  const task = Promise.resolve(entry.state)
57
61
  .then((state) => entry.dispose!(state))
58
62
  .catch((error) => {
59
- log.error('Error while disposing state:', { error, key });
63
+ log.error(() => ({
64
+ message: 'Error while disposing state',
65
+ error,
66
+ key,
67
+ }));
60
68
  });
61
69
 
62
70
  tasks.push(task);
@@ -64,6 +72,6 @@ export namespace State {
64
72
  await Promise.all(tasks);
65
73
  recordsByKey.delete(key);
66
74
  disposalFinished = true;
67
- log.info('state disposal completed', { key });
75
+ log.info(() => ({ message: 'state disposal completed', key }));
68
76
  }
69
77
  }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Cache Provider - A synthetic provider for caching API responses
3
+ *
4
+ * This provider caches API responses to enable deterministic testing.
5
+ * When a response is not cached, it falls back to the echo provider behavior.
6
+ * Cached responses are stored using Links Notation format (.lino files).
7
+ *
8
+ * Usage:
9
+ * agent --model link-assistant/cache/opencode -p "hello" # Uses cached responses
10
+ *
11
+ * Cache location: ./data/api-cache/{provider}/{model}/
12
+ * Format: Links Notation files with .lino extension
13
+ *
14
+ * @see https://github.com/link-assistant/agent/issues/89
15
+ * @see https://github.com/link-foundation/lino-objects-codec
16
+ */
17
+
18
+ import type { LanguageModelV2, LanguageModelV2CallOptions } from 'ai';
19
+ import { Log } from '../util/log';
20
+ import { createEchoModel } from './echo';
21
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
22
+ import { dirname, join } from 'path';
23
+ import { fileURLToPath } from 'url';
24
+ // @ts-ignore - lino-objects-codec is a JavaScript library
25
+ import { encode, decode } from 'lino-objects-codec';
26
+
27
+ const log = Log.create({ service: 'provider.cache' });
28
+
29
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
+ const CACHE_ROOT = join(__dirname, '../../data/api-cache');
31
+
32
+ /**
33
+ * Generate a cache key from the prompt
34
+ */
35
+ function generateCacheKey(
36
+ prompt: LanguageModelV2CallOptions['prompt']
37
+ ): string {
38
+ // Simple hash of the prompt content
39
+ const content = JSON.stringify(prompt);
40
+ let hash = 0;
41
+ for (let i = 0; i < content.length; i++) {
42
+ const char = content.charCodeAt(i);
43
+ hash = (hash << 5) - hash + char;
44
+ hash = hash & hash; // Convert to 32-bit integer
45
+ }
46
+ return Math.abs(hash).toString(36);
47
+ }
48
+
49
+ /**
50
+ * Get cache file path for a provider/model combination
51
+ * Uses .lino extension for Links Notation format
52
+ */
53
+ function getCachePath(provider: string, model: string, key: string): string {
54
+ return join(CACHE_ROOT, provider, model, `${key}.lino`);
55
+ }
56
+
57
+ /**
58
+ * Generate a unique ID for streaming parts
59
+ */
60
+ function generatePartId(): string {
61
+ return `cache_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
62
+ }
63
+
64
+ /**
65
+ * Load cached response from file using Links Notation format
66
+ */
67
+ function loadCachedResponse(filePath: string): any | null {
68
+ try {
69
+ if (!existsSync(filePath)) {
70
+ return null;
71
+ }
72
+ const content = readFileSync(filePath, 'utf8');
73
+ // Decode from Links Notation format
74
+ return decode({ notation: content });
75
+ } catch (error: any) {
76
+ log.warn('Failed to load cached response', {
77
+ filePath,
78
+ error: error.message,
79
+ });
80
+ return null;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Save response to cache file using Links Notation format
86
+ */
87
+ function saveCachedResponse(filePath: string, response: any): void {
88
+ try {
89
+ const dir = dirname(filePath);
90
+ if (!existsSync(dir)) {
91
+ mkdirSync(dir, { recursive: true });
92
+ }
93
+ // Encode to Links Notation format
94
+ const encoded = encode({ obj: response });
95
+ writeFileSync(filePath, encoded, 'utf8');
96
+ log.info('Saved cached response', { filePath });
97
+ } catch (error: any) {
98
+ log.warn('Failed to save cached response', {
99
+ filePath,
100
+ error: error.message,
101
+ });
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Creates a cache language model that stores/retrieves responses
107
+ * Implements LanguageModelV2 interface for AI SDK 6.x compatibility
108
+ */
109
+ export function createCacheModel(
110
+ providerId: string,
111
+ modelId: string
112
+ ): LanguageModelV2 {
113
+ const model: LanguageModelV2 = {
114
+ specificationVersion: 'v2',
115
+ provider: 'link-assistant',
116
+ modelId: `${providerId}/${modelId}`,
117
+
118
+ // No external URLs are supported by this synthetic provider
119
+ supportedUrls: {},
120
+
121
+ async doGenerate(options: LanguageModelV2CallOptions) {
122
+ const cacheKey = generateCacheKey(options.prompt);
123
+ const cachePath = getCachePath(providerId, modelId, cacheKey);
124
+
125
+ // Try to load from cache first
126
+ const cached = loadCachedResponse(cachePath);
127
+ if (cached) {
128
+ log.info('Using cached response', { providerId, modelId, cacheKey });
129
+ return cached;
130
+ }
131
+
132
+ // Fall back to echo behavior
133
+ log.info('No cached response, using echo fallback', {
134
+ providerId,
135
+ modelId,
136
+ cacheKey,
137
+ });
138
+ const echoModel = createEchoModel(`${providerId}/${modelId}`);
139
+ const response = await echoModel.doGenerate(options);
140
+
141
+ // Save to cache for future use
142
+ saveCachedResponse(cachePath, response);
143
+
144
+ return response;
145
+ },
146
+
147
+ async doStream(options: LanguageModelV2CallOptions) {
148
+ const cacheKey = generateCacheKey(options.prompt);
149
+ const cachePath = getCachePath(providerId, modelId, cacheKey);
150
+
151
+ // Try to load from cache first
152
+ const cached = loadCachedResponse(cachePath);
153
+ if (cached) {
154
+ log.info('Using cached streaming response', {
155
+ providerId,
156
+ modelId,
157
+ cacheKey,
158
+ });
159
+
160
+ // For cached responses, we need to simulate streaming
161
+ // Extract the text from the cached response
162
+ const echoText =
163
+ cached.content?.[0]?.text || cached.text || 'Cached response';
164
+ const textPartId = generatePartId();
165
+
166
+ // Create a ReadableStream with LanguageModelV2StreamPart format
167
+ const stream = new ReadableStream({
168
+ async start(controller) {
169
+ // Emit text-start
170
+ controller.enqueue({
171
+ type: 'text-start',
172
+ id: textPartId,
173
+ providerMetadata: undefined,
174
+ });
175
+
176
+ // Emit the text in chunks for realistic streaming behavior
177
+ const chunkSize = 10;
178
+ for (let i = 0; i < echoText.length; i += chunkSize) {
179
+ const chunk = echoText.slice(i, i + chunkSize);
180
+ controller.enqueue({
181
+ type: 'text-delta',
182
+ id: textPartId,
183
+ delta: chunk,
184
+ providerMetadata: undefined,
185
+ });
186
+ }
187
+
188
+ // Emit text-end
189
+ controller.enqueue({
190
+ type: 'text-end',
191
+ id: textPartId,
192
+ providerMetadata: undefined,
193
+ });
194
+
195
+ // Emit finish event
196
+ controller.enqueue({
197
+ type: 'finish',
198
+ finishReason: 'stop',
199
+ usage: cached.usage || {
200
+ promptTokens: Math.ceil(echoText.length / 4),
201
+ completionTokens: Math.ceil(echoText.length / 4),
202
+ },
203
+ providerMetadata: undefined,
204
+ });
205
+
206
+ controller.close();
207
+ },
208
+ });
209
+
210
+ return {
211
+ stream,
212
+ request: undefined,
213
+ response: undefined,
214
+ warnings: [],
215
+ };
216
+ }
217
+
218
+ // Fall back to echo streaming behavior
219
+ log.info('No cached streaming response, using echo fallback', {
220
+ providerId,
221
+ modelId,
222
+ cacheKey,
223
+ });
224
+ const echoModel = createEchoModel(`${providerId}/${modelId}`);
225
+ const response = await echoModel.doStream(options);
226
+
227
+ // Note: We don't cache streaming responses as they're consumed immediately
228
+ return response;
229
+ },
230
+ };
231
+
232
+ return model;
233
+ }
234
+
235
+ /**
236
+ * Cache provider factory function
237
+ */
238
+ export function createCacheProvider(options?: { name?: string }) {
239
+ return {
240
+ languageModel(modelId: string): LanguageModelV2 {
241
+ // Parse provider/model from modelId like "opencode/grok-code"
242
+ const parts = modelId.split('/');
243
+ if (parts.length < 2) {
244
+ throw new Error(
245
+ `Invalid cache model ID: ${modelId}. Expected format: provider/model`
246
+ );
247
+ }
248
+ const [providerId, ...modelParts] = parts;
249
+ const actualModelId = modelParts.join('/');
250
+
251
+ return createCacheModel(providerId, actualModelId);
252
+ },
253
+ textEmbeddingModel() {
254
+ throw new Error('Cache provider does not support text embeddings');
255
+ },
256
+ };
257
+ }
258
+
259
+ export const cacheProvider = createCacheProvider();