@gajae-code/coding-agent 0.2.1 → 0.2.3
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/CHANGELOG.md +59 -1
- package/dist/types/cli/setup-cli.d.ts +1 -0
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/deep-interview.d.ts +41 -0
- package/dist/types/commands/session.d.ts +24 -0
- package/dist/types/commands/setup.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +2 -2
- package/dist/types/config/models-config-schema.d.ts +17 -9
- package/dist/types/config/settings-schema.d.ts +37 -24
- package/dist/types/discovery/helpers.d.ts +2 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +33 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +12 -11
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +25 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +13 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +37 -5
- package/dist/types/gjc-runtime/tmux-common.d.ts +41 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +17 -0
- package/dist/types/goals/runtime.d.ts +3 -9
- package/dist/types/goals/state.d.ts +3 -6
- package/dist/types/goals/tools/goal-tool.d.ts +1 -69
- package/dist/types/hooks/skill-state.d.ts +5 -0
- package/dist/types/memories/index.d.ts +1 -1
- package/dist/types/memory-backend/local-backend.d.ts +3 -3
- package/dist/types/modes/components/hook-selector.d.ts +7 -0
- package/dist/types/modes/components/settings-selector.d.ts +0 -2
- package/dist/types/modes/components/status-line/types.d.ts +0 -3
- package/dist/types/modes/components/status-line.d.ts +0 -3
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -12
- package/dist/types/modes/theme/defaults/index.d.ts +0 -2
- package/dist/types/modes/theme/theme.d.ts +1 -2
- package/dist/types/modes/types.d.ts +1 -7
- package/dist/types/modes/utils/context-usage.d.ts +6 -2
- package/dist/types/sdk.d.ts +6 -2
- package/dist/types/session/agent-session.d.ts +47 -1
- package/dist/types/session/contribution-prep.d.ts +47 -0
- package/dist/types/session/session-manager.d.ts +3 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +1 -0
- package/dist/types/setup/provider-onboarding.d.ts +29 -5
- package/dist/types/skill-state/active-state.d.ts +30 -1
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +6 -1
- package/dist/types/skill-state/initial-phase.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +9 -4
- package/dist/types/skill-state/workflow-state-contract.d.ts +34 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/types.d.ts +11 -0
- package/dist/types/tools/index.d.ts +20 -1
- package/dist/types/tools/skill.d.ts +47 -0
- package/dist/types/utils/changelog.d.ts +18 -2
- package/package.json +7 -7
- package/src/cli/args.ts +3 -2
- package/src/cli/setup-cli.ts +26 -12
- package/src/cli.ts +7 -1
- package/src/commands/contribution-prep.ts +41 -0
- package/src/commands/deep-interview.ts +30 -23
- package/src/commands/launch.ts +10 -1
- package/src/commands/ralplan.ts +10 -22
- package/src/commands/session.ts +150 -0
- package/src/commands/setup.ts +2 -0
- package/src/commands/state.ts +15 -4
- package/src/commands/team.ts +23 -3
- package/src/config/model-registry.ts +10 -2
- package/src/config/models-config-schema.ts +120 -102
- package/src/config/settings-schema.ts +42 -25
- package/src/config.ts +1 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +32 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +22 -2
- package/src/defaults/gjc/skills/team/SKILL.md +39 -7
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +33 -25
- package/src/discovery/helpers.ts +24 -1
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +546 -0
- package/src/gjc-runtime/goal-mode-request.ts +2 -19
- package/src/gjc-runtime/launch-tmux.ts +83 -43
- package/src/gjc-runtime/ralplan-runtime.ts +460 -0
- package/src/gjc-runtime/state-runtime.ts +731 -0
- package/src/gjc-runtime/team-runtime.ts +708 -52
- package/src/gjc-runtime/tmux-common.ts +119 -0
- package/src/gjc-runtime/tmux-sessions.ts +165 -0
- package/src/gjc-runtime/ultragoal-guard.ts +6 -3
- package/src/gjc-runtime/ultragoal-runtime.ts +5 -4
- package/src/goals/runtime.ts +38 -144
- package/src/goals/state.ts +36 -7
- package/src/goals/tools/goal-tool.ts +15 -172
- package/src/hooks/skill-state.ts +39 -18
- package/src/internal-urls/docs-index.generated.ts +5 -4
- package/src/internal-urls/memory-protocol.ts +3 -2
- package/src/main.ts +2 -3
- package/src/memories/index.ts +2 -1
- package/src/memory-backend/local-backend.ts +14 -6
- package/src/modes/components/hook-selector.ts +156 -1
- package/src/modes/components/settings-selector.ts +5 -12
- package/src/modes/components/skill-hud/render.ts +4 -0
- package/src/modes/components/status-line/segments.ts +5 -16
- package/src/modes/components/status-line/types.ts +0 -3
- package/src/modes/components/status-line.ts +0 -6
- package/src/modes/controllers/command-controller.ts +27 -4
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +0 -15
- package/src/modes/controllers/selector-controller.ts +4 -11
- package/src/modes/interactive-mode.ts +18 -219
- package/src/modes/theme/defaults/dark-poimandres.json +0 -1
- package/src/modes/theme/defaults/light-poimandres.json +0 -1
- package/src/modes/theme/theme.ts +0 -6
- package/src/modes/types.ts +1 -7
- package/src/modes/utils/context-usage.ts +66 -17
- package/src/prompts/agents/architect.md +3 -0
- package/src/prompts/agents/executor.md +2 -0
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/prompts/goals/goal-continuation.md +1 -4
- package/src/prompts/goals/goal-mode-active.md +3 -5
- package/src/prompts/system/subagent-system-prompt.md +6 -0
- package/src/prompts/system/system-prompt.md +5 -7
- package/src/prompts/tools/goal.md +4 -4
- package/src/prompts/tools/skill.md +28 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/sdk.ts +51 -11
- package/src/session/agent-session.ts +222 -21
- package/src/session/contribution-prep.ts +320 -0
- package/src/session/session-manager.ts +9 -1
- package/src/setup/model-onboarding-guidance.ts +6 -3
- package/src/setup/provider-onboarding.ts +177 -16
- package/src/skill-state/active-state.ts +188 -25
- package/src/skill-state/deep-interview-mutation-guard.ts +72 -21
- package/src/skill-state/initial-phase.ts +17 -0
- package/src/skill-state/workflow-hud.ts +23 -5
- package/src/skill-state/workflow-state-contract.ts +121 -0
- package/src/slash-commands/builtin-registry.ts +75 -25
- package/src/slash-commands/helpers/context-report.ts +123 -13
- package/src/task/agents.ts +1 -0
- package/src/task/commands.ts +1 -5
- package/src/task/executor.ts +9 -1
- package/src/task/index.ts +91 -4
- package/src/task/types.ts +6 -0
- package/src/tools/ask.ts +2 -0
- package/src/tools/gh.ts +212 -2
- package/src/tools/index.ts +25 -6
- package/src/tools/skill.ts +153 -0
- package/src/utils/changelog.ts +67 -44
- package/dist/types/commands/gjc-runtime-bridge.d.ts +0 -30
- package/dist/types/commands/question.d.ts +0 -7
- package/dist/types/modes/loop-limit.d.ts +0 -22
- package/src/commands/gjc-runtime-bridge.ts +0 -227
- package/src/commands/question.ts +0 -12
- package/src/modes/loop-limit.ts +0 -140
- package/src/prompts/commands/orchestrate.md +0 -49
- package/src/prompts/goals/goal-budget-limit.md +0 -16
- package/src/prompts/tools/create-goal.md +0 -3
- package/src/prompts/tools/get-goal.md +0 -3
- package/src/prompts/tools/update-goal.md +0 -3
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import type { AgentMessage } from "@gajae-code/agent-core";
|
|
5
|
+
import type { AssistantMessage, ToolResultMessage, UserMessage } from "@gajae-code/ai";
|
|
6
|
+
import { $ } from "bun";
|
|
7
|
+
import { resolveGjcCommand } from "../task/gjc-command";
|
|
8
|
+
import { shortenPath } from "../tools/render-utils";
|
|
9
|
+
|
|
10
|
+
export const CONTRIBUTION_PREP_SCHEMA_VERSION = 1;
|
|
11
|
+
|
|
12
|
+
const MAX_TRANSCRIPT_MESSAGES = 20;
|
|
13
|
+
const MAX_TEXT_CHARS = 12000;
|
|
14
|
+
const MAX_GIT_OUTPUT_CHARS = 60000;
|
|
15
|
+
|
|
16
|
+
export interface ContributionPrepArtifact {
|
|
17
|
+
path: string;
|
|
18
|
+
description: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ContributionPrepManifest {
|
|
22
|
+
schema_version: number;
|
|
23
|
+
source_session_id: string;
|
|
24
|
+
created_at: string;
|
|
25
|
+
cwd: string;
|
|
26
|
+
git_head: string | null;
|
|
27
|
+
changed_files: string[];
|
|
28
|
+
artifacts: ContributionPrepArtifact[];
|
|
29
|
+
redactions: string[];
|
|
30
|
+
recommended_output: string[];
|
|
31
|
+
worker_prompt_path: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ContributionPrepResult {
|
|
35
|
+
manifestPath: string;
|
|
36
|
+
workerPromptPath: string;
|
|
37
|
+
artifactDir: string;
|
|
38
|
+
changedFiles: string[];
|
|
39
|
+
spawned: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ContributionPrepOptions {
|
|
43
|
+
customInstructions?: string;
|
|
44
|
+
spawnWorker?: boolean;
|
|
45
|
+
artifactRoot?: string;
|
|
46
|
+
now?: Date;
|
|
47
|
+
spawn?: (args: string[], cwd: string, shell: boolean) => Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ContributionPrepContext {
|
|
51
|
+
sessionId: string;
|
|
52
|
+
cwd: string;
|
|
53
|
+
sessionFile?: string;
|
|
54
|
+
messages: AgentMessage[];
|
|
55
|
+
customInstructions?: string;
|
|
56
|
+
now?: Date;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface RedactionState {
|
|
60
|
+
labels: Set<string>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function limitText(text: string, maxChars = MAX_TEXT_CHARS): string {
|
|
64
|
+
if (text.length <= maxChars) return text;
|
|
65
|
+
return `${text.slice(0, maxChars)}\n\n[truncated ${text.length - maxChars} chars]`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function replaceRegex(text: string, regex: RegExp, replacement: string, state: RedactionState, label: string): string {
|
|
69
|
+
if (!regex.test(text)) return text;
|
|
70
|
+
state.labels.add(label);
|
|
71
|
+
regex.lastIndex = 0;
|
|
72
|
+
return text.replace(regex, replacement);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function redactContributionPrepText(
|
|
76
|
+
text: string,
|
|
77
|
+
cwd: string,
|
|
78
|
+
state: RedactionState = { labels: new Set() },
|
|
79
|
+
): string {
|
|
80
|
+
let redacted = text;
|
|
81
|
+
redacted = replaceRegex(
|
|
82
|
+
redacted,
|
|
83
|
+
/\b(?:sk|pk|rk|xox[baprs])-[-_A-Za-z0-9]{12,}\b/g,
|
|
84
|
+
"[REDACTED_TOKEN]",
|
|
85
|
+
state,
|
|
86
|
+
"tokens",
|
|
87
|
+
);
|
|
88
|
+
redacted = replaceRegex(
|
|
89
|
+
redacted,
|
|
90
|
+
/\b(?:ghp_[A-Za-z0-9_]{12,}|gho_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,})\b/g,
|
|
91
|
+
"[REDACTED_TOKEN]",
|
|
92
|
+
state,
|
|
93
|
+
"tokens",
|
|
94
|
+
);
|
|
95
|
+
redacted = replaceRegex(
|
|
96
|
+
redacted,
|
|
97
|
+
/\b((?:ANTHROPIC|OPENAI|GITHUB|GOOGLE|GEMINI|KAGI|TAVILY|EXA|PERPLEXITY|ZAI|KIMI|BRAVE|SEARXNG|AWS)_[A-Z0-9_]*(?:KEY|TOKEN|SECRET|COOKIE|PASSWORD))\s*=\s*[^\s\n]+/gi,
|
|
98
|
+
"$1=[REDACTED_SECRET]",
|
|
99
|
+
state,
|
|
100
|
+
"provider_keys",
|
|
101
|
+
);
|
|
102
|
+
redacted = replaceRegex(
|
|
103
|
+
redacted,
|
|
104
|
+
/\b(Authorization|Proxy-Authorization)\s*:\s*(?:Bearer|Basic|Token)\s+[^\s\n]+/gi,
|
|
105
|
+
"$1: [REDACTED_AUTH_HEADER]",
|
|
106
|
+
state,
|
|
107
|
+
"auth_headers",
|
|
108
|
+
);
|
|
109
|
+
redacted = replaceRegex(redacted, /\b(Cookie|Set-Cookie)\s*:\s*[^\n]+/gi, "$1: [REDACTED_COOKIE]", state, "cookies");
|
|
110
|
+
redacted = replaceRegex(
|
|
111
|
+
redacted,
|
|
112
|
+
/https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})[^\s)>'"]*/gi,
|
|
113
|
+
"[REDACTED_PRIVATE_ENDPOINT]",
|
|
114
|
+
state,
|
|
115
|
+
"private_endpoints",
|
|
116
|
+
);
|
|
117
|
+
const home = os.homedir();
|
|
118
|
+
if (home && redacted.includes(home)) {
|
|
119
|
+
state.labels.add("home_paths");
|
|
120
|
+
redacted = redacted.split(home).join("~");
|
|
121
|
+
}
|
|
122
|
+
const normalizedCwd = path.resolve(cwd);
|
|
123
|
+
if (normalizedCwd && redacted.includes(normalizedCwd)) {
|
|
124
|
+
state.labels.add("cwd_paths");
|
|
125
|
+
redacted = redacted.split(normalizedCwd).join(shortenPath(normalizedCwd));
|
|
126
|
+
}
|
|
127
|
+
return redacted;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function contentText(content: UserMessage["content"] | AssistantMessage["content"]): string {
|
|
131
|
+
if (typeof content === "string") return content;
|
|
132
|
+
return content
|
|
133
|
+
.map(part => {
|
|
134
|
+
if (part.type === "text") return part.text;
|
|
135
|
+
if (part.type === "toolCall") return `[tool call: ${part.name}] ${JSON.stringify(part.arguments)}`;
|
|
136
|
+
if (part.type === "image") return "[image]";
|
|
137
|
+
return `[${part.type}]`;
|
|
138
|
+
})
|
|
139
|
+
.join("\n");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatMessage(message: AgentMessage): string {
|
|
143
|
+
if (message.role === "user" || message.role === "assistant") {
|
|
144
|
+
return `## ${message.role}\n\n${contentText(message.content)}\n`;
|
|
145
|
+
}
|
|
146
|
+
if (message.role === "toolResult") {
|
|
147
|
+
const tool = message as ToolResultMessage;
|
|
148
|
+
return `## toolResult: ${tool.toolName}\n\n${typeof tool.content === "string" ? tool.content : JSON.stringify(tool.content)}\n`;
|
|
149
|
+
}
|
|
150
|
+
return `## ${message.role}\n\n${JSON.stringify(message)}\n`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function gitOutput(cwd: string, args: string[], maxChars = MAX_GIT_OUTPUT_CHARS): Promise<string> {
|
|
154
|
+
try {
|
|
155
|
+
const output = await $`git ${args}`.cwd(cwd).quiet().text();
|
|
156
|
+
return limitText(output.trim(), maxChars);
|
|
157
|
+
} catch {
|
|
158
|
+
return "";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function changedFiles(cwd: string): Promise<string[]> {
|
|
163
|
+
const output = await gitOutput(cwd, ["status", "--short"]);
|
|
164
|
+
return output
|
|
165
|
+
.split("\n")
|
|
166
|
+
.map(line => line.trim())
|
|
167
|
+
.filter(Boolean)
|
|
168
|
+
.map(line => line.replace(/^..\s+/, ""));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function writeArtifact(
|
|
172
|
+
dir: string,
|
|
173
|
+
name: string,
|
|
174
|
+
description: string,
|
|
175
|
+
text: string,
|
|
176
|
+
): Promise<ContributionPrepArtifact> {
|
|
177
|
+
const filePath = path.join(dir, name);
|
|
178
|
+
await Bun.write(filePath, `${text.trimEnd()}\n`);
|
|
179
|
+
return { path: filePath, description };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function buildContributionPrepWorkerPrompt(manifestPath: string): string {
|
|
183
|
+
return [
|
|
184
|
+
"Prepare a maintainer-friendly contribution draft from the redacted context dump.",
|
|
185
|
+
"Read the manifest and referenced artifact file pointers. Do not assume transcript context was inlined here.",
|
|
186
|
+
`Manifest: ${manifestPath}`,
|
|
187
|
+
"Produce structured markdown with: title, problem summary, reproduction/context, proposed fix or implementation plan, affected files, tests to run, and uncertainty/remaining risks.",
|
|
188
|
+
"Do not create GitHub issues, open PRs, push branches, or perform remote writes unless the user explicitly confirms that action in this fresh session.",
|
|
189
|
+
].join("\n");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function prepareContributionPrep(
|
|
193
|
+
context: ContributionPrepContext,
|
|
194
|
+
options: ContributionPrepOptions = {},
|
|
195
|
+
): Promise<ContributionPrepResult> {
|
|
196
|
+
const createdAt = (options.now ?? context.now ?? new Date()).toISOString();
|
|
197
|
+
const safeTimestamp = createdAt.replace(/[:.]/g, "-");
|
|
198
|
+
const artifactDir = path.join(
|
|
199
|
+
options.artifactRoot ?? path.join(context.cwd, ".gjc", "contribution-prep"),
|
|
200
|
+
safeTimestamp,
|
|
201
|
+
);
|
|
202
|
+
await fs.mkdir(artifactDir, { recursive: true });
|
|
203
|
+
|
|
204
|
+
const redactions: RedactionState = { labels: new Set() };
|
|
205
|
+
const recentMessages = context.messages.slice(-MAX_TRANSCRIPT_MESSAGES);
|
|
206
|
+
const artifacts: ContributionPrepArtifact[] = [];
|
|
207
|
+
const redact = (text: string) => redactContributionPrepText(text, context.cwd, redactions);
|
|
208
|
+
|
|
209
|
+
artifacts.push(
|
|
210
|
+
await writeArtifact(
|
|
211
|
+
artifactDir,
|
|
212
|
+
"transcript.md",
|
|
213
|
+
"Redacted recent transcript window",
|
|
214
|
+
redact(recentMessages.map(formatMessage).join("\n---\n")),
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
artifacts.push(
|
|
218
|
+
await writeArtifact(
|
|
219
|
+
artifactDir,
|
|
220
|
+
"summary.md",
|
|
221
|
+
"Current session summary and operator instructions",
|
|
222
|
+
redact(
|
|
223
|
+
[
|
|
224
|
+
`# Contribution prep context`,
|
|
225
|
+
`Source session: ${context.sessionId}`,
|
|
226
|
+
`Session file: ${context.sessionFile ?? "(none)"}`,
|
|
227
|
+
`Working directory: ${context.cwd}`,
|
|
228
|
+
options.customInstructions || context.customInstructions
|
|
229
|
+
? `Custom instructions: ${options.customInstructions ?? context.customInstructions}`
|
|
230
|
+
: "Custom instructions: (none)",
|
|
231
|
+
].join("\n"),
|
|
232
|
+
),
|
|
233
|
+
),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const gitHead = (await gitOutput(context.cwd, ["rev-parse", "HEAD"])) || null;
|
|
237
|
+
const files = await changedFiles(context.cwd);
|
|
238
|
+
artifacts.push(
|
|
239
|
+
await writeArtifact(artifactDir, "changed-files.txt", "Changed files from git status", redact(files.join("\n"))),
|
|
240
|
+
);
|
|
241
|
+
artifacts.push(
|
|
242
|
+
await writeArtifact(
|
|
243
|
+
artifactDir,
|
|
244
|
+
"git-diff.patch",
|
|
245
|
+
"Bounded redacted git diff",
|
|
246
|
+
redact(await gitOutput(context.cwd, ["diff", "--no-ext-diff"])),
|
|
247
|
+
),
|
|
248
|
+
);
|
|
249
|
+
artifacts.push(
|
|
250
|
+
await writeArtifact(
|
|
251
|
+
artifactDir,
|
|
252
|
+
"environment.md",
|
|
253
|
+
"Redacted environment and reproduction metadata",
|
|
254
|
+
redact(
|
|
255
|
+
[
|
|
256
|
+
`cwd: ${context.cwd}`,
|
|
257
|
+
`git_head: ${gitHead ?? "unknown"}`,
|
|
258
|
+
`platform: ${process.platform}`,
|
|
259
|
+
`arch: ${process.arch}`,
|
|
260
|
+
`bun: ${Bun.version}`,
|
|
261
|
+
].join("\n"),
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const manifestPath = path.join(artifactDir, "manifest.json");
|
|
267
|
+
const workerPromptPath = path.join(artifactDir, "worker-prompt.md");
|
|
268
|
+
await Bun.write(workerPromptPath, `${buildContributionPrepWorkerPrompt(manifestPath)}\n`);
|
|
269
|
+
|
|
270
|
+
const manifest: ContributionPrepManifest = {
|
|
271
|
+
schema_version: CONTRIBUTION_PREP_SCHEMA_VERSION,
|
|
272
|
+
source_session_id: context.sessionId,
|
|
273
|
+
created_at: createdAt,
|
|
274
|
+
cwd: redact(context.cwd),
|
|
275
|
+
git_head: gitHead,
|
|
276
|
+
changed_files: files,
|
|
277
|
+
artifacts,
|
|
278
|
+
redactions: [...redactions.labels].sort(),
|
|
279
|
+
recommended_output: [
|
|
280
|
+
"title",
|
|
281
|
+
"problem summary",
|
|
282
|
+
"reproduction/context",
|
|
283
|
+
"proposed fix or implementation plan",
|
|
284
|
+
"affected files",
|
|
285
|
+
"tests to run",
|
|
286
|
+
"uncertainty / remaining risks",
|
|
287
|
+
],
|
|
288
|
+
worker_prompt_path: workerPromptPath,
|
|
289
|
+
};
|
|
290
|
+
await Bun.write(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
291
|
+
|
|
292
|
+
let spawned = false;
|
|
293
|
+
if (options.spawnWorker) {
|
|
294
|
+
const spawn =
|
|
295
|
+
options.spawn ??
|
|
296
|
+
(async (args, cwd, shell) => {
|
|
297
|
+
if (shell) {
|
|
298
|
+
Bun.spawn({
|
|
299
|
+
cmd: args,
|
|
300
|
+
cwd,
|
|
301
|
+
stdout: "inherit",
|
|
302
|
+
stderr: "inherit",
|
|
303
|
+
stdin: "inherit",
|
|
304
|
+
windowsVerbatimArguments: true,
|
|
305
|
+
});
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
Bun.spawn(args, { cwd, stdout: "inherit", stderr: "inherit", stdin: "inherit" });
|
|
309
|
+
});
|
|
310
|
+
const command = resolveGjcCommand();
|
|
311
|
+
await spawn(
|
|
312
|
+
[command.cmd, ...command.args, "--no-skills", "--", `@${workerPromptPath}`],
|
|
313
|
+
context.cwd,
|
|
314
|
+
command.shell,
|
|
315
|
+
);
|
|
316
|
+
spawned = true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return { manifestPath, workerPromptPath, artifactDir, changedFiles: files, spawned };
|
|
320
|
+
}
|
|
@@ -176,6 +176,8 @@ export interface SessionInitEntry extends SessionEntryBase {
|
|
|
176
176
|
tools: string[];
|
|
177
177
|
/** Output schema if structured output was requested */
|
|
178
178
|
outputSchema?: unknown;
|
|
179
|
+
/** Fork-context seed metadata for subagent debugging/replay. */
|
|
180
|
+
forkContext?: unknown;
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
/** Mode change entry - tracks agent mode transitions (e.g. plan mode). */
|
|
@@ -2714,7 +2716,13 @@ export class SessionManager {
|
|
|
2714
2716
|
}
|
|
2715
2717
|
|
|
2716
2718
|
/** Append session init metadata (for subagent debugging/replay). Returns entry id. */
|
|
2717
|
-
appendSessionInit(init: {
|
|
2719
|
+
appendSessionInit(init: {
|
|
2720
|
+
systemPrompt: string;
|
|
2721
|
+
task: string;
|
|
2722
|
+
tools: string[];
|
|
2723
|
+
outputSchema?: unknown;
|
|
2724
|
+
forkContext?: unknown;
|
|
2725
|
+
}): string {
|
|
2718
2726
|
const entry: SessionInitEntry = {
|
|
2719
2727
|
type: "session_init",
|
|
2720
2728
|
id: generateId(this.#byId),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const MODEL_ONBOARDING_API_PROVIDER_COMMAND =
|
|
2
2
|
"/provider add --compat <openai|anthropic> --provider <id> --base-url <url> --api-key-env <ENV> --model <model>";
|
|
3
|
+
export const MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND = "/provider add --preset <minimax|minimax-cn|glm>";
|
|
3
4
|
|
|
4
5
|
export const MODEL_ONBOARDING_SETUP_COMMAND = "gjc setup provider";
|
|
5
6
|
export const MODEL_ONBOARDING_OAUTH_COMMAND = "/provider login [provider-id] or /login [provider-id]";
|
|
@@ -9,14 +10,15 @@ export function formatModelOnboardingGuidance(): string {
|
|
|
9
10
|
"Model selection only shows configured providers.",
|
|
10
11
|
"Assignment targets are DEFAULT plus the GJC role agents: EXECUTOR, ARCHITECT, PLANNER, and CRITIC.",
|
|
11
12
|
"Legacy model-role aliases are compatibility-only and are not shown as assignment targets.",
|
|
12
|
-
`
|
|
13
|
+
`Provider presets: ${MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND} --preset <preset>).`,
|
|
14
|
+
`API-compatible custom providers: ${MODEL_ONBOARDING_API_PROVIDER_COMMAND}.`,
|
|
13
15
|
`OAuth/subscription providers: ${MODEL_ONBOARDING_OAUTH_COMMAND}.`,
|
|
14
16
|
"Then run /model to select a configured model or assign it to a target.",
|
|
15
17
|
].join("\n");
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export function formatModelOnboardingInlineHint(): string {
|
|
19
|
-
return `Add API
|
|
21
|
+
return `Add MiniMax/GLM presets with ${MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND}; custom API providers with ${MODEL_ONBOARDING_API_PROVIDER_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND}); OAuth/subscription with ${MODEL_ONBOARDING_OAUTH_COMMAND}; then run /model for DEFAULT, EXECUTOR, ARCHITECT, PLANNER, and CRITIC.`;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export function formatNoModelOnboardingError(): string {
|
|
@@ -27,7 +29,8 @@ export function formatNoCredentialOnboardingError(providerId: string): string {
|
|
|
27
29
|
return [
|
|
28
30
|
`No credentials found for ${providerId}.`,
|
|
29
31
|
"",
|
|
30
|
-
`For
|
|
32
|
+
`For MiniMax/GLM presets, configure credentials with ${MODEL_ONBOARDING_PROVIDER_PRESET_COMMAND} (or ${MODEL_ONBOARDING_SETUP_COMMAND} --preset <preset>).`,
|
|
33
|
+
`For custom API-compatible providers, use ${MODEL_ONBOARDING_API_PROVIDER_COMMAND}.`,
|
|
31
34
|
`For OAuth/subscription providers, use ${MODEL_ONBOARDING_OAUTH_COMMAND}.`,
|
|
32
35
|
"Then run /model to select a configured model or assign it to DEFAULT, EXECUTOR, ARCHITECT, PLANNER, or CRITIC.",
|
|
33
36
|
].join("\n");
|
|
@@ -5,14 +5,16 @@ import { YAML } from "bun";
|
|
|
5
5
|
import { type ModelsConfig, ModelsConfigSchema } from "../config/models-config-schema";
|
|
6
6
|
|
|
7
7
|
export type ProviderCompatibility = "openai" | "anthropic";
|
|
8
|
+
export type ProviderSetupApi = "openai-responses" | "openai-completions" | "anthropic-messages";
|
|
8
9
|
|
|
9
10
|
export interface ProviderSetupInput {
|
|
10
|
-
compatibility
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
compatibility?: ProviderCompatibility;
|
|
12
|
+
preset?: string;
|
|
13
|
+
providerId?: string;
|
|
14
|
+
baseUrl?: string;
|
|
13
15
|
apiKey?: string;
|
|
14
16
|
apiKeyEnv?: string;
|
|
15
|
-
models
|
|
17
|
+
models?: string[];
|
|
16
18
|
modelsPath?: string;
|
|
17
19
|
force?: boolean;
|
|
18
20
|
}
|
|
@@ -20,19 +22,93 @@ export interface ProviderSetupInput {
|
|
|
20
22
|
export interface ProviderSetupResult {
|
|
21
23
|
providerId: string;
|
|
22
24
|
compatibility: ProviderCompatibility;
|
|
23
|
-
api:
|
|
25
|
+
api: ProviderSetupApi;
|
|
24
26
|
baseUrl: string;
|
|
25
27
|
modelIds: string[];
|
|
26
28
|
modelsPath: string;
|
|
27
29
|
redactedApiKey: string;
|
|
28
30
|
credentialSource: "literal" | "env";
|
|
31
|
+
preset?: string;
|
|
32
|
+
presetName?: string;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
type ProviderConfig = NonNullable<NonNullable<ModelsConfig["providers"]>[string]>;
|
|
36
|
+
type ProviderCompatConfig = NonNullable<ProviderConfig["compat"]>;
|
|
37
|
+
|
|
38
|
+
interface ProviderPreset {
|
|
39
|
+
id: string;
|
|
40
|
+
aliases: readonly string[];
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
compatibility: ProviderCompatibility;
|
|
44
|
+
api: ProviderSetupApi;
|
|
45
|
+
providerId: string;
|
|
46
|
+
baseUrl: string;
|
|
47
|
+
apiKeyEnv: string;
|
|
48
|
+
models: readonly string[];
|
|
49
|
+
compat?: ProviderCompatConfig;
|
|
50
|
+
}
|
|
32
51
|
|
|
33
52
|
const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
|
|
34
53
|
const REDACT_PREFIX = 4;
|
|
35
54
|
const REDACT_SUFFIX = 4;
|
|
55
|
+
// Preset compat values are onboarding snapshots for generated models.yml entries.
|
|
56
|
+
// Keep them aligned with provider descriptor behavior without importing descriptor internals into setup UX.
|
|
57
|
+
const MINIMAX_OPENAI_COMPAT: ProviderCompatConfig = {
|
|
58
|
+
supportsStore: false,
|
|
59
|
+
supportsDeveloperRole: false,
|
|
60
|
+
supportsReasoningEffort: false,
|
|
61
|
+
reasoningContentField: "reasoning_content",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const GLM_OPENAI_COMPAT: ProviderCompatConfig = {
|
|
65
|
+
supportsDeveloperRole: false,
|
|
66
|
+
supportsReasoningEffort: false,
|
|
67
|
+
thinkingFormat: "zai",
|
|
68
|
+
reasoningContentField: "reasoning_content",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const PROVIDER_PRESETS: readonly ProviderPreset[] = [
|
|
72
|
+
{
|
|
73
|
+
id: "minimax",
|
|
74
|
+
aliases: ["minimax-code"],
|
|
75
|
+
name: "MiniMax Coding Plan",
|
|
76
|
+
description: "OpenAI-compatible MiniMax Coding Plan endpoint",
|
|
77
|
+
compatibility: "openai",
|
|
78
|
+
api: "openai-completions",
|
|
79
|
+
providerId: "minimax-code",
|
|
80
|
+
baseUrl: "https://api.minimax.io/v1",
|
|
81
|
+
apiKeyEnv: "MINIMAX_CODE_API_KEY",
|
|
82
|
+
models: ["MiniMax-M2.5"],
|
|
83
|
+
compat: MINIMAX_OPENAI_COMPAT,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "minimax-cn",
|
|
87
|
+
aliases: ["minimax-code-cn", "minimaxi"],
|
|
88
|
+
name: "MiniMax Coding Plan (China)",
|
|
89
|
+
description: "OpenAI-compatible MiniMax China endpoint",
|
|
90
|
+
compatibility: "openai",
|
|
91
|
+
api: "openai-completions",
|
|
92
|
+
providerId: "minimax-code-cn",
|
|
93
|
+
baseUrl: "https://api.minimaxi.com/v1",
|
|
94
|
+
apiKeyEnv: "MINIMAX_CODE_CN_API_KEY",
|
|
95
|
+
models: ["MiniMax-M2.5"],
|
|
96
|
+
compat: MINIMAX_OPENAI_COMPAT,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "glm",
|
|
100
|
+
aliases: ["zai", "z-ai", "bigmodel"],
|
|
101
|
+
name: "GLM / zAI",
|
|
102
|
+
description: "OpenAI-compatible GLM endpoint from zAI/BigModel",
|
|
103
|
+
compatibility: "openai",
|
|
104
|
+
api: "openai-completions",
|
|
105
|
+
providerId: "glm-proxy",
|
|
106
|
+
baseUrl: "https://api.z.ai/api/paas/v4",
|
|
107
|
+
apiKeyEnv: "ZAI_API_KEY",
|
|
108
|
+
models: ["glm-4.6"],
|
|
109
|
+
compat: GLM_OPENAI_COMPAT,
|
|
110
|
+
},
|
|
111
|
+
];
|
|
36
112
|
|
|
37
113
|
export function getDefaultModelsPath(): string {
|
|
38
114
|
return path.join(getAgentDir(), "models.yml");
|
|
@@ -51,6 +127,19 @@ export function parseProviderCompatibility(value: string): ProviderCompatibility
|
|
|
51
127
|
throw new Error("Provider compatibility must be 'openai' or 'anthropic'.");
|
|
52
128
|
}
|
|
53
129
|
|
|
130
|
+
export function findProviderPreset(value: string | undefined): ProviderPreset | undefined {
|
|
131
|
+
const normalized = value?.trim().toLowerCase();
|
|
132
|
+
if (!normalized) return undefined;
|
|
133
|
+
return PROVIDER_PRESETS.find(preset => preset.id === normalized || preset.aliases.includes(normalized));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function formatProviderPresetList(): string {
|
|
137
|
+
return PROVIDER_PRESETS.map(preset => {
|
|
138
|
+
const aliases = preset.aliases.length > 0 ? ` (aliases: ${preset.aliases.join(", ")})` : "";
|
|
139
|
+
return `${preset.id}${aliases}: ${preset.description}`;
|
|
140
|
+
}).join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
54
143
|
export function parseModelList(values: readonly string[]): string[] {
|
|
55
144
|
const models = values
|
|
56
145
|
.flatMap(value => value.split(","))
|
|
@@ -65,23 +154,82 @@ export function redactSecret(secret: string): string {
|
|
|
65
154
|
return `${trimmed.slice(0, REDACT_PREFIX)}…${trimmed.slice(-REDACT_SUFFIX)}`;
|
|
66
155
|
}
|
|
67
156
|
|
|
68
|
-
function apiForCompatibility(compatibility: ProviderCompatibility):
|
|
157
|
+
function apiForCompatibility(compatibility: ProviderCompatibility): ProviderSetupApi {
|
|
69
158
|
return compatibility === "openai" ? "openai-responses" : "anthropic-messages";
|
|
70
159
|
}
|
|
71
160
|
|
|
161
|
+
function resolvePresetInput(input: ProviderSetupInput): {
|
|
162
|
+
compatibility: ProviderCompatibility;
|
|
163
|
+
preset?: ProviderPreset;
|
|
164
|
+
providerId?: string;
|
|
165
|
+
baseUrl?: string;
|
|
166
|
+
apiKey?: string;
|
|
167
|
+
apiKeyEnv?: string;
|
|
168
|
+
models: readonly string[];
|
|
169
|
+
api: ProviderSetupApi;
|
|
170
|
+
compat?: ProviderCompatConfig;
|
|
171
|
+
} {
|
|
172
|
+
const preset = input.preset ? findProviderPreset(input.preset) : undefined;
|
|
173
|
+
if (input.preset && !preset) {
|
|
174
|
+
throw new Error(`Unknown provider preset '${input.preset}'. Available presets:\n${formatProviderPresetList()}`);
|
|
175
|
+
}
|
|
176
|
+
if (preset && input.compatibility && input.compatibility !== preset.compatibility) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Provider preset '${preset.id}' is ${preset.compatibility}-compatible; omit --compat or use '${preset.compatibility}'.`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
if (preset && input.baseUrl !== undefined) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Provider preset '${preset.id}' uses a fixed base URL; omit --base-url or use --compat openai for a custom provider.`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (preset && input.models && input.models.length > 0) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Provider preset '${preset.id}' uses fixed model ids; omit --model or use --compat openai for a custom provider.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (preset && input.apiKeyEnv !== undefined && input.apiKeyEnv.trim() !== preset.apiKeyEnv) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Provider preset '${preset.id}' uses ${preset.apiKeyEnv}; omit --api-key-env or use --compat openai for a custom provider.`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
const compatibility = preset?.compatibility ?? input.compatibility;
|
|
197
|
+
if (!compatibility) {
|
|
198
|
+
throw new Error("Provider compatibility is required unless --preset is used.");
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
compatibility,
|
|
202
|
+
preset,
|
|
203
|
+
providerId: input.providerId ?? preset?.providerId,
|
|
204
|
+
baseUrl: input.baseUrl ?? preset?.baseUrl,
|
|
205
|
+
apiKey: input.apiKey,
|
|
206
|
+
apiKeyEnv: input.apiKeyEnv ?? preset?.apiKeyEnv,
|
|
207
|
+
models: input.models && input.models.length > 0 ? input.models : (preset?.models ?? []),
|
|
208
|
+
api: preset?.api ?? apiForCompatibility(compatibility),
|
|
209
|
+
compat: preset?.compat,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
72
213
|
function validateSetupInput(input: ProviderSetupInput): {
|
|
73
214
|
providerId: string;
|
|
74
215
|
baseUrl: string;
|
|
75
216
|
apiKey: string;
|
|
76
217
|
credentialSource: ProviderSetupResult["credentialSource"];
|
|
77
218
|
models: string[];
|
|
219
|
+
compatibility: ProviderCompatibility;
|
|
220
|
+
api: ProviderSetupApi;
|
|
221
|
+
compat?: ProviderCompatConfig;
|
|
222
|
+
preset?: ProviderPreset;
|
|
78
223
|
} {
|
|
79
|
-
const
|
|
224
|
+
const resolved = resolvePresetInput(input);
|
|
225
|
+
if (!resolved.providerId) throw new Error("Provider id is required.");
|
|
226
|
+
if (!resolved.baseUrl) throw new Error("Base URL is required.");
|
|
227
|
+
const providerId = normalizeProviderId(resolved.providerId);
|
|
80
228
|
if (!PROVIDER_ID_PATTERN.test(providerId)) {
|
|
81
229
|
throw new Error("Provider id must use lowercase letters, numbers, dots, underscores, or hyphens.");
|
|
82
230
|
}
|
|
83
231
|
|
|
84
|
-
const baseUrl =
|
|
232
|
+
const baseUrl = resolved.baseUrl.trim();
|
|
85
233
|
let url: URL;
|
|
86
234
|
try {
|
|
87
235
|
url = new URL(baseUrl);
|
|
@@ -95,19 +243,29 @@ function validateSetupInput(input: ProviderSetupInput): {
|
|
|
95
243
|
throw new Error("Base URL must use https unless it targets localhost or a loopback address.");
|
|
96
244
|
}
|
|
97
245
|
|
|
98
|
-
const apiKeyEnv =
|
|
246
|
+
const apiKeyEnv = resolved.apiKeyEnv?.trim();
|
|
99
247
|
if (apiKeyEnv) {
|
|
100
248
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(apiKeyEnv)) {
|
|
101
249
|
throw new Error("API key environment variable must be a valid environment variable name.");
|
|
102
250
|
}
|
|
103
251
|
}
|
|
104
|
-
const apiKey = apiKeyEnv ??
|
|
252
|
+
const apiKey = apiKeyEnv ?? resolved.apiKey?.trim() ?? "";
|
|
105
253
|
if (!apiKey) throw new Error("API key is required.");
|
|
106
254
|
|
|
107
|
-
const models = parseModelList(
|
|
255
|
+
const models = parseModelList(resolved.models);
|
|
108
256
|
if (models.length === 0) throw new Error("At least one model id is required.");
|
|
109
257
|
|
|
110
|
-
return {
|
|
258
|
+
return {
|
|
259
|
+
providerId,
|
|
260
|
+
baseUrl,
|
|
261
|
+
apiKey,
|
|
262
|
+
credentialSource: apiKeyEnv ? "env" : "literal",
|
|
263
|
+
models,
|
|
264
|
+
compatibility: resolved.compatibility,
|
|
265
|
+
api: resolved.api,
|
|
266
|
+
compat: resolved.compat,
|
|
267
|
+
preset: resolved.preset,
|
|
268
|
+
};
|
|
111
269
|
}
|
|
112
270
|
|
|
113
271
|
async function readModelsConfig(modelsPath: string): Promise<ModelsConfig> {
|
|
@@ -140,16 +298,16 @@ export async function addApiCompatibleProvider(input: ProviderSetupInput): Promi
|
|
|
140
298
|
const validated = validateSetupInput(input);
|
|
141
299
|
const modelsPath = input.modelsPath ?? getDefaultModelsPath();
|
|
142
300
|
const existing = await readModelsConfig(modelsPath);
|
|
143
|
-
const api = apiForCompatibility(input.compatibility);
|
|
144
301
|
if (existing.providers?.[validated.providerId] && !input.force) {
|
|
145
302
|
throw new Error(`Provider '${validated.providerId}' already exists. Use --force to replace it.`);
|
|
146
303
|
}
|
|
147
304
|
const provider: ProviderConfig = {
|
|
148
305
|
baseUrl: validated.baseUrl,
|
|
149
|
-
api,
|
|
306
|
+
api: validated.api,
|
|
150
307
|
auth: "apiKey",
|
|
151
308
|
models: validated.models.map(id => ({ id })),
|
|
152
309
|
};
|
|
310
|
+
if (validated.compat) provider.compat = validated.compat;
|
|
153
311
|
if (validated.credentialSource === "env") {
|
|
154
312
|
provider.apiKeyEnv = validated.apiKey;
|
|
155
313
|
} else {
|
|
@@ -165,13 +323,15 @@ export async function addApiCompatibleProvider(input: ProviderSetupInput): Promi
|
|
|
165
323
|
await writeModelsConfig(modelsPath, next);
|
|
166
324
|
return {
|
|
167
325
|
providerId: validated.providerId,
|
|
168
|
-
compatibility:
|
|
169
|
-
api,
|
|
326
|
+
compatibility: validated.compatibility,
|
|
327
|
+
api: validated.api,
|
|
170
328
|
baseUrl: validated.baseUrl,
|
|
171
329
|
modelIds: validated.models,
|
|
172
330
|
modelsPath,
|
|
173
331
|
redactedApiKey: redactSecret(validated.apiKey),
|
|
174
332
|
credentialSource: validated.credentialSource,
|
|
333
|
+
preset: validated.preset?.id,
|
|
334
|
+
presetName: validated.preset?.name,
|
|
175
335
|
};
|
|
176
336
|
}
|
|
177
337
|
|
|
@@ -189,6 +349,7 @@ function isLocalHttpHost(hostname: string): boolean {
|
|
|
189
349
|
export function formatProviderSetupResult(result: ProviderSetupResult): string {
|
|
190
350
|
return [
|
|
191
351
|
`Provider '${result.providerId}' configured as ${result.compatibility}-compatible.`,
|
|
352
|
+
...(result.presetName ? [`Preset: ${result.presetName}`] : []),
|
|
192
353
|
`Models: ${result.modelIds.join(", ")}`,
|
|
193
354
|
`Base URL: ${result.baseUrl}`,
|
|
194
355
|
`API key: ${result.credentialSource === "env" ? `${result.redactedApiKey} (environment variable)` : result.redactedApiKey}`,
|