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