mini-codex 0.1.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.
package/dist/loop.js ADDED
@@ -0,0 +1,221 @@
1
+ import crypto from "node:crypto";
2
+ import path from "node:path";
3
+ import { createProvider } from "./providers/index.js";
4
+ import { buildPromptInput } from "./prompt.js";
5
+ import { AgentRuntime } from "./runtime.js";
6
+ import { selectSkill } from "./skills.js";
7
+ import { SessionLogger } from "./terminal.js";
8
+ function repeatedSteps(session, window = 3) {
9
+ if (session.steps.length < window)
10
+ return false;
11
+ const tail = session.steps.slice(-window).map((step) => step.action.type === "tool"
12
+ ? `${step.action.toolCall.tool}:${JSON.stringify(step.action.toolCall.args)}`
13
+ : `clarify:${step.action.question}:${step.userResponse || ""}`);
14
+ return new Set(tail).size === 1;
15
+ }
16
+ function finalAnswer(session) {
17
+ const lastStep = session.steps.at(-1);
18
+ if (!lastStep)
19
+ return null;
20
+ if (lastStep.action.type === "clarify") {
21
+ return lastStep.userResponse ? null : `Need clarification: ${lastStep.action.question}`;
22
+ }
23
+ const answer = lastStep.action.toolCall.args.answer;
24
+ return typeof answer === "string" && answer.trim() ? answer.trim() : null;
25
+ }
26
+ function stepNameForAction(action) {
27
+ if (action.type === "clarify")
28
+ return "Clarify Request";
29
+ switch (action.toolCall.tool) {
30
+ case "list_files":
31
+ return "List Files";
32
+ case "read_file":
33
+ return "Read File";
34
+ case "search_files":
35
+ return "Search Files";
36
+ case "write_file":
37
+ return "Write File";
38
+ case "edit_file":
39
+ return "Edit File";
40
+ case "run_command":
41
+ return "Run Command";
42
+ case "write_memory":
43
+ return "Write Memory";
44
+ case "spawn_subagents":
45
+ return "Spawn Sub-Agents";
46
+ case "wait_subagents":
47
+ return "Wait For Sub-Agents";
48
+ case "finish":
49
+ return "Finish";
50
+ }
51
+ }
52
+ function timestampFileName(date = new Date()) {
53
+ const yyyy = String(date.getFullYear());
54
+ const mm = String(date.getMonth() + 1).padStart(2, "0");
55
+ const dd = String(date.getDate()).padStart(2, "0");
56
+ const hh = String(date.getHours()).padStart(2, "0");
57
+ const min = String(date.getMinutes()).padStart(2, "0");
58
+ const ss = String(date.getSeconds()).padStart(2, "0");
59
+ const ms = String(date.getMilliseconds()).padStart(3, "0");
60
+ return `${yyyy}-${mm}-${dd}_${hh}-${min}-${ss}-${ms}.log`;
61
+ }
62
+ function initialStepName(index) {
63
+ return index === 1 ? "Start" : "Continue";
64
+ }
65
+ export async function runLoop(task, repoPath, options = {}) {
66
+ const maxSteps = options.maxSteps ?? 12;
67
+ const depth = options.depth ?? 0;
68
+ const interactive = options.interactive ?? false;
69
+ const renderer = options.renderer ?? null;
70
+ const provider = options.provider ?? createProvider();
71
+ const sessionId = crypto.randomUUID();
72
+ const logger = new SessionLogger(path.join(repoPath, ".codex", "logs", timestampFileName()));
73
+ const skill = await selectSkill(task, repoPath);
74
+ const session = {
75
+ id: sessionId,
76
+ task: task.trim(),
77
+ repoPath,
78
+ maxSteps,
79
+ depth,
80
+ interactive,
81
+ turns: [{ role: "user", kind: "task", content: task.trim() }],
82
+ steps: [],
83
+ skillName: skill?.name,
84
+ skillContent: skill?.content,
85
+ subagents: [],
86
+ };
87
+ await logger.start(sessionId, task.trim(), repoPath);
88
+ const runtime = new AgentRuntime(session);
89
+ await runtime.refreshMemory(session.task);
90
+ try {
91
+ for (let i = 1; i <= maxSteps; i++) {
92
+ if (repeatedSteps(session)) {
93
+ session.steps.push({
94
+ index: i,
95
+ name: "Finish",
96
+ rationale: "Stopping because the same action repeated without progress.",
97
+ action: { type: "tool", toolCall: { tool: "finish", args: { answer: "Stopped because the same action repeated without progress." } } },
98
+ result: { ok: true, summary: "Finished due to repetition detection." },
99
+ });
100
+ break;
101
+ }
102
+ renderer?.step(i, initialStepName(i));
103
+ const prompt = buildPromptInput(session);
104
+ const model = provider.getModelName();
105
+ renderer?.llmStart(model, prompt);
106
+ const decision = await provider.next(session, prompt);
107
+ const stepName = stepNameForAction(decision.action);
108
+ await logger.llm(i, stepName, prompt, decision.rawResponse || JSON.stringify(decision), decision.model || model);
109
+ renderer?.llmAction(decision.action, stepName);
110
+ if (decision.action.type === "clarify") {
111
+ session.turns.push({ role: "assistant", kind: "clarification_question", content: decision.action.question });
112
+ let userResponse;
113
+ if (interactive && options.askUser) {
114
+ renderer?.clarifyPrompt(decision.action.question);
115
+ userResponse = await options.askUser(decision.action.question);
116
+ renderer?.clarifyAnswer(userResponse);
117
+ session.turns.push({ role: "user", kind: "clarification_answer", content: userResponse });
118
+ }
119
+ else {
120
+ renderer?.warning(`Clarification requested but interactive input is unavailable: ${decision.action.question}`);
121
+ }
122
+ session.steps.push({
123
+ index: i,
124
+ name: stepName,
125
+ rationale: decision.rationale,
126
+ model: decision.model || model,
127
+ action: decision.action,
128
+ userResponse,
129
+ });
130
+ await logger.clarify(decision.action.question, userResponse);
131
+ if (!userResponse)
132
+ break;
133
+ continue;
134
+ }
135
+ renderer?.toolStart(decision.action.toolCall);
136
+ const result = await runtime.execute(decision.action.toolCall);
137
+ renderer?.toolResult(result);
138
+ await logger.tool(decision.action.toolCall);
139
+ session.steps.push({
140
+ index: i,
141
+ name: stepName,
142
+ rationale: decision.rationale,
143
+ model: decision.model || model,
144
+ action: decision.action,
145
+ result,
146
+ });
147
+ if (decision.action.toolCall.tool === "write_memory") {
148
+ await runtime.refreshMemory(session.task);
149
+ }
150
+ if (decision.action.toolCall.tool === "finish")
151
+ break;
152
+ }
153
+ }
154
+ finally {
155
+ await runtime.cleanup();
156
+ }
157
+ const answer = finalAnswer(session);
158
+ if (answer) {
159
+ await logger.final(answer, session.steps.length);
160
+ }
161
+ return session;
162
+ }
163
+ export function renderSession(session, options = {}) {
164
+ const showToolResults = options.showToolResults ?? true;
165
+ const lines = [];
166
+ const touchedFiles = new Set();
167
+ const commands = [];
168
+ const answer = finalAnswer(session);
169
+ for (const step of session.steps) {
170
+ if (step.action.type !== "tool")
171
+ continue;
172
+ const p = step.action.toolCall.args.path;
173
+ if (typeof p === "string" && (step.action.toolCall.tool === "write_file" || step.action.toolCall.tool === "edit_file")) {
174
+ touchedFiles.add(p);
175
+ }
176
+ const c = step.action.toolCall.args.command;
177
+ if (typeof c === "string" && step.action.toolCall.tool === "run_command") {
178
+ commands.push(c);
179
+ }
180
+ }
181
+ lines.push("# mini-codex report");
182
+ lines.push(`Task: ${session.task}`);
183
+ lines.push(`Repo: ${session.repoPath}`);
184
+ lines.push(`Session: ${session.id}`);
185
+ lines.push(`Depth: ${session.depth}`);
186
+ lines.push(`Steps: ${session.steps.length}/${session.maxSteps}`);
187
+ lines.push(`Skill: ${session.skillName || "none"}`);
188
+ lines.push(`Touched files: ${touchedFiles.size ? [...touchedFiles].join(", ") : "none"}`);
189
+ lines.push(`Commands run: ${commands.length ? commands.join(" | ") : "none"}`);
190
+ lines.push(`Sub-agents: ${session.subagents.length ? session.subagents.map((agent) => `${agent.name}:${agent.status}`).join(", ") : "none"}`);
191
+ lines.push("");
192
+ if (answer) {
193
+ lines.push("## Answer");
194
+ lines.push(answer);
195
+ lines.push("");
196
+ }
197
+ for (const step of session.steps) {
198
+ lines.push(`## Step ${step.index}`);
199
+ if (step.name)
200
+ lines.push(`Name: ${step.name}`);
201
+ if (step.model)
202
+ lines.push(`Model: ${step.model}`);
203
+ lines.push(`Rationale: ${step.rationale}`);
204
+ if (step.action.type === "clarify") {
205
+ lines.push("Action: clarify");
206
+ lines.push(`Question: ${step.action.question}`);
207
+ lines.push(`User response: ${step.userResponse || "No response"}`);
208
+ }
209
+ else {
210
+ lines.push(`Tool: ${step.action.toolCall.tool}`);
211
+ lines.push(`Args: ${JSON.stringify(step.action.toolCall.args)}`);
212
+ lines.push(`Result: ${step.result?.summary || "No result"}`);
213
+ if (showToolResults && step.result?.output)
214
+ lines.push(step.result.output);
215
+ if (showToolResults && step.result?.error)
216
+ lines.push(`ERROR: ${step.result.error}`);
217
+ }
218
+ lines.push("");
219
+ }
220
+ return lines.join("\n");
221
+ }
@@ -0,0 +1,7 @@
1
+ import type { MemoryNote, ProjectMemory } from "./types.js";
2
+ export declare function loadProjectMemory(repoPath: string, task: string): Promise<ProjectMemory>;
3
+ export declare function writeProjectMemory(repoPath: string, input: {
4
+ category: string;
5
+ title: string;
6
+ content: string;
7
+ }): Promise<MemoryNote>;
package/dist/memory.js ADDED
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const MEMORY_ROOT = path.join(".codex", "memory");
4
+ const NOTES_DIR = path.join(MEMORY_ROOT, "notes");
5
+ const SUMMARY_FILE = path.join(MEMORY_ROOT, "project.md");
6
+ function tokenize(text) {
7
+ return [...new Set(text.toLowerCase().split(/[^a-z0-9_]+/).filter((token) => token.length >= 3))];
8
+ }
9
+ function slugify(value) {
10
+ return value
11
+ .toLowerCase()
12
+ .replace(/[^a-z0-9]+/g, "-")
13
+ .replace(/^-+|-+$/g, "")
14
+ .slice(0, 60);
15
+ }
16
+ function preview(content) {
17
+ const text = content.replace(/\s+/g, " ").trim();
18
+ return text.length > 180 ? `${text.slice(0, 177)}...` : text;
19
+ }
20
+ function noteScore(taskTokens, text) {
21
+ const haystack = text.toLowerCase();
22
+ return taskTokens.reduce((score, token) => score + (haystack.includes(token) ? 1 : 0), 0);
23
+ }
24
+ async function ensureMemoryDir(repoPath) {
25
+ await fs.mkdir(path.join(repoPath, NOTES_DIR), { recursive: true });
26
+ }
27
+ export async function loadProjectMemory(repoPath, task) {
28
+ const summaryPath = path.join(repoPath, SUMMARY_FILE);
29
+ const notesRoot = path.join(repoPath, NOTES_DIR);
30
+ const summary = await fs.readFile(summaryPath, "utf8").catch(() => null);
31
+ const taskTokens = tokenize(task);
32
+ const entries = await fs.readdir(notesRoot, { withFileTypes: true }).catch(() => []);
33
+ const scored = [];
34
+ for (const entry of entries) {
35
+ if (!entry.isFile() || !entry.name.endsWith(".md"))
36
+ continue;
37
+ const fullPath = path.join(notesRoot, entry.name);
38
+ const raw = await fs.readFile(fullPath, "utf8").catch(() => "");
39
+ const body = raw.replace(/^---[\s\S]*?---\n?/, "").trim();
40
+ const titleMatch = raw.match(/^title:\s*(.+)$/m);
41
+ const categoryMatch = raw.match(/^category:\s*(.+)$/m);
42
+ const title = titleMatch?.[1]?.trim() || entry.name.replace(/\.md$/, "");
43
+ const category = categoryMatch?.[1]?.trim() || "finding";
44
+ const note = {
45
+ slug: entry.name.replace(/\.md$/, ""),
46
+ title,
47
+ category,
48
+ path: path.relative(repoPath, fullPath),
49
+ preview: preview(body),
50
+ };
51
+ scored.push({ note, score: noteScore(taskTokens, `${title}\n${body}`) });
52
+ }
53
+ scored.sort((a, b) => b.score - a.score || a.note.title.localeCompare(b.note.title));
54
+ const relevantNotes = scored.filter((item) => item.score > 0).slice(0, 3).map((item) => item.note);
55
+ return { summary, relevantNotes };
56
+ }
57
+ export async function writeProjectMemory(repoPath, input) {
58
+ await ensureMemoryDir(repoPath);
59
+ const slugBase = slugify(input.title) || "memory-note";
60
+ const slug = `${new Date().toISOString().slice(0, 10)}-${slugBase}`;
61
+ const notePath = path.join(repoPath, NOTES_DIR, `${slug}.md`);
62
+ const now = new Date().toISOString();
63
+ const body = `---\ntitle: ${input.title}\ncategory: ${input.category}\ncreatedAt: ${now}\n---\n\n${input.content.trim()}\n`;
64
+ await fs.writeFile(notePath, body, "utf8");
65
+ const notes = await fs.readdir(path.join(repoPath, NOTES_DIR)).catch(() => []);
66
+ const bullets = [];
67
+ for (const name of notes.filter((name) => name.endsWith(".md")).sort()) {
68
+ const fullPath = path.join(repoPath, NOTES_DIR, name);
69
+ const raw = await fs.readFile(fullPath, "utf8").catch(() => "");
70
+ const title = raw.match(/^title:\s*(.+)$/m)?.[1]?.trim() || name.replace(/\.md$/, "");
71
+ const category = raw.match(/^category:\s*(.+)$/m)?.[1]?.trim() || "finding";
72
+ const bodyText = raw.replace(/^---[\s\S]*?---\n?/, "").trim();
73
+ bullets.push(`- [${category}] ${title}: ${preview(bodyText)}`);
74
+ }
75
+ const summary = ["# Project Memory", "", ...bullets].join("\n");
76
+ await fs.writeFile(path.join(repoPath, SUMMARY_FILE), `${summary}\n`, "utf8");
77
+ return {
78
+ slug,
79
+ title: input.title,
80
+ category: input.category,
81
+ path: path.relative(repoPath, notePath),
82
+ preview: preview(input.content),
83
+ };
84
+ }
@@ -0,0 +1,5 @@
1
+ import type { ModelDecision, Session } from "./types.js";
2
+ export declare class Model {
3
+ private client;
4
+ next(session: Session): Promise<ModelDecision>;
5
+ }
package/dist/model.js ADDED
@@ -0,0 +1,69 @@
1
+ import OpenAI from "openai";
2
+ import { z } from "zod";
3
+ const MODEL = process.env.MINI_CODEX_MODEL || "gpt-5";
4
+ const tools = [
5
+ "list_files",
6
+ "read_file",
7
+ "search_files",
8
+ "write_file",
9
+ "edit_file",
10
+ "run_command",
11
+ "finish",
12
+ ];
13
+ const schema = z.object({
14
+ rationale: z.string(),
15
+ toolCall: z.object({
16
+ tool: z.enum(tools),
17
+ args: z.record(z.unknown()),
18
+ }),
19
+ });
20
+ const SYSTEM_PROMPT = `You are a useful mini coding agent.
21
+ Work inside one repo only.
22
+ Choose exactly one tool call at a time.
23
+ Prefer list_files/search_files/read_file before write_file/edit_file.
24
+ Prefer edit_file for targeted changes and write_file for full file creation/replacement.
25
+ Use run_command for validation when useful.
26
+ Use finish when done, blocked, or when user input is needed.
27
+ Return only JSON.`;
28
+ export class Model {
29
+ client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
30
+ async next(session) {
31
+ const history = session.steps
32
+ .map((step) => `Step ${step.index}\nRationale: ${step.rationale}\nTool: ${step.toolCall.tool}\nArgs: ${JSON.stringify(step.toolCall.args)}\nResult: ${step.result.summary}\nOutput: ${step.result.output || step.result.error || ""}`)
33
+ .join("\n\n");
34
+ const response = await this.client.responses.create({
35
+ model: MODEL,
36
+ input: [
37
+ { role: "system", content: SYSTEM_PROMPT },
38
+ {
39
+ role: "user",
40
+ content: `Task: ${session.task}\nRepo: ${session.repoPath}\nPast steps:\n${history || "None yet."}`,
41
+ },
42
+ ],
43
+ text: {
44
+ format: {
45
+ type: "json_schema",
46
+ name: "decision",
47
+ schema: {
48
+ type: "object",
49
+ additionalProperties: false,
50
+ properties: {
51
+ rationale: { type: "string" },
52
+ toolCall: {
53
+ type: "object",
54
+ additionalProperties: false,
55
+ properties: {
56
+ tool: { type: "string", enum: [...tools] },
57
+ args: { type: "object", additionalProperties: true },
58
+ },
59
+ required: ["tool", "args"],
60
+ },
61
+ },
62
+ required: ["rationale", "toolCall"],
63
+ },
64
+ },
65
+ },
66
+ });
67
+ return schema.parse(JSON.parse(response.output_text));
68
+ }
69
+ }
@@ -0,0 +1,3 @@
1
+ import type { Session } from "./types.js";
2
+ export declare function buildPromptInput(session: Session): string;
3
+ export declare function providerInstructions(depth: number): string;
package/dist/prompt.js ADDED
@@ -0,0 +1,91 @@
1
+ function formatStep(step) {
2
+ if (step.action.type === "clarify") {
3
+ return `Step ${step.index}\nRationale: ${step.rationale}\nClarification question: ${step.action.question}\nUser response: ${step.userResponse || "No response"}`;
4
+ }
5
+ const output = step.result?.output || step.result?.error || "";
6
+ return `Step ${step.index}\nRationale: ${step.rationale}\nTool: ${step.action.toolCall.tool}\nArgs: ${JSON.stringify(step.action.toolCall.args)}\nResult: ${step.result?.summary || "No result"}\nOutput: ${output}`;
7
+ }
8
+ function compactOlderSteps(session, keep = 4) {
9
+ if (session.steps.length <= keep)
10
+ return "None.";
11
+ return session.steps
12
+ .slice(0, -keep)
13
+ .map((step) => step.action.type === "clarify"
14
+ ? `Step ${step.index}: clarify -> ${step.action.question}${step.userResponse ? ` / ${step.userResponse}` : ""}`
15
+ : `Step ${step.index}: ${step.action.toolCall.tool} -> ${step.result?.summary || "No result"}`)
16
+ .join("\n");
17
+ }
18
+ function recentSteps(session, keep = 4) {
19
+ const recent = session.steps.slice(-keep);
20
+ return recent.length ? recent.map(formatStep).join("\n\n") : "None yet.";
21
+ }
22
+ function memoryBlock(session) {
23
+ const parts = [];
24
+ if (session.memorySummary?.trim()) {
25
+ parts.push(`Project memory summary:\n${session.memorySummary.trim()}`);
26
+ }
27
+ if (session.memoryNotes?.length) {
28
+ parts.push(`Relevant memory notes:\n${session.memoryNotes
29
+ .map((note) => `- [${note.category}] ${note.title} (${note.path}): ${note.preview}`)
30
+ .join("\n")}`);
31
+ }
32
+ return parts.length ? parts.join("\n\n") : "No relevant project memory.";
33
+ }
34
+ function conversationBlock(session) {
35
+ if (!session.turns.length)
36
+ return "No conversation history.";
37
+ const recent = session.turns.slice(-6);
38
+ return recent.map((turn) => `${turn.role} [${turn.kind}]: ${turn.content}`).join("\n");
39
+ }
40
+ function subagentBlock(session) {
41
+ if (!session.subagents.length)
42
+ return "No child agents.";
43
+ return session.subagents
44
+ .map((agent) => {
45
+ const extra = agent.status === "completed" ? agent.summary || agent.answer || "completed" : agent.error || agent.status;
46
+ return `- ${agent.name} [${agent.status}] task="${agent.task}" worktree=${agent.worktreePath}${extra ? ` summary=${extra}` : ""}`;
47
+ })
48
+ .join("\n");
49
+ }
50
+ export function buildPromptInput(session) {
51
+ const sections = [
52
+ `Task: ${session.task}`,
53
+ `Repo: ${session.repoPath}`,
54
+ `Agent depth: ${session.depth}`,
55
+ `Interactive clarifications: ${session.interactive ? "allowed" : "disabled"}`,
56
+ `Selected skill: ${session.skillName || "none"}`,
57
+ session.skillContent ? `Skill content:\n${session.skillContent}` : "Skill content: none",
58
+ `Conversation:\n${conversationBlock(session)}`,
59
+ memoryBlock(session),
60
+ `Child agents:\n${subagentBlock(session)}`,
61
+ `Older step summary:\n${compactOlderSteps(session)}`,
62
+ `Recent steps:\n${recentSteps(session)}`,
63
+ ];
64
+ return sections.join("\n\n");
65
+ }
66
+ export function providerInstructions(depth) {
67
+ const depthNote = depth > 0
68
+ ? "You are a child agent. Do not spawn more sub-agents and do not write project memory. Focus on your assigned subtask and finish with a concise answer."
69
+ : "You may use sub-agents for independent subtasks when parallel work would clearly help. Only write project memory for durable, high-confidence repo knowledge.";
70
+ return [
71
+ "You are a useful mini coding agent for one local repository.",
72
+ "Classify each task before acting: question-answering, investigation/debugging, code-change, or ambiguous.",
73
+ "If ambiguity materially blocks progress and interactive clarifications are allowed, you should usually ask a short clarification question using the clarify action before guessing.",
74
+ "For greetings or underspecified requests like hello, hi, help, or fix this, prefer a brief clarification question when interactive clarifications are allowed.",
75
+ "Do not ask clarification questions for facts that can be discovered from the repository itself.",
76
+ "For question-answering and investigation tasks, prefer read-only evidence gathering and provide a direct answer in finish.args.answer.",
77
+ "Prefer list_files and read_file for known files.",
78
+ "For targeted codebase search, prefer run_command with rg when available.",
79
+ 'Good search patterns include: rg -n "login" src and rg "auth" .',
80
+ "Use search_files as the cross-platform fallback when rg is unavailable or CLI search is not needed.",
81
+ "Keep command usage repo-local and purposeful. Good command categories are search (rg), inspection (git status, git diff --stat, ls, pwd), and validation (build/test/typecheck commands).",
82
+ "Avoid arbitrary shell usage when file reads or search already answer the question.",
83
+ "Only modify files when the user clearly asks for a concrete repo change.",
84
+ depthNote,
85
+ "When using sub-agents, give each one a focused independent task and wait for their summaries before finalizing.",
86
+ "If interactive clarifications are disabled, do not use clarify; finish with a brief answer or clarification request instead.",
87
+ "If the request is ambiguous, underspecified, or not clearly a coding task, clarify or finish with a brief answer instead of guessing.",
88
+ "Use finish when done or blocked.",
89
+ "Return only JSON.",
90
+ ].join(" ");
91
+ }
@@ -0,0 +1,5 @@
1
+ import type { Session, ModelDecision } from "../types.js";
2
+ export interface ModelProvider {
3
+ getModelName(): string;
4
+ next(session: Session, prompt: string): Promise<ModelDecision>;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ModelProvider } from "./base.js";
2
+ export declare function createProvider(): ModelProvider;
@@ -0,0 +1,13 @@
1
+ import { OpenAIOAuthProvider } from "./openai.js";
2
+ import { TestFixtureProvider } from "./test-fixture.js";
3
+ export function createProvider() {
4
+ const provider = process.env.MINI_CODEX_PROVIDER || "openai-oauth";
5
+ switch (provider) {
6
+ case "openai-oauth":
7
+ return new OpenAIOAuthProvider();
8
+ case "test-fixture":
9
+ return new TestFixtureProvider();
10
+ default:
11
+ throw new Error(`Unsupported provider: ${provider}`);
12
+ }
13
+ }
@@ -0,0 +1,9 @@
1
+ import type { ModelDecision, Session } from "../types.js";
2
+ import type { ModelProvider } from "./base.js";
3
+ export declare function extractJsonObjects(text: string): string[];
4
+ export declare function parseDecision(raw: string): ModelDecision;
5
+ export declare class OpenAIOAuthProvider implements ModelProvider {
6
+ private auth;
7
+ getModelName(): string;
8
+ next(session: Session, prompt: string): Promise<ModelDecision>;
9
+ }