@cortexkit/opencode-magic-context 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,216 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+
4
+ import type { ConflictResult } from "./conflict-detector";
5
+ import { readJsoncFile } from "./jsonc-parser";
6
+ import { getOpenCodeConfigPaths } from "./opencode-config-dir";
7
+
8
+ type JsonObject = Record<string, unknown>;
9
+
10
+ const CONFLICTING_OMO_HOOKS = [
11
+ "context-window-monitor",
12
+ "preemptive-compaction",
13
+ "anthropic-context-window-limit-recovery",
14
+ ] as const;
15
+
16
+ const OMO_CONFIG_NAMES = [
17
+ "oh-my-openagent.jsonc",
18
+ "oh-my-openagent.json",
19
+ "oh-my-opencode.jsonc",
20
+ "oh-my-opencode.json",
21
+ ] as const;
22
+
23
+ function ensureParentDir(filePath: string): void {
24
+ mkdirSync(dirname(filePath), { recursive: true });
25
+ }
26
+
27
+ function isRecord(value: unknown): value is JsonObject {
28
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29
+ }
30
+
31
+ function asStringArray(value: unknown): string[] {
32
+ return Array.isArray(value)
33
+ ? value.filter((item): item is string => typeof item === "string")
34
+ : [];
35
+ }
36
+
37
+ function readConfig(filePath: string): JsonObject | null {
38
+ if (!existsSync(filePath)) {
39
+ return {};
40
+ }
41
+
42
+ return readJsoncFile<JsonObject>(filePath);
43
+ }
44
+
45
+ // Intentional: conflict-fixer uses JSON.stringify (not comment-json) because this module
46
+ // is imported by the TUI process which loads raw source and cannot resolve npm dependencies.
47
+ // Comment preservation for config writes is handled by the CLI paths (doctor.ts, setup.ts).
48
+ function writeConfig(filePath: string, config: JsonObject): void {
49
+ ensureParentDir(filePath);
50
+ writeFileSync(filePath, `${JSON.stringify(config, null, 2)}\n`);
51
+ }
52
+
53
+ function resolveUserOpenCodeConfigPath(): string {
54
+ const paths = getOpenCodeConfigPaths({ binary: "opencode" });
55
+ if (existsSync(paths.configJsonc)) return paths.configJsonc;
56
+ return paths.configJson;
57
+ }
58
+
59
+ function collectOpenCodeConfigPaths(directory: string): string[] {
60
+ const paths = new Set<string>();
61
+ const userConfig = resolveUserOpenCodeConfigPath();
62
+
63
+ paths.add(userConfig);
64
+
65
+ for (const filePath of [
66
+ join(directory, ".opencode", "opencode.jsonc"),
67
+ join(directory, ".opencode", "opencode.json"),
68
+ join(directory, "opencode.jsonc"),
69
+ join(directory, "opencode.json"),
70
+ ]) {
71
+ if (existsSync(filePath)) {
72
+ paths.add(filePath);
73
+ }
74
+ }
75
+
76
+ return [...paths];
77
+ }
78
+
79
+ function collectOmoConfigPaths(directory: string): string[] {
80
+ const paths = new Set<string>();
81
+ const configDir = getOpenCodeConfigPaths({ binary: "opencode" }).configDir;
82
+
83
+ for (const fileName of OMO_CONFIG_NAMES) {
84
+ const userPath = join(configDir, fileName);
85
+ const projectPath = join(directory, fileName);
86
+
87
+ if (existsSync(userPath)) {
88
+ paths.add(userPath);
89
+ }
90
+
91
+ if (existsSync(projectPath)) {
92
+ paths.add(projectPath);
93
+ }
94
+ }
95
+
96
+ if (paths.size === 0) {
97
+ paths.add(join(configDir, "oh-my-openagent.json"));
98
+ }
99
+
100
+ return [...paths];
101
+ }
102
+
103
+ export function fixConflicts(directory: string, conflicts: ConflictResult["conflicts"]): string[] {
104
+ const actions: string[] = [];
105
+ let updatedCompaction = false;
106
+ let removedDcpPlugin = false;
107
+ let disabledOmoHooks = false;
108
+
109
+ if (conflicts.compactionAuto || conflicts.compactionPrune || conflicts.dcpPlugin) {
110
+ for (const configPath of collectOpenCodeConfigPaths(directory)) {
111
+ const config = readConfig(configPath);
112
+ if (!config) {
113
+ continue;
114
+ }
115
+
116
+ let changed = false;
117
+
118
+ if (conflicts.compactionAuto || conflicts.compactionPrune) {
119
+ const compaction = isRecord(config.compaction) ? { ...config.compaction } : {};
120
+
121
+ if (compaction.auto !== false) {
122
+ compaction.auto = false;
123
+ changed = true;
124
+ updatedCompaction = true;
125
+ }
126
+
127
+ if (compaction.prune !== false) {
128
+ compaction.prune = false;
129
+ changed = true;
130
+ updatedCompaction = true;
131
+ }
132
+
133
+ config.compaction = compaction;
134
+ }
135
+
136
+ if (conflicts.dcpPlugin) {
137
+ const plugins = asStringArray(config.plugin);
138
+ const filteredPlugins = plugins.filter(
139
+ (plugin) => !plugin.includes("opencode-dcp"),
140
+ );
141
+
142
+ if (filteredPlugins.length !== plugins.length) {
143
+ config.plugin = filteredPlugins;
144
+ changed = true;
145
+ removedDcpPlugin = true;
146
+ }
147
+ }
148
+
149
+ if (changed) {
150
+ writeConfig(configPath, config);
151
+ }
152
+ }
153
+ }
154
+
155
+ if (
156
+ conflicts.omoContextWindowMonitor ||
157
+ conflicts.omoPreemptiveCompaction ||
158
+ conflicts.omoAnthropicRecovery
159
+ ) {
160
+ const hooksToDisable = new Set<string>();
161
+ if (conflicts.omoContextWindowMonitor) {
162
+ hooksToDisable.add("context-window-monitor");
163
+ }
164
+ if (conflicts.omoPreemptiveCompaction) {
165
+ hooksToDisable.add("preemptive-compaction");
166
+ }
167
+ if (conflicts.omoAnthropicRecovery) {
168
+ hooksToDisable.add("anthropic-context-window-limit-recovery");
169
+ }
170
+
171
+ for (const configPath of collectOmoConfigPaths(directory)) {
172
+ const config = readConfig(configPath);
173
+ if (!config) {
174
+ continue;
175
+ }
176
+
177
+ const disabledHooks = new Set(asStringArray(config.disabled_hooks));
178
+ let changed = false;
179
+
180
+ for (const hook of hooksToDisable) {
181
+ if (!disabledHooks.has(hook)) {
182
+ disabledHooks.add(hook);
183
+ changed = true;
184
+ disabledOmoHooks = true;
185
+ }
186
+ }
187
+
188
+ if (changed) {
189
+ config.disabled_hooks = [
190
+ ...CONFLICTING_OMO_HOOKS.filter((hook) => disabledHooks.has(hook)),
191
+ ...[...disabledHooks].filter(
192
+ (hook) =>
193
+ !CONFLICTING_OMO_HOOKS.includes(
194
+ hook as (typeof CONFLICTING_OMO_HOOKS)[number],
195
+ ),
196
+ ),
197
+ ];
198
+ writeConfig(configPath, config);
199
+ }
200
+ }
201
+ }
202
+
203
+ if (updatedCompaction) {
204
+ actions.push("Disabled auto-compaction");
205
+ }
206
+
207
+ if (removedDcpPlugin) {
208
+ actions.push("Removed opencode-dcp plugin");
209
+ }
210
+
211
+ if (disabledOmoHooks) {
212
+ actions.push("Disabled conflicting oh-my-opencode hooks");
213
+ }
214
+
215
+ return actions;
216
+ }
@@ -0,0 +1,10 @@
1
+ import * as os from "node:os";
2
+ import * as path from "node:path";
3
+
4
+ export function getDataDir(): string {
5
+ return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
6
+ }
7
+
8
+ export function getOpenCodeStorageDir(): string {
9
+ return path.join(getDataDir(), "opencode", "storage");
10
+ }
@@ -0,0 +1,3 @@
1
+ export function getErrorMessage(error: unknown): string {
2
+ return error instanceof Error ? error.message : String(error);
3
+ }
@@ -0,0 +1,5 @@
1
+ export function formatBytes(bytes: number): string {
2
+ if (bytes < 1024) return `${bytes}B`;
3
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
4
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
5
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./logger";
2
+ export * from "./model-requirements";
3
+ export * from "./model-suggestion-retry";
4
+ export * from "./normalize-sdk-response";
@@ -0,0 +1 @@
1
+ export const OMO_INTERNAL_INITIATOR_MARKER = "<!-- OMO_INTERNAL_INITIATOR -->";
@@ -0,0 +1,138 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+
3
+ function stripJsonComments(content: string): string {
4
+ let result = "";
5
+ let inString = false;
6
+ let escaped = false;
7
+ let inLineComment = false;
8
+ let inBlockComment = false;
9
+
10
+ for (let index = 0; index < content.length; index += 1) {
11
+ const char = content[index];
12
+ const next = content[index + 1];
13
+
14
+ if (inLineComment) {
15
+ if (char === "\n") {
16
+ inLineComment = false;
17
+ result += char;
18
+ }
19
+ continue;
20
+ }
21
+
22
+ if (inBlockComment) {
23
+ if (char === "*" && next === "/") {
24
+ inBlockComment = false;
25
+ index += 1;
26
+ }
27
+ continue;
28
+ }
29
+
30
+ if (inString) {
31
+ result += char;
32
+ if (escaped) {
33
+ escaped = false;
34
+ } else if (char === "\\") {
35
+ escaped = true;
36
+ } else if (char === '"') {
37
+ inString = false;
38
+ }
39
+ continue;
40
+ }
41
+
42
+ if (char === '"') {
43
+ inString = true;
44
+ result += char;
45
+ continue;
46
+ }
47
+
48
+ if (char === "/" && next === "/") {
49
+ inLineComment = true;
50
+ index += 1;
51
+ continue;
52
+ }
53
+
54
+ if (char === "/" && next === "*") {
55
+ inBlockComment = true;
56
+ index += 1;
57
+ continue;
58
+ }
59
+
60
+ result += char;
61
+ }
62
+
63
+ return result;
64
+ }
65
+
66
+ function stripTrailingCommas(content: string): string {
67
+ let result = "";
68
+ let inString = false;
69
+ let escaped = false;
70
+
71
+ for (let index = 0; index < content.length; index += 1) {
72
+ const char = content[index];
73
+
74
+ if (inString) {
75
+ result += char;
76
+ if (escaped) {
77
+ escaped = false;
78
+ } else if (char === "\\") {
79
+ escaped = true;
80
+ } else if (char === '"') {
81
+ inString = false;
82
+ }
83
+ continue;
84
+ }
85
+
86
+ if (char === '"') {
87
+ inString = true;
88
+ result += char;
89
+ continue;
90
+ }
91
+
92
+ if (char === ",") {
93
+ let lookahead = index + 1;
94
+ while (lookahead < content.length && /\s/.test(content[lookahead] ?? "")) {
95
+ lookahead += 1;
96
+ }
97
+ const next = content[lookahead];
98
+ if (next === "}" || next === "]") {
99
+ continue;
100
+ }
101
+ }
102
+
103
+ result += char;
104
+ }
105
+
106
+ return result;
107
+ }
108
+
109
+ export function parseJsonc<T = unknown>(content: string): T {
110
+ const normalized = stripTrailingCommas(stripJsonComments(content));
111
+ return JSON.parse(normalized) as T;
112
+ }
113
+
114
+ export function readJsoncFile<T = unknown>(filePath: string): T | null {
115
+ try {
116
+ return parseJsonc<T>(readFileSync(filePath, "utf-8"));
117
+ } catch (_error) {
118
+ return null;
119
+ }
120
+ }
121
+
122
+ export function detectConfigFile(basePath: string): {
123
+ format: "json" | "jsonc" | "none";
124
+ path: string;
125
+ } {
126
+ const jsoncPath = `${basePath}.jsonc`;
127
+ const jsonPath = `${basePath}.json`;
128
+
129
+ if (existsSync(jsoncPath)) {
130
+ return { format: "jsonc", path: jsoncPath };
131
+ }
132
+
133
+ if (existsSync(jsonPath)) {
134
+ return { format: "json", path: jsonPath };
135
+ }
136
+
137
+ return { format: "none", path: jsoncPath };
138
+ }
@@ -0,0 +1,63 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+
5
+ const logFile = path.join(os.tmpdir(), "magic-context.log");
6
+ const isTestEnv = process.env.NODE_ENV === "test";
7
+
8
+ let buffer: string[] = [];
9
+ let flushTimer: ReturnType<typeof setTimeout> | null = null;
10
+ const FLUSH_INTERVAL_MS = 500;
11
+ const BUFFER_SIZE_LIMIT = 50;
12
+
13
+ function flush(): void {
14
+ if (flushTimer) {
15
+ clearTimeout(flushTimer);
16
+ flushTimer = null;
17
+ }
18
+ if (buffer.length === 0) return;
19
+ const data = buffer.join("");
20
+ buffer = [];
21
+ try {
22
+ fs.appendFileSync(logFile, data);
23
+ } catch {
24
+ // Intentional: logging must never throw
25
+ }
26
+ }
27
+
28
+ function scheduleFlush(): void {
29
+ if (flushTimer) return;
30
+ flushTimer = setTimeout(() => {
31
+ flushTimer = null;
32
+ flush();
33
+ }, FLUSH_INTERVAL_MS);
34
+ }
35
+
36
+ export function log(message: string, data?: unknown): void {
37
+ if (isTestEnv) return;
38
+ try {
39
+ const timestamp = new Date().toISOString();
40
+ const serialized = data === undefined ? "" : ` ${JSON.stringify(data)}`;
41
+ buffer.push(`[${timestamp}] ${message}${serialized}\n`);
42
+ if (buffer.length >= BUFFER_SIZE_LIMIT) {
43
+ flush();
44
+ } else {
45
+ scheduleFlush();
46
+ }
47
+ } catch {
48
+ // Intentional: logging must never throw
49
+ }
50
+ }
51
+
52
+ export function sessionLog(sessionId: string, message: string, data?: unknown): void {
53
+ log(`[magic-context][${sessionId}] ${message}`, data);
54
+ }
55
+
56
+ export function getLogFilePath(): string {
57
+ return logFile;
58
+ }
59
+
60
+ // Flush remaining buffer on process exit
61
+ if (!isTestEnv) {
62
+ process.on("exit", flush);
63
+ }
@@ -0,0 +1,84 @@
1
+ import { DREAMER_AGENT } from "../agents/dreamer";
2
+ import { HISTORIAN_AGENT } from "../agents/historian";
3
+ import { SIDEKICK_AGENT } from "../agents/sidekick";
4
+
5
+ /**
6
+ * Provider-agnostic fallback chain entry.
7
+ * Each entry specifies a model and the providers to try in priority order.
8
+ * Follows oh-my-opencode's FallbackEntry pattern — `opencode` acts as a
9
+ * catch-all proxy provider and is listed last in most entries.
10
+ */
11
+ export type FallbackEntry = {
12
+ providers: string[];
13
+ model: string;
14
+ variant?: string;
15
+ };
16
+
17
+ export type AgentModelRequirement = {
18
+ fallbackChain: FallbackEntry[];
19
+ };
20
+
21
+ // Historian: quality matters, single long prompt.
22
+ // Copilot first (request-based pricing, ideal for single-prompt background work).
23
+ const HISTORIAN_FALLBACK_CHAIN: FallbackEntry[] = [
24
+ { providers: ["github-copilot", "anthropic", "opencode"], model: "claude-sonnet-4-6" },
25
+ { providers: ["opencode-go"], model: "minimax-m2.7" },
26
+ {
27
+ providers: ["zai-coding-plan", "bailian-coding-plan", "opencode-go", "opencode"],
28
+ model: "glm-5",
29
+ },
30
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4" },
31
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3.1-pro" },
32
+ ];
33
+
34
+ // Dreamer: runs overnight during idle time, can be slow.
35
+ // Copilot first (request-based pricing). Local models also work well here.
36
+ const DREAMER_FALLBACK_CHAIN: FallbackEntry[] = [
37
+ { providers: ["github-copilot", "anthropic", "opencode"], model: "claude-sonnet-4-6" },
38
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
39
+ {
40
+ providers: ["zai-coding-plan", "bailian-coding-plan", "opencode-go", "opencode"],
41
+ model: "glm-5",
42
+ },
43
+ { providers: ["opencode-go"], model: "minimax-m2.7" },
44
+ { providers: ["github-copilot", "openai", "opencode"], model: "gpt-5.4-mini" },
45
+ ];
46
+
47
+ // Sidekick: speed is critical — fast inference providers first.
48
+ // No Copilot preference (low token count, request-based pricing doesn't help).
49
+ const SIDEKICK_FALLBACK_CHAIN: FallbackEntry[] = [
50
+ { providers: ["cerebras"], model: "qwen-3-235b-a22b-instruct-2507" },
51
+ { providers: ["google", "github-copilot", "opencode"], model: "gemini-3-flash" },
52
+ { providers: ["openai", "github-copilot", "opencode"], model: "gpt-5.4-mini" },
53
+ { providers: ["opencode"], model: "gpt-5-nano" },
54
+ ];
55
+
56
+ export const AGENT_MODEL_REQUIREMENTS: Record<string, AgentModelRequirement> = {
57
+ [HISTORIAN_AGENT]: { fallbackChain: HISTORIAN_FALLBACK_CHAIN },
58
+ [DREAMER_AGENT]: { fallbackChain: DREAMER_FALLBACK_CHAIN },
59
+ [SIDEKICK_AGENT]: { fallbackChain: SIDEKICK_FALLBACK_CHAIN },
60
+ };
61
+
62
+ /**
63
+ * Expand a provider-agnostic fallback chain into a flat `provider/model` list
64
+ * that OpenCode's agent config accepts as `fallback_models`.
65
+ */
66
+ export function expandFallbackChain(chain: FallbackEntry[]): string[] {
67
+ const models: string[] = [];
68
+ for (const entry of chain) {
69
+ for (const provider of entry.providers) {
70
+ models.push(`${provider}/${entry.model}`);
71
+ }
72
+ }
73
+ return models;
74
+ }
75
+
76
+ /**
77
+ * Get the expanded fallback_models list for an agent.
78
+ * Returns undefined if no requirement is defined.
79
+ */
80
+ export function getAgentFallbackModels(agent: string): string[] | undefined {
81
+ const requirement = AGENT_MODEL_REQUIREMENTS[agent];
82
+ if (!requirement) return undefined;
83
+ return expandFallbackChain(requirement.fallbackChain);
84
+ }
@@ -0,0 +1,160 @@
1
+ import type { createOpencodeClient } from "@opencode-ai/sdk";
2
+
3
+ import { log } from "./logger";
4
+
5
+ type Client = ReturnType<typeof createOpencodeClient>;
6
+
7
+ type PromptBody = {
8
+ model?: { providerID: string; modelID: string };
9
+ [key: string]: unknown;
10
+ };
11
+
12
+ type PromptArgs = {
13
+ path: { id: string };
14
+ body: PromptBody;
15
+ signal?: AbortSignal;
16
+ [key: string]: unknown;
17
+ };
18
+
19
+ export interface PromptRetryOptions {
20
+ timeoutMs?: number;
21
+ /** External abort signal — cancels the in-flight LLM prompt immediately when aborted */
22
+ signal?: AbortSignal;
23
+ }
24
+
25
+ export interface ModelSuggestionInfo {
26
+ providerID: string;
27
+ modelID: string;
28
+ suggestion: string;
29
+ }
30
+
31
+ function extractMessage(error: unknown): string {
32
+ if (typeof error === "string") return error;
33
+ if (error instanceof Error) return error.message;
34
+ if (typeof error === "object" && error !== null) {
35
+ const obj = error as Record<string, unknown>;
36
+ if (typeof obj.message === "string") return obj.message;
37
+ }
38
+
39
+ try {
40
+ return JSON.stringify(error);
41
+ } catch (_error) {
42
+ return String(error);
43
+ }
44
+ }
45
+
46
+ export function parseModelSuggestion(error: unknown): ModelSuggestionInfo | null {
47
+ if (!error) return null;
48
+
49
+ if (typeof error === "object" && error !== null) {
50
+ const errObj = error as Record<string, unknown>;
51
+
52
+ if (
53
+ errObj.name === "ProviderModelNotFoundError" &&
54
+ typeof errObj.data === "object" &&
55
+ errObj.data !== null
56
+ ) {
57
+ const data = errObj.data as Record<string, unknown>;
58
+ const suggestions = data.suggestions;
59
+ if (Array.isArray(suggestions) && typeof suggestions[0] === "string") {
60
+ return {
61
+ providerID: String(data.providerID ?? ""),
62
+ modelID: String(data.modelID ?? ""),
63
+ suggestion: suggestions[0],
64
+ };
65
+ }
66
+ }
67
+
68
+ for (const key of ["data", "error", "cause"] as const) {
69
+ const nested = errObj[key];
70
+ if (nested && typeof nested === "object") {
71
+ const result = parseModelSuggestion(nested);
72
+ if (result) return result;
73
+ }
74
+ }
75
+ }
76
+
77
+ const message = extractMessage(error);
78
+ const modelMatch = message.match(/model not found:\s*([^/\s]+)\s*\/\s*([^.,\s]+)/i);
79
+ const suggestionMatch = message.match(/did you mean:\s*([^,?]+)/i);
80
+
81
+ if (!modelMatch || !suggestionMatch) {
82
+ return null;
83
+ }
84
+
85
+ return {
86
+ providerID: modelMatch[1].trim(),
87
+ modelID: modelMatch[2].trim(),
88
+ suggestion: suggestionMatch[1].trim(),
89
+ };
90
+ }
91
+
92
+ async function promptWithTimeout(
93
+ client: Client,
94
+ args: PromptArgs,
95
+ timeoutMs: number,
96
+ signal?: AbortSignal,
97
+ ): Promise<void> {
98
+ const controller = new AbortController();
99
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
100
+
101
+ // Link external signal to internal controller so external abort cancels the fetch
102
+ const onExternalAbort = () => controller.abort();
103
+ signal?.addEventListener("abort", onExternalAbort);
104
+
105
+ try {
106
+ await client.session.prompt({
107
+ ...args,
108
+ signal: controller.signal,
109
+ } as Parameters<typeof client.session.prompt>[0]);
110
+ } catch (error) {
111
+ if (signal?.aborted) {
112
+ throw new Error("prompt aborted by external signal");
113
+ }
114
+ if (controller.signal.aborted) {
115
+ throw new Error(`prompt timed out after ${timeoutMs}ms`);
116
+ }
117
+ throw error;
118
+ } finally {
119
+ clearTimeout(timeout);
120
+ signal?.removeEventListener("abort", onExternalAbort);
121
+ }
122
+ }
123
+
124
+ export async function promptSyncWithModelSuggestionRetry(
125
+ client: Client,
126
+ args: PromptArgs,
127
+ options: PromptRetryOptions = {},
128
+ ): Promise<void> {
129
+ const timeoutMs = options.timeoutMs ?? 300_000;
130
+
131
+ try {
132
+ await promptWithTimeout(client, args, timeoutMs, options.signal);
133
+ } catch (error) {
134
+ const suggestion = parseModelSuggestion(error);
135
+ if (!suggestion || !args.body.model) {
136
+ throw error;
137
+ }
138
+
139
+ log("[model-suggestion-retry] Model not found, retrying with suggestion", {
140
+ original: `${suggestion.providerID}/${suggestion.modelID}`,
141
+ suggested: suggestion.suggestion,
142
+ });
143
+
144
+ await promptWithTimeout(
145
+ client,
146
+ {
147
+ ...args,
148
+ body: {
149
+ ...args.body,
150
+ model: {
151
+ providerID: suggestion.providerID,
152
+ modelID: suggestion.suggestion,
153
+ },
154
+ },
155
+ },
156
+ timeoutMs,
157
+ options.signal,
158
+ );
159
+ }
160
+ }