webmux 0.10.1 → 0.11.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/backend/dist/server.js
CHANGED
|
@@ -7399,10 +7399,12 @@ function parseLifecycleHooks(raw) {
|
|
|
7399
7399
|
function parseAutoName(raw) {
|
|
7400
7400
|
if (!isRecord(raw))
|
|
7401
7401
|
return null;
|
|
7402
|
-
|
|
7402
|
+
const provider = raw.provider;
|
|
7403
|
+
if (provider !== "claude" && provider !== "codex")
|
|
7403
7404
|
return null;
|
|
7404
7405
|
return {
|
|
7405
|
-
|
|
7406
|
+
provider,
|
|
7407
|
+
...typeof raw.model === "string" && raw.model.trim() ? { model: raw.model.trim() } : {},
|
|
7406
7408
|
...typeof raw.system_prompt === "string" && raw.system_prompt.trim() ? { systemPrompt: raw.system_prompt.trim() } : {}
|
|
7407
7409
|
};
|
|
7408
7410
|
}
|
|
@@ -9857,47 +9859,16 @@ class BunPortProbe {
|
|
|
9857
9859
|
}
|
|
9858
9860
|
|
|
9859
9861
|
// backend/src/services/auto-name-service.ts
|
|
9860
|
-
var BRANCH_NAME_SCHEMA = {
|
|
9861
|
-
type: "object",
|
|
9862
|
-
properties: {
|
|
9863
|
-
branch_name: {
|
|
9864
|
-
type: "string",
|
|
9865
|
-
description: "A lowercase kebab-case git branch name with no prefix"
|
|
9866
|
-
}
|
|
9867
|
-
},
|
|
9868
|
-
required: ["branch_name"],
|
|
9869
|
-
additionalProperties: false
|
|
9870
|
-
};
|
|
9871
|
-
var GEMINI_BRANCH_NAME_SCHEMA = {
|
|
9872
|
-
...BRANCH_NAME_SCHEMA,
|
|
9873
|
-
propertyOrdering: ["branch_name"]
|
|
9874
|
-
};
|
|
9875
9862
|
var DEFAULT_SYSTEM_PROMPT = [
|
|
9876
9863
|
"Generate a concise git branch name from the task description.",
|
|
9877
9864
|
"Return only the branch name.",
|
|
9878
9865
|
"Use lowercase kebab-case.",
|
|
9879
9866
|
"Do not include quotes, code fences, or prefixes like feature/ or fix/."
|
|
9880
9867
|
].join(" ");
|
|
9881
|
-
function isRecord4(value) {
|
|
9882
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9883
|
-
}
|
|
9884
9868
|
function buildPrompt(task) {
|
|
9885
9869
|
return `Task description:
|
|
9886
9870
|
${task.trim()}`;
|
|
9887
9871
|
}
|
|
9888
|
-
function parseBranchNamePayload(raw) {
|
|
9889
|
-
if (!isRecord4(raw) || typeof raw.branch_name !== "string") {
|
|
9890
|
-
throw new Error("Auto-name response did not include branch_name");
|
|
9891
|
-
}
|
|
9892
|
-
return raw.branch_name;
|
|
9893
|
-
}
|
|
9894
|
-
function parseJsonText(text) {
|
|
9895
|
-
try {
|
|
9896
|
-
return JSON.parse(text);
|
|
9897
|
-
} catch {
|
|
9898
|
-
throw new Error(`Auto-name response was not valid JSON: ${text}`);
|
|
9899
|
-
}
|
|
9900
|
-
}
|
|
9901
9872
|
function normalizeGeneratedBranchName(raw) {
|
|
9902
9873
|
let branch = raw.trim();
|
|
9903
9874
|
branch = branch.replace(/^```[\w-]*\s*/, "").replace(/\s*```$/, "");
|
|
@@ -9917,231 +9888,84 @@ function normalizeGeneratedBranchName(raw) {
|
|
|
9917
9888
|
}
|
|
9918
9889
|
return branch;
|
|
9919
9890
|
}
|
|
9920
|
-
function resolveAutoNameModel(modelSpec) {
|
|
9921
|
-
const trimmed = modelSpec.trim();
|
|
9922
|
-
const slashIndex = trimmed.indexOf("/");
|
|
9923
|
-
if (slashIndex > 0) {
|
|
9924
|
-
const provider = trimmed.slice(0, slashIndex);
|
|
9925
|
-
const model = trimmed.slice(slashIndex + 1).trim().replace(/^models\//, "");
|
|
9926
|
-
if (!model) {
|
|
9927
|
-
throw new Error(`Invalid auto_name model: ${modelSpec}`);
|
|
9928
|
-
}
|
|
9929
|
-
if (provider === "anthropic" || provider === "google" || provider === "openai") {
|
|
9930
|
-
return { provider, model };
|
|
9931
|
-
}
|
|
9932
|
-
if (provider === "gemini") {
|
|
9933
|
-
return { provider: "google", model };
|
|
9934
|
-
}
|
|
9935
|
-
}
|
|
9936
|
-
if (trimmed.startsWith("claude-")) {
|
|
9937
|
-
return { provider: "anthropic", model: trimmed };
|
|
9938
|
-
}
|
|
9939
|
-
if (trimmed.startsWith("gemini-") || trimmed.startsWith("models/gemini-")) {
|
|
9940
|
-
return { provider: "google", model: trimmed.replace(/^models\//, "") };
|
|
9941
|
-
}
|
|
9942
|
-
if (/^(gpt-|chatgpt-|o\d)/.test(trimmed)) {
|
|
9943
|
-
return { provider: "openai", model: trimmed };
|
|
9944
|
-
}
|
|
9945
|
-
throw new Error(`Unsupported auto_name model provider for ${modelSpec}. Use an anthropic/, gemini/, google/, or openai/ prefix, or a known model name.`);
|
|
9946
|
-
}
|
|
9947
9891
|
function getSystemPrompt(config) {
|
|
9948
9892
|
return config.systemPrompt?.trim() || DEFAULT_SYSTEM_PROMPT;
|
|
9949
9893
|
}
|
|
9950
|
-
function
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9959
|
-
|
|
9960
|
-
return
|
|
9894
|
+
async function defaultSpawn(args) {
|
|
9895
|
+
const proc = Bun.spawn(args, {
|
|
9896
|
+
stdout: "pipe",
|
|
9897
|
+
stderr: "pipe"
|
|
9898
|
+
});
|
|
9899
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
9900
|
+
new Response(proc.stdout).text(),
|
|
9901
|
+
new Response(proc.stderr).text(),
|
|
9902
|
+
proc.exited
|
|
9903
|
+
]);
|
|
9904
|
+
return { exitCode, stdout, stderr };
|
|
9961
9905
|
}
|
|
9962
|
-
function
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
|
|
9906
|
+
function buildClaudeArgs(model, systemPrompt, prompt) {
|
|
9907
|
+
const args = [
|
|
9908
|
+
"claude",
|
|
9909
|
+
"-p",
|
|
9910
|
+
"--system-prompt",
|
|
9911
|
+
systemPrompt,
|
|
9912
|
+
"--output-format",
|
|
9913
|
+
"text",
|
|
9914
|
+
"--no-session-persistence"
|
|
9915
|
+
];
|
|
9916
|
+
if (model) {
|
|
9917
|
+
args.push("--model", model);
|
|
9973
9918
|
}
|
|
9974
|
-
|
|
9919
|
+
args.push(prompt);
|
|
9920
|
+
return args;
|
|
9975
9921
|
}
|
|
9976
|
-
function
|
|
9977
|
-
|
|
9978
|
-
return null;
|
|
9979
|
-
if (typeof raw.output_text === "string" && raw.output_text.trim()) {
|
|
9980
|
-
return raw.output_text;
|
|
9981
|
-
}
|
|
9982
|
-
if (!Array.isArray(raw.output))
|
|
9983
|
-
return null;
|
|
9984
|
-
for (const item of raw.output) {
|
|
9985
|
-
if (!isRecord4(item) || !Array.isArray(item.content))
|
|
9986
|
-
continue;
|
|
9987
|
-
for (const content of item.content) {
|
|
9988
|
-
if (!isRecord4(content))
|
|
9989
|
-
continue;
|
|
9990
|
-
if (typeof content.text === "string" && content.text.trim()) {
|
|
9991
|
-
return content.text;
|
|
9992
|
-
}
|
|
9993
|
-
}
|
|
9994
|
-
}
|
|
9995
|
-
return null;
|
|
9922
|
+
function escapeTomlString(s) {
|
|
9923
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
9996
9924
|
}
|
|
9997
|
-
|
|
9998
|
-
const
|
|
9999
|
-
|
|
9925
|
+
function buildCodexArgs(model, systemPrompt, prompt) {
|
|
9926
|
+
const args = [
|
|
9927
|
+
"codex",
|
|
9928
|
+
"-c",
|
|
9929
|
+
`developer_instructions="${escapeTomlString(systemPrompt)}"`,
|
|
9930
|
+
"exec",
|
|
9931
|
+
"--ephemeral"
|
|
9932
|
+
];
|
|
9933
|
+
if (model) {
|
|
9934
|
+
args.push("-m", model);
|
|
9935
|
+
}
|
|
9936
|
+
args.push(prompt);
|
|
9937
|
+
return args;
|
|
10000
9938
|
}
|
|
10001
9939
|
|
|
10002
9940
|
class AutoNameService {
|
|
10003
|
-
|
|
10004
|
-
anthropicApiKey;
|
|
10005
|
-
geminiApiKey;
|
|
10006
|
-
openaiApiKey;
|
|
9941
|
+
spawnImpl;
|
|
10007
9942
|
constructor(deps = {}) {
|
|
10008
|
-
this.
|
|
10009
|
-
this.anthropicApiKey = deps.anthropicApiKey ?? Bun.env.ANTHROPIC_API_KEY;
|
|
10010
|
-
this.geminiApiKey = deps.geminiApiKey ?? Bun.env.GEMINI_API_KEY;
|
|
10011
|
-
this.openaiApiKey = deps.openaiApiKey ?? Bun.env.OPENAI_API_KEY;
|
|
9943
|
+
this.spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
10012
9944
|
}
|
|
10013
9945
|
async generateBranchName(config, task) {
|
|
10014
9946
|
const prompt = task.trim();
|
|
10015
9947
|
if (!prompt) {
|
|
10016
9948
|
throw new Error("Auto-name requires a prompt");
|
|
10017
9949
|
}
|
|
10018
|
-
const
|
|
10019
|
-
const
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
}
|
|
10026
|
-
|
|
10027
|
-
method: "POST",
|
|
10028
|
-
headers: {
|
|
10029
|
-
"content-type": "application/json",
|
|
10030
|
-
"x-api-key": this.anthropicApiKey,
|
|
10031
|
-
"anthropic-version": "2023-06-01"
|
|
10032
|
-
},
|
|
10033
|
-
body: JSON.stringify({
|
|
10034
|
-
model,
|
|
10035
|
-
system: systemPrompt,
|
|
10036
|
-
max_tokens: 64,
|
|
10037
|
-
messages: [{ role: "user", content: buildPrompt(task) }],
|
|
10038
|
-
output_config: {
|
|
10039
|
-
format: {
|
|
10040
|
-
type: "json_schema",
|
|
10041
|
-
schema: BRANCH_NAME_SCHEMA
|
|
10042
|
-
}
|
|
10043
|
-
}
|
|
10044
|
-
})
|
|
10045
|
-
});
|
|
10046
|
-
if (!response.ok) {
|
|
10047
|
-
throw new Error(`Anthropic auto-name request failed: ${await readErrorBody(response)}`);
|
|
10048
|
-
}
|
|
10049
|
-
const json = await response.json();
|
|
10050
|
-
if (isRecord4(json) && json.stop_reason === "refusal") {
|
|
10051
|
-
throw new Error("Anthropic auto-name request was refused");
|
|
10052
|
-
}
|
|
10053
|
-
if (isRecord4(json) && json.stop_reason === "max_tokens") {
|
|
10054
|
-
throw new Error("Anthropic auto-name response hit max_tokens before completing");
|
|
10055
|
-
}
|
|
10056
|
-
const text = extractAnthropicText(json);
|
|
10057
|
-
if (!text) {
|
|
10058
|
-
throw new Error("Anthropic auto-name response did not include text");
|
|
10059
|
-
}
|
|
10060
|
-
return parseBranchNamePayload(parseJsonText(text));
|
|
10061
|
-
}
|
|
10062
|
-
async generateWithGoogle(model, systemPrompt, task) {
|
|
10063
|
-
if (!this.geminiApiKey) {
|
|
10064
|
-
throw new Error("GEMINI_API_KEY is required for auto_name with Gemini models");
|
|
10065
|
-
}
|
|
10066
|
-
const response = await this.fetchImpl(`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`, {
|
|
10067
|
-
method: "POST",
|
|
10068
|
-
headers: {
|
|
10069
|
-
"content-type": "application/json",
|
|
10070
|
-
"x-goog-api-key": this.geminiApiKey
|
|
10071
|
-
},
|
|
10072
|
-
body: JSON.stringify({
|
|
10073
|
-
systemInstruction: {
|
|
10074
|
-
parts: [{ text: systemPrompt }]
|
|
10075
|
-
},
|
|
10076
|
-
contents: [
|
|
10077
|
-
{
|
|
10078
|
-
role: "user",
|
|
10079
|
-
parts: [{ text: buildPrompt(task) }]
|
|
10080
|
-
}
|
|
10081
|
-
],
|
|
10082
|
-
generationConfig: {
|
|
10083
|
-
responseMimeType: "application/json",
|
|
10084
|
-
responseJsonSchema: GEMINI_BRANCH_NAME_SCHEMA
|
|
10085
|
-
}
|
|
10086
|
-
})
|
|
10087
|
-
});
|
|
10088
|
-
if (!response.ok) {
|
|
10089
|
-
throw new Error(`Google auto-name request failed: ${await readErrorBody(response)}`);
|
|
10090
|
-
}
|
|
10091
|
-
const json = await response.json();
|
|
10092
|
-
const text = extractGoogleText(json);
|
|
10093
|
-
if (!text) {
|
|
10094
|
-
throw new Error("Google auto-name response did not include text");
|
|
10095
|
-
}
|
|
10096
|
-
return parseBranchNamePayload(parseJsonText(text));
|
|
10097
|
-
}
|
|
10098
|
-
async generateWithOpenAI(model, systemPrompt, task) {
|
|
10099
|
-
if (!this.openaiApiKey) {
|
|
10100
|
-
throw new Error("OPENAI_API_KEY is required for auto_name with OpenAI models");
|
|
10101
|
-
}
|
|
10102
|
-
const response = await this.fetchImpl("https://api.openai.com/v1/responses", {
|
|
10103
|
-
method: "POST",
|
|
10104
|
-
headers: {
|
|
10105
|
-
"content-type": "application/json",
|
|
10106
|
-
authorization: `Bearer ${this.openaiApiKey}`
|
|
10107
|
-
},
|
|
10108
|
-
body: JSON.stringify({
|
|
10109
|
-
model,
|
|
10110
|
-
input: [
|
|
10111
|
-
{ role: "system", content: systemPrompt },
|
|
10112
|
-
{ role: "user", content: buildPrompt(task) }
|
|
10113
|
-
],
|
|
10114
|
-
max_output_tokens: 64,
|
|
10115
|
-
text: {
|
|
10116
|
-
format: {
|
|
10117
|
-
type: "json_schema",
|
|
10118
|
-
name: "branch_name_response",
|
|
10119
|
-
strict: true,
|
|
10120
|
-
schema: BRANCH_NAME_SCHEMA
|
|
10121
|
-
}
|
|
10122
|
-
}
|
|
10123
|
-
})
|
|
10124
|
-
});
|
|
10125
|
-
if (!response.ok) {
|
|
10126
|
-
throw new Error(`OpenAI auto-name request failed: ${await readErrorBody(response)}`);
|
|
9950
|
+
const systemPrompt = getSystemPrompt(config);
|
|
9951
|
+
const userPrompt = buildPrompt(prompt);
|
|
9952
|
+
const args = config.provider === "claude" ? buildClaudeArgs(config.model, systemPrompt, userPrompt) : buildCodexArgs(config.model, systemPrompt, userPrompt);
|
|
9953
|
+
const cli = config.provider === "claude" ? "claude" : "codex";
|
|
9954
|
+
let result;
|
|
9955
|
+
try {
|
|
9956
|
+
result = await this.spawnImpl(args);
|
|
9957
|
+
} catch {
|
|
9958
|
+
throw new Error(`'${cli}' CLI not found. Install it or check your PATH.`);
|
|
10127
9959
|
}
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
if (!isRecord4(item) || !Array.isArray(item.content))
|
|
10132
|
-
continue;
|
|
10133
|
-
for (const content of item.content) {
|
|
10134
|
-
if (isRecord4(content) && content.type === "refusal" && typeof content.refusal === "string") {
|
|
10135
|
-
throw new Error(`OpenAI auto-name request was refused: ${content.refusal}`);
|
|
10136
|
-
}
|
|
10137
|
-
}
|
|
10138
|
-
}
|
|
9960
|
+
if (result.exitCode !== 0) {
|
|
9961
|
+
const detail = result.stderr.trim() || `exit ${result.exitCode}`;
|
|
9962
|
+
throw new Error(`${cli} failed: ${detail}`);
|
|
10139
9963
|
}
|
|
10140
|
-
const
|
|
10141
|
-
if (!
|
|
10142
|
-
throw new Error(
|
|
9964
|
+
const output = result.stdout.trim();
|
|
9965
|
+
if (!output) {
|
|
9966
|
+
throw new Error(`${cli} returned empty output`);
|
|
10143
9967
|
}
|
|
10144
|
-
return
|
|
9968
|
+
return normalizeGeneratedBranchName(output);
|
|
10145
9969
|
}
|
|
10146
9970
|
}
|
|
10147
9971
|
|