agent-accelerator 0.1.0 → 0.3.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 (48) hide show
  1. package/README.md +174 -123
  2. package/{SYSTEM_PROMPT_AGENT.md → examples/prompts/SYSTEM_PROMPT_AGENT.md} +1 -1
  3. package/package.json +14 -9
  4. package/src/agent/agent.ts +385 -73
  5. package/src/agent/context.ts +1 -0
  6. package/src/agent/delegation.ts +443 -30
  7. package/src/agent/loop.ts +183 -53
  8. package/src/data/README.md +6 -6
  9. package/src/index.ts +163 -46
  10. package/src/models/catalog-cache.ts +60 -9
  11. package/src/models/catalog.ts +55 -8
  12. package/src/providers/google.ts +926 -0
  13. package/src/providers/openai-compat.ts +1147 -0
  14. package/src/providers/openai.ts +959 -0
  15. package/src/providers/openrouter-responses.ts +949 -0
  16. package/src/providers/openrouter.ts +1037 -0
  17. package/src/{ai-sdk → providers}/registry.ts +43 -55
  18. package/src/providers.ts +492 -0
  19. package/src/session/store.ts +806 -0
  20. package/src/streaming/sse-parser.ts +6 -4
  21. package/src/tools/executor.ts +23 -7
  22. package/src/tools/schema.ts +21 -11
  23. package/src/types/agent.ts +33 -2
  24. package/src/types/core.ts +3 -2
  25. package/src/types/message.ts +5 -0
  26. package/src/types/model.ts +3 -7
  27. package/src/types/provider-payloads.ts +2 -84
  28. package/src/types/response.ts +24 -0
  29. package/src/types/tool.ts +8 -0
  30. package/src/update-models.ts +56 -0
  31. package/src/utils/cache.ts +1 -1
  32. package/src/utils/documents.ts +517 -0
  33. package/src/utils/env.ts +0 -7
  34. package/src/{ai-sdk → utils}/errors.ts +61 -2
  35. package/src/utils/headers.ts +10 -20
  36. package/src/utils/media.ts +5 -2
  37. package/src/utils/retry.ts +89 -0
  38. package/src/utils/serialization.ts +15 -0
  39. package/src/utils/session.ts +36 -0
  40. package/src/ai-sdk/converters.ts +0 -342
  41. package/src/ai-sdk/executor.ts +0 -454
  42. package/src/ai-sdk/index.ts +0 -55
  43. package/src/ai-sdk/model-provider.ts +0 -303
  44. package/src/ai-sdk/options.ts +0 -306
  45. package/src/ai-sdk/provider.ts +0 -415
  46. package/src/tokens/counter.ts +0 -136
  47. /package/{SYSTEM_PROMPT.md → examples/prompts/SYSTEM_PROMPT.md} +0 -0
  48. /package/{SYSTEM_PROMPT_TOOLS.md → examples/prompts/SYSTEM_PROMPT_TOOLS.md} +0 -0
package/src/index.ts CHANGED
@@ -9,21 +9,49 @@ export type { CreateToolOptions } from "./tools/tool.ts";
9
9
  export { zodToJsonSchema, cleanJsonSchema } from "./tools/schema.ts";
10
10
  export { executeToolCalls } from "./tools/executor.ts";
11
11
 
12
+ // Client-side document conversion (optional anydoc peer — see utils/documents.ts)
13
+ export {
14
+ convertDocumentToMarkdown,
15
+ convertDocumentInput,
16
+ convert_document_to_markdown,
17
+ isAnydocAvailable,
18
+ resolveDocumentFormat,
19
+ truncateMarkdown,
20
+ buildDocumentXml,
21
+ modelSupportsFileInput,
22
+ preprocessFilePartsForBypass,
23
+ DocumentConversionError,
24
+ DEFAULT_DOCUMENT_MAX_CHARS,
25
+ MAX_DOCUMENT_FETCH_BYTES,
26
+ } from "./utils/documents.ts";
27
+ export type { ConvertDocumentOptions, ConvertedDocument } from "./utils/documents.ts";
28
+
12
29
  // Multi-Agent Delegation & Tools
13
30
  export {
14
31
  buildAgentTools,
32
+ agentToTool,
15
33
  createSubagentSpawnTool,
34
+ createChildSessionId,
35
+ createFixedChildSessionId,
36
+ isSessionDescendant,
37
+ getSubAgentTrace,
38
+ listSubAgentTraceIds,
39
+ subscribeToSubAgent,
16
40
  } from "./agent/delegation.ts";
17
- export type { DynamicSubagentTask } from "./agent/delegation.ts";
41
+ export type { DynamicSubagentTask, AgentAsToolTarget, SubAgentTrace } from "./agent/delegation.ts";
18
42
 
19
- // Providers & Registry — unified multi-provider layer powered by Vercel AI SDK
43
+ // Providers & Registry — unified multi-provider layer, all native REST
20
44
  export {
21
45
  getProvider,
22
46
  resolveModel,
23
47
  ModelProvider,
24
48
  ensureCustomProvider,
25
49
  normalizeProviderPrefix,
26
- } from "./ai-sdk/registry.ts";
50
+ } from "./providers/registry.ts";
51
+ export type {
52
+ ModelProviderInstance,
53
+ ModelProviderConfig,
54
+ } from "./providers/registry.ts";
27
55
  export {
28
56
  getModelFromCatalog,
29
57
  getModelThinkingInfo,
@@ -37,48 +65,78 @@ export {
37
65
  getCatalogTTL,
38
66
  getCacheDir,
39
67
  getCacheFilePath,
68
+ isValidCatalogPayload,
69
+ createGenericModelSpec,
40
70
  DEFAULT_CATALOG_TTL_MS,
41
71
  type CatalogStatus,
42
72
  type RefreshCatalogOptions,
43
73
  } from "./models/catalog.ts";
44
- export { AiSdkBaseProvider as BaseProvider } from "./ai-sdk/model-provider.ts";
74
+ export { resolveEffectiveThinking } from "./agent/agent.ts";
75
+ export { withRetries, isTransientError } from "./utils/retry.ts";
76
+ export { toConciseProviderError, assertModalitiesSupported, assertNoVideoPartsOnResponses } from "./utils/errors.ts";
77
+ // Native provider implementations. `openrouter-responses.ts` (discontinued
78
+ // beta-Responses transport) stays importable directly but is wired nowhere.
45
79
  export {
46
- mapThinkingToReasoning,
47
- resolveEffectiveThinking,
48
- mapToolChoice,
49
- mapThinkingToProviderOptions,
50
- mapServiceTierToProviderOptions,
51
- mapCacheToProviderOptions,
52
- buildAiSdkCallOptions,
53
- withAiSdkRetries,
54
- isTransientAiSdkError,
55
- } from "./ai-sdk/options.ts";
56
- export { toConciseProviderError, assertModalitiesSupported } from "./ai-sdk/errors.ts";
57
-
58
- // Provider Implementations powered by Vercel AI SDK
59
- export {
60
- GoogleAIStudioProvider,
61
- GoogleAiSdkProvider,
62
- OpenCodeProvider,
63
- OpenCodeAiSdkProvider,
64
- OpenRouterProvider,
65
- OpenRouterAiSdkProvider,
66
- OpenAIProvider,
67
- OpenAiAiSdkProvider,
68
- OpenAICompatibleProvider,
69
- CustomAiSdkProvider,
70
- AiSdkBaseProvider,
80
+ OpenAICompatibleChatProvider,
81
+ OpenAICompatibleChatProvider as OpenAICompatibleProvider,
71
82
  createOpenAICompatibleProvider,
72
83
  createCustomProvider,
73
84
  CustomProvider,
74
- createGenericModelSpec,
75
- } from "./ai-sdk/model-provider.ts";
76
- export type { CustomProviderOptions } from "./ai-sdk/model-provider.ts";
85
+ } from "./providers/openai-compat.ts";
86
+ export type { CustomProviderOptions } from "./providers/openai-compat.ts";
87
+
88
+ // Canonical provider contract (provider-agnostic) + native adapters.
89
+ export {
90
+ GoogleInteractionsProvider,
91
+ GoogleInteractionsProvider as GoogleAIStudioProvider,
92
+ clearInteractionChains,
93
+ } from "./providers/google.ts";
94
+ export {
95
+ OpenRouterChatCompletionsProvider,
96
+ OpenRouterChatCompletionsProvider as OpenRouterProvider,
97
+ } from "./providers/openrouter.ts";
98
+ // Discontinued beta-Responses transport, retained frozen as migration
99
+ // evidence (importable via `./providers/openrouter-responses.ts`, not wired
100
+ // anywhere): OpenRouterResponsesProvider.
101
+ export {
102
+ OpenAIResponsesProvider,
103
+ OpenAIResponsesProvider as OpenAIProvider,
104
+ } from "./providers/openai.ts";
105
+ export {
106
+ emitProviderWarning,
107
+ clearEmittedWarnings,
108
+ noteProviderTurn,
109
+ lastProviderFor,
110
+ isMixedProviderSession,
111
+ clearSessionRouting,
112
+ mapThinkingLevelToGoogle,
113
+ mapServiceTierToGoogle,
114
+ applyCacheForGoogle,
115
+ mapThinkingLevelToOpenRouter,
116
+ mapServiceTierToOpenRouter,
117
+ applyCacheForOpenRouter,
118
+ mapToolChoiceToOpenRouter,
119
+ mapThinkingLevelToOpenRouterChat,
120
+ mapToolChoiceToOpenRouterChat,
121
+ mapThinkingLevelToOpenAI,
122
+ mapServiceTierToOpenAI,
123
+ applyCacheForOpenAI,
124
+ applyCacheForCustom,
125
+ mapToolChoiceToOpenAI,
126
+ normalizeToolChoice,
127
+ parseStreamedToolArguments,
128
+ } from "./providers.ts";
129
+ export type {
130
+ ProviderCapabilityStatus,
131
+ CanonicalToolChoiceMode,
132
+ OpenRouterToolChoice,
133
+ OpenRouterChatToolChoice,
134
+ OpenAIToolChoice,
135
+ } from "./providers.ts";
77
136
 
78
137
  export {
79
138
  GOOGLE_MODELS,
80
139
  OPENAI_MODELS,
81
- OPENCODE_MODELS,
82
140
  OPENROUTER_MODELS,
83
141
  } from "./models/catalog.ts";
84
142
 
@@ -101,14 +159,6 @@ export type {
101
159
  CachedContentMetadata,
102
160
  } from "./utils/cache.ts";
103
161
 
104
-
105
- export {
106
- countTokens,
107
- estimateTokensFromText,
108
- estimateTokensFromMessage,
109
- estimateTokensFromPart,
110
- } from "./tokens/counter.ts";
111
-
112
162
  // Streaming & Events
113
163
  export { AssistantMessageEventStream } from "./streaming/event-stream.ts";
114
164
  export { SSEParser } from "./streaming/sse-parser.ts";
@@ -116,13 +166,37 @@ export { SSEParser } from "./streaming/sse-parser.ts";
116
166
  // Responses
117
167
  export { AgentResponse } from "./types/response.ts";
118
168
 
169
+ // Session persistence & telemetry (conversation history + cost rollup)
170
+ export {
171
+ SessionTelemetry,
172
+ loadSessionFile,
173
+ saveSessionFile,
174
+ loadSessionDir,
175
+ saveSessionDir,
176
+ writeFileAtomic,
177
+ findLatestSessionDir,
178
+ sessionDirFor,
179
+ extractMediaToDir,
180
+ resolveMediaPaths,
181
+ buildSessionData,
182
+ serializeSession,
183
+ deserializeSession,
184
+ getSessionContextWindow,
185
+ computeSessionTurnCost,
186
+ formatSessionTokens,
187
+ formatSessionCost,
188
+ formatSessionBanner,
189
+ emptyTotals,
190
+ } from "./session/store.ts";
191
+ export type { PersistedAgentSession, SessionTotals, SessionAgentLike, LoadedSessionDir, PersistedSubAgentTrace } from "./session/store.ts";
192
+
119
193
  // Utilities
120
- export { createSessionId } from "./utils/session.ts";
194
+ export { createSessionId, hashSessionPart, createTrackingId } from "./utils/session.ts";
121
195
  export { getApiKey, getEnv } from "./utils/env.ts";
122
196
  export { buildSessionHeaders } from "./utils/headers.ts";
123
197
  export { normalizeMediaInput, inferMimeType } from "./utils/media.ts";
124
198
  export { base64ToBytes, bytesToBase64 } from "./utils/base64.ts";
125
- export { toJsonSafe, safeStringify } from "./utils/serialization.ts";
199
+ export { toJsonSafe, safeStringify, escapeXml } from "./utils/serialization.ts";
126
200
 
127
201
  // Re-export Zod
128
202
  export { z };
@@ -174,6 +248,7 @@ export type {
174
248
  export type {
175
249
  AgentResponseJSON,
176
250
  SubAgentExecutionMetadata,
251
+ SubAgentStep,
177
252
  StreamEvent,
178
253
  StreamEventType,
179
254
  } from "./types/response.ts";
@@ -181,10 +256,52 @@ export type {
181
256
  export type {
182
257
  AgentConfig,
183
258
  AgentRunOptions,
259
+ AgentTurnEvent,
184
260
  DynamicSubagentsConfig,
185
261
  } from "./types/agent.ts";
186
262
 
263
+ // Provider wire payload types (back-compat inspection of raw requests/responses,
264
+ // e.g. `res.raw.request.body as OpenAIChatCompletionRequest`).
265
+ // NOTE: wire `OpenAIToolChoice` intentionally omitted — it duplicates the
266
+ // canonical `OpenAIToolChoice` exported above (same shape); use that one.
187
267
  export type {
188
- ModelProviderInstance,
189
- ModelProviderConfig,
190
- } from "./ai-sdk/registry.ts";
268
+ GoogleThinkingLevel,
269
+ GoogleThinkingConfig,
270
+ GoogleFunctionCallingMode,
271
+ GoogleFunctionCallingConfig,
272
+ GoogleToolConfig,
273
+ GoogleFunctionDeclaration,
274
+ GoogleTool,
275
+ GoogleBlob,
276
+ GooglePart,
277
+ GoogleContent,
278
+ GoogleGenerationConfig,
279
+ GoogleCandidate,
280
+ GoogleUsageMetadata,
281
+ GoogleGenerateContentRequest,
282
+ GoogleGenerateContentResponse,
283
+ OpenRouterProviderRouting,
284
+ OpenRouterReasoning,
285
+ OpenRouterParameters,
286
+ OpenRouterUsage,
287
+ OpenRouterChatRequest,
288
+ OpenRouterResponse,
289
+ OpenAIMessageRole,
290
+ OpenAIReasoningEffort,
291
+ OpenAIServiceTier,
292
+ OpenAITextPart,
293
+ OpenAIImageUrlPart,
294
+ OpenAIInputAudioPart,
295
+ OpenAIVideoUrlPart,
296
+ OpenAIContentPart,
297
+ OpenAIToolCall,
298
+ OpenAIMessage,
299
+ OpenAITool,
300
+ OpenAIUsage,
301
+ OpenAIChoice,
302
+ OpenAIDelta,
303
+ OpenAIChunkChoice,
304
+ OpenAIChatCompletionRequest,
305
+ OpenAIChatCompletionResponse,
306
+ OpenAIChatCompletionChunk,
307
+ } from "./types/provider-payloads.ts";
@@ -1,5 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import * as fs from "node:fs";
3
+ import { getEnv } from "../utils/env.ts";
3
4
 
4
5
  /** Default TTL: 12 Hours (in milliseconds). */
5
6
  export const DEFAULT_CATALOG_TTL_MS = 12 * 60 * 60 * 1000;
@@ -65,14 +66,20 @@ function notifyUpdateListeners(): void {
65
66
  }
66
67
  }
67
68
 
68
- /** Non-configurable cache directory: src/data */
69
+ /**
70
+ * Catalog cache directory. Defaults to `src/data` (repo convention); override
71
+ * with `AGENT_CACHE_DIR` when embedding as a package or running on a
72
+ * read-only filesystem (serverless/containers).
73
+ */
69
74
  export function getCacheDir(): string {
75
+ const override = getEnv("AGENT_CACHE_DIR");
76
+ if (override && override.trim()) return path.resolve(override.trim());
70
77
  return path.resolve(process.cwd(), "src/data");
71
78
  }
72
79
 
73
- /** Non-configurable cache file path: src/data/models-cache.json */
80
+ /** Non-configurable cache file path: src/data/models.dev.json */
74
81
  export function getCacheFilePath(): string {
75
- return path.resolve(process.cwd(), "src/data/models-cache.json");
82
+ return path.resolve(process.cwd(), "src/data/models.dev.json");
76
83
  }
77
84
 
78
85
  function readJsonFileSync(filePath: string): any {
@@ -99,7 +106,7 @@ let activeFetchedAt: number | undefined = undefined;
99
106
  let activeTtlMs: number = globalCatalogTtlMs;
100
107
  let activeFromCache = false;
101
108
 
102
- // Synchronous bootstrap: load from src/data/models-cache.json if present
109
+ // Synchronous bootstrap: load from src/data/models.dev.json if present
103
110
  function initializeCatalogSync(): void {
104
111
  const cachePath = getCacheFilePath();
105
112
  const cached = readJsonFileSync(cachePath);
@@ -165,9 +172,30 @@ export function getCatalogStatus(): CatalogStatus {
165
172
  };
166
173
  }
167
174
 
175
+ /**
176
+ * Structural check for a models.dev catalog payload: a map of provider ids
177
+ * to entries that each carry a `models` map. Rejects chat-completion
178
+ * payloads, error envelopes, and other non-catalog JSON (e.g. from mocked
179
+ * `fetch` in tests) BEFORE they can overwrite the good on-disk cache.
180
+ */
181
+ export function isValidCatalogPayload(data: unknown): data is Record<string, any> {
182
+ if (!data || typeof data !== "object" || Array.isArray(data)) return false;
183
+ const entries = Object.entries(data as Record<string, unknown>);
184
+ if (entries.length < 3) return false;
185
+ let providersWithModels = 0;
186
+ for (const [, value] of entries) {
187
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
188
+ const models = (value as Record<string, unknown>).models;
189
+ if (!models || typeof models !== "object" || Array.isArray(models)) return false;
190
+ if (Object.keys(models).length > 0) providersWithModels++;
191
+ }
192
+ // Guard against degenerate-but-shaped payloads (e.g. `{a:{models:{}}}`).
193
+ return providersWithModels >= 3;
194
+ }
195
+
168
196
  /**
169
197
  * Refreshes the model catalog by downloading from models.dev
170
- * directly into src/data/models-cache.json with a 12-hour TTL.
198
+ * directly into src/data/models.dev.json with a 12-hour TTL.
171
199
  */
172
200
  export async function refreshModelCatalog(options: RefreshCatalogOptions = {}): Promise<CatalogStatus> {
173
201
  const effectiveTtl = options.ttlMs ?? globalCatalogTtlMs;
@@ -211,6 +239,21 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
211
239
  }
212
240
 
213
241
  freshData = (await res.json()) as Record<string, any>;
242
+
243
+ // Never let a non-catalog payload (mocked fetch, proxy error page,
244
+ // chat-completion stub) overwrite the good on-disk cache.
245
+ if (!isValidCatalogPayload(freshData)) {
246
+ clearTimeout(timer);
247
+ const existing = readJsonFileSync(cachePath);
248
+ if (existing && existing.data) {
249
+ activeCatalog = existing.data;
250
+ activeFetchedAt = existing.fetchedAt;
251
+ activeTtlMs = existing.ttlMs || effectiveTtl;
252
+ activeFromCache = true;
253
+ notifyUpdateListeners();
254
+ }
255
+ return getCatalogStatus();
256
+ }
214
257
  } catch (err: any) {
215
258
  clearTimeout(timer);
216
259
  // If download fails, retain disk cache if available
@@ -236,15 +279,23 @@ export async function refreshModelCatalog(options: RefreshCatalogOptions = {}):
236
279
  data: freshData,
237
280
  };
238
281
 
239
- // Save to src/data/models-cache.json
240
- writeJsonFileSync(cachePath, cachePayload);
241
-
242
- // Update in-memory state
282
+ // Update in-memory state FIRST so a read-only filesystem still serves the
283
+ // fresh catalog for this process; disk persistence below is best-effort.
243
284
  activeCatalog = freshData;
244
285
  activeFetchedAt = fetchedAt;
245
286
  activeTtlMs = effectiveTtl;
246
287
  activeFromCache = true;
247
288
 
289
+ // Save to src/data/models.dev.json (or the AGENT_CACHE_DIR override)
290
+ try {
291
+ writeJsonFileSync(cachePath, cachePayload);
292
+ } catch (err) {
293
+ activeFromCache = false;
294
+ console.warn(
295
+ `[Agent Accelerator] Could not write model catalog cache to ${cachePath} (${err instanceof Error ? err.message : String(err)}). Continuing with the in-memory catalog.`
296
+ );
297
+ }
298
+
248
299
  notifyUpdateListeners();
249
300
  return getCatalogStatus();
250
301
  }
@@ -22,12 +22,10 @@ import type { ThinkingLevel } from "../types/core.ts";
22
22
 
23
23
  // ---------------------------------------------------------------------------
24
24
  // Provider aliases — maps our internal ids to models.dev keys
25
- // battle-tested: opencode covers zen/go, google covers vertex variants
25
+ // battle-tested: google covers vertex variants
26
26
  // ---------------------------------------------------------------------------
27
27
  const PROVIDER_ALIASES: Record<string, string[]> = {
28
28
  google: ["google", "google-vertex", "google-vertex-anthropic"],
29
- opencode: ["opencode", "opencode-go"],
30
- "opencode-go": ["opencode-go", "opencode"],
31
29
  openrouter: ["openrouter"],
32
30
  openai: ["openai"],
33
31
  };
@@ -42,6 +40,11 @@ const MODALITY_OVERRIDES: Record<string, { input?: string[]; output?: string[] }
42
40
  // verified end-to-end via examples/08. If routing changes, the runtime
43
41
  // 404 still surfaces as a one-line error.
44
42
  "openrouter/nvidia/nemotron-3.5-lightning:free": { input: ["text", "pdf"] },
43
+ // Live-verified 2026-09-24: `stealth/space-bunny-alpha` accepts PDF
44
+ // `input_file` over Responses and PDFs via the file-parser plugin over Chat
45
+ // Completions (Berkshire letter + sample-files PDF both completed), though
46
+ // models.dev lists text/image/video only.
47
+ "openrouter/stealth/space-bunny-alpha": { input: ["text", "image", "video", "pdf"] },
45
48
  };
46
49
 
47
50
  function applyModalityOverrides(provider: string, modelId: string, base: ModelModalities): ModelModalities {
@@ -241,7 +244,7 @@ export function getModelThinkingInfo(provider: string, modelId: string): ModelTh
241
244
  if (!spec) {
242
245
  return {
243
246
  supportsThinking: true,
244
- allowedLevels: ["none", "minimal", "low", "medium", "high", "xhigh", "dynamic"],
247
+ allowedLevels: ["none", "minimal", "low", "medium", "high", "xhigh", "max", "dynamic"],
245
248
  supportsDisable: true,
246
249
  description: "Model not found in catalog; generic thinking levels allowed.",
247
250
  };
@@ -292,6 +295,7 @@ export function getModelThinkingInfo(provider: string, modelId: string): ModelTh
292
295
  allowed.add("medium");
293
296
  allowed.add("high");
294
297
  allowed.add("xhigh");
298
+ allowed.add("max");
295
299
  }
296
300
 
297
301
  // Fixed reasoning model (no reasoning_options) e.g. DeepSeek-R1, QwQ-32B
@@ -305,6 +309,11 @@ export function getModelThinkingInfo(provider: string, modelId: string): ModelTh
305
309
  };
306
310
  }
307
311
 
312
+ // "dynamic" means server default (the level is omitted from the request),
313
+ // so it is expressible on every configurable thinking model regardless of
314
+ // which concrete effort/toggle/budget options the catalog lists.
315
+ allowed.add("dynamic");
316
+
308
317
  const allowedLevels = Array.from(allowed);
309
318
  const supportsDisable = allowed.has("none") || !!toggleOpt || Boolean(budgetOpt && (budgetOpt.min === undefined || budgetOpt.min <= 0));
310
319
 
@@ -437,7 +446,7 @@ export function validateModelThinking(provider: string, modelId: string, request
437
446
 
438
447
  // ---------------------------------------------------------------------------
439
448
  // getModelsForProvider — battle-tested view for BaseProvider.models
440
- // Opencode total context length now comes from catalog, not stale hardcoded file.
449
+ // Total context length now comes from catalog, not stale hardcoded file.
441
450
  // ---------------------------------------------------------------------------
442
451
  /**
443
452
  * Returns normalized catalog specs for a provider and its configured aliases.
@@ -466,7 +475,6 @@ export function getModelsForProvider(provider: string): ModelSpec[] {
466
475
 
467
476
  export const GOOGLE_MODELS: ModelSpec[] = [...getModelsForProvider("google")];
468
477
  export const OPENAI_MODELS: ModelSpec[] = [...getModelsForProvider("openai")];
469
- export const OPENCODE_MODELS: ModelSpec[] = [...getModelsForProvider("opencode")];
470
478
  export const OPENROUTER_MODELS: ModelSpec[] = [...getModelsForProvider("openrouter")];
471
479
 
472
480
  function refreshExportedModels(): void {
@@ -474,8 +482,6 @@ function refreshExportedModels(): void {
474
482
  GOOGLE_MODELS.push(...getModelsForProvider("google"));
475
483
  OPENAI_MODELS.length = 0;
476
484
  OPENAI_MODELS.push(...getModelsForProvider("openai"));
477
- OPENCODE_MODELS.length = 0;
478
- OPENCODE_MODELS.push(...getModelsForProvider("opencode"));
479
485
  OPENROUTER_MODELS.length = 0;
480
486
  OPENROUTER_MODELS.push(...getModelsForProvider("openrouter"));
481
487
  }
@@ -497,7 +503,48 @@ export {
497
503
  getCatalogTTL,
498
504
  getCacheDir,
499
505
  getCacheFilePath,
506
+ isValidCatalogPayload,
500
507
  DEFAULT_CATALOG_TTL_MS,
501
508
  type CatalogStatus,
502
509
  type RefreshCatalogOptions,
503
510
  } from "./catalog-cache.ts";
511
+
512
+ /**
513
+ * Permissive placeholder so custom endpoints never fail preflight:
514
+ * allows every ThinkingLevel (none -> max + dynamic).
515
+ * @example `const spec = createGenericModelSpec("groq", "llama-3.3-70b-versatile");`
516
+ */
517
+ export function createGenericModelSpec(provider: string, modelId: string): ModelSpec {
518
+ const clean = modelId.includes("/") ? modelId.split("/").slice(1).join("/") : modelId;
519
+ return {
520
+ id: clean,
521
+ provider: provider as ProviderId,
522
+ name: clean,
523
+ contextWindow: 128000,
524
+ maxOutputTokens: 8192,
525
+ limit: { context: 128000, output: 8192 },
526
+ cost: {},
527
+ modalities: { input: ["text"], output: ["text"] },
528
+ reasoning: true,
529
+ reasoning_options: [
530
+ { type: "toggle" },
531
+ { type: "effort", values: ["minimal", "low", "medium", "high", "xhigh", "max"] },
532
+ { type: "budget_tokens", min: 0, max: 128000 },
533
+ ],
534
+ tool_call: true,
535
+ capabilities: {
536
+ supportsThinking: true,
537
+ supportsThinkingLevel: true,
538
+ supportsThinkingBudget: true,
539
+ supportsReasoningToggle: true,
540
+ supportsReasoningEffort: true,
541
+ supportsImplicitCaching: true,
542
+ supportsExplicitCaching: false,
543
+ supportsLongCacheRetention: false,
544
+ supportsParallelToolCalls: true,
545
+ supportsStreaming: true,
546
+ modalities: ["text"],
547
+ },
548
+ pricing: {},
549
+ };
550
+ }