fix(ai): Codex CLI provider reliability fixes for DBX Desktop
* fix(ai): resolve Codex CLI from shell PATH on macOS * fix(ai): hide Codex CLI subprocess windows on Windows * feat(ai): allow to set environment variable for Codex CLI * fix(ai): parse Codex MCP tool events * fix(mcp): honor Postgres TLS settings in direct queries
This commit is contained in:
1 parent
2759480331
commit
034cce47e0
17 files changed
+613
-30
No files matched your search
@@ -403,10 +403,20 @@ function agentStepClass(tone: AiAgentStepTone): string {
|
||||
function extractToolResultContent(result: unknown): string | undefined {
|
||||
if (!result) return undefined;
|
||||
if (typeof result === "string") return result;
|
||||
if (Array.isArray(result)) return result.map(extractToolResultContent).filter(Boolean).join("\n");
|
||||
if (typeof result === "object" && result !== null && "content" in result) {
|
||||
const content = (result as Record<string, unknown>).content;
|
||||
if (Array.isArray(content)) return content.map(extractToolResultContent).filter(Boolean).join("\n");
|
||||
return typeof content === "string" ? content : JSON.stringify(content);
|
||||
}
|
||||
if (typeof result === "object" && result !== null && "text" in result) {
|
||||
const text = (result as Record<string, unknown>).text;
|
||||
if (typeof text === "string") return text;
|
||||
}
|
||||
if (typeof result === "object" && result !== null && "message" in result) {
|
||||
const message = (result as Record<string, unknown>).message;
|
||||
if (typeof message === "string") return message;
|
||||
}
|
||||
return JSON.stringify(result);
|
||||
}
|
||||
|
||||
@@ -454,7 +464,7 @@ function agentEventToStep(event: AgentEvent, index: number): AiAgentStepItem | u
|
||||
titleParams: { tool: event.tool_name || "" },
|
||||
toolName: event.tool_name,
|
||||
toolArgs: event.type === "tool_call_start" ? (event.args as Record<string, unknown>) : undefined,
|
||||
toolResult: event.type === "tool_call_end" && !event.is_error ? extractToolResultContent(event.result) : undefined,
|
||||
toolResult: event.type === "tool_call_end" ? extractToolResultContent(event.result) : undefined,
|
||||
explainData: event.type === "tool_call_end" ? extractExplainData(event.result) : undefined,
|
||||
isError: event.type === "tool_call_end" ? event.is_error : undefined,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ref, watch, shallowRef, computed, onMounted, onUnmounted, nextTick } fr
|
||||
import type { Ref } from "vue";
|
||||
import type { EditorView as EditorViewType } from "@codemirror/view";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { AlertTriangle, CheckCircle2, CircleHelp, Cloud, Copy, Download, ExternalLink, GripVertical, Loader2, Moon, PackageSearch, Pencil, RefreshCw, RotateCcw, Settings, Sun, SunMoon, Terminal, Trash2, Upload, X } from "@lucide/vue";
|
||||
import { AlertTriangle, CheckCircle2, CircleHelp, Cloud, Copy, Download, ExternalLink, GripVertical, Loader2, Moon, PackageSearch, Pencil, Plus, RefreshCw, RotateCcw, Settings, Sun, SunMoon, Terminal, Trash2, Upload, X } from "@lucide/vue";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
DEFAULT_EDITOR_SETTINGS,
|
||||
DEFAULT_DESKTOP_SETTINGS,
|
||||
DEFAULT_SIDEBAR_TABLE_PAGE_SIZE,
|
||||
normalizeAiEnv,
|
||||
type AiProvider,
|
||||
type AiApiStyle,
|
||||
type AiAuthMethod,
|
||||
@@ -112,6 +113,12 @@ interface TableColumnTemplateGridRow {
|
||||
overrides: TableColumnTemplateOverrideRow[];
|
||||
}
|
||||
|
||||
interface AiEnvRow {
|
||||
id: string;
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
function tableColumnTemplateRowsFromSettings(lines: readonly string[]): TableColumnTemplateGridRow[] {
|
||||
return parseTableColumnTemplateFields([...lines]).map((field) => ({
|
||||
id: uuid(),
|
||||
@@ -1477,6 +1484,7 @@ const aiEditEnableThinking = ref(settingsStore.aiConfig.enableThinking ?? true);
|
||||
const aiEditReasoningLevel = ref<AiReasoningLevel>(settingsStore.aiConfig.reasoningLevel || "default");
|
||||
const aiEditContextWindow = ref<number | undefined>(settingsStore.aiConfig.contextWindow);
|
||||
const aiEditCodexCliPath = ref(settingsStore.aiConfig.codexCliPath || "");
|
||||
const aiEditCodexCliEnvRows = ref<AiEnvRow[]>(aiEnvRowsFromConfig(settingsStore.aiConfig.codexCliEnv));
|
||||
|
||||
const aiModelOptions = ref<AiModelInfo[]>([]);
|
||||
const aiModelLoading = ref(false);
|
||||
@@ -1541,6 +1549,52 @@ const aiModelEmptyText = computed(() => {
|
||||
if (!aiModelListSupported.value) return t("ai.modelListUnsupported");
|
||||
return t("ai.noModels");
|
||||
});
|
||||
const aiCodexEnvError = computed(() => codexEnvValidationError());
|
||||
const aiCodexPathError = computed(() => {
|
||||
const path = aiEditCodexCliPath.value.trim();
|
||||
const firstToken = path.split(/\s+/)[0] || "";
|
||||
return /^[A-Za-z_][A-Za-z0-9_]*=/.test(firstToken) ? t("ai.codexCliPathEnvError") : "";
|
||||
});
|
||||
const aiCodexValidationError = computed(() => (aiIsCodexCli.value ? aiCodexPathError.value || aiCodexEnvError.value : ""));
|
||||
|
||||
function aiEnvRowsFromConfig(env: unknown): AiEnvRow[] {
|
||||
return Object.entries(normalizeAiEnv(env)).map(([key, value]) => ({ id: uuid(), key, value }));
|
||||
}
|
||||
|
||||
function codexEnvFromRows(): Record<string, string> {
|
||||
const result: Record<string, string> = {};
|
||||
for (const row of aiEditCodexCliEnvRows.value) {
|
||||
const key = row.key.trim();
|
||||
if (!key || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key) || key.toUpperCase().startsWith("DBX_MCP_")) continue;
|
||||
result[key] = row.value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function codexEnvSignature(): string {
|
||||
return JSON.stringify(Object.entries(codexEnvFromRows()).sort(([left], [right]) => left.localeCompare(right)));
|
||||
}
|
||||
|
||||
function savedCodexEnvSignature(): string {
|
||||
return JSON.stringify(Object.entries(normalizeAiEnv(settingsStore.aiConfig.codexCliEnv)).sort(([left], [right]) => left.localeCompare(right)));
|
||||
}
|
||||
|
||||
function codexEnvValidationError(): string {
|
||||
for (const row of aiEditCodexCliEnvRows.value) {
|
||||
const key = row.key.trim();
|
||||
if (key && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return t("ai.codexCliEnvInvalidName", { name: key });
|
||||
if (key.toUpperCase().startsWith("DBX_MCP_")) return t("ai.codexCliEnvReservedName", { name: key });
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function addCodexEnvRow() {
|
||||
aiEditCodexCliEnvRows.value.push({ id: uuid(), key: "", value: "" });
|
||||
}
|
||||
|
||||
function removeCodexEnvRow(id: string) {
|
||||
aiEditCodexCliEnvRows.value = aiEditCodexCliEnvRows.value.filter((row) => row.id !== id);
|
||||
}
|
||||
|
||||
function clearAiModelOptions() {
|
||||
aiModelRequestToken += 1;
|
||||
@@ -1559,6 +1613,7 @@ function aiModelConfigSignature() {
|
||||
proxyEnabled: aiEditProxyEnabled.value,
|
||||
proxyUrl: aiEditProxyUrl.value.trim(),
|
||||
codexCliPath: aiEditCodexCliPath.value.trim(),
|
||||
codexCliEnv: codexEnvSignature(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1576,6 +1631,7 @@ function currentAiEditConfig() {
|
||||
reasoningLevel: aiEditReasoningLevel.value,
|
||||
contextWindow: aiEditContextWindow.value || undefined,
|
||||
codexCliPath: aiEditCodexCliPath.value.trim() || undefined,
|
||||
codexCliEnv: aiIsCodexCli.value ? codexEnvFromRows() : {},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1661,6 +1717,7 @@ function syncAiEditState() {
|
||||
aiEditReasoningLevel.value = settingsStore.aiConfig.reasoningLevel || "default";
|
||||
aiEditContextWindow.value = settingsStore.aiConfig.contextWindow;
|
||||
aiEditCodexCliPath.value = settingsStore.aiConfig.codexCliPath || "";
|
||||
aiEditCodexCliEnvRows.value = aiEnvRowsFromConfig(settingsStore.aiConfig.codexCliEnv);
|
||||
aiTestResult.value = "";
|
||||
aiTestError.value = "";
|
||||
aiTestLatency.value = null;
|
||||
@@ -1678,6 +1735,7 @@ function aiSelectProvider(provider: AiProvider) {
|
||||
aiEditReasoningLevel.value = "default";
|
||||
if (!AI_PROVIDER_PRESETS[provider].requiresApiKey) aiEditApiKey.value = "";
|
||||
aiEditCodexCliPath.value = "";
|
||||
aiEditCodexCliEnvRows.value = [];
|
||||
aiTestResult.value = "";
|
||||
aiTestError.value = "";
|
||||
aiTestLatency.value = null;
|
||||
@@ -1699,16 +1757,27 @@ function aiHasChanges(): boolean {
|
||||
aiEditEnableThinking.value !== (settingsStore.aiConfig.enableThinking ?? true) ||
|
||||
aiEditReasoningLevel.value !== (settingsStore.aiConfig.reasoningLevel || "default") ||
|
||||
aiEditContextWindow.value !== settingsStore.aiConfig.contextWindow ||
|
||||
aiEditCodexCliPath.value !== (settingsStore.aiConfig.codexCliPath || "")
|
||||
aiEditCodexCliPath.value !== (settingsStore.aiConfig.codexCliPath || "") ||
|
||||
codexEnvSignature() !== savedCodexEnvSignature()
|
||||
);
|
||||
}
|
||||
|
||||
function aiApplySettings() {
|
||||
if (aiCodexValidationError.value) {
|
||||
aiTestResult.value = "error";
|
||||
aiTestError.value = aiCodexValidationError.value;
|
||||
return;
|
||||
}
|
||||
settingsStore.updateAiConfig(currentAiEditConfig());
|
||||
}
|
||||
|
||||
async function aiTestConn() {
|
||||
if ((aiRequiresApiKey.value && !aiEditApiKey.value.trim()) || (!aiIsCodexCli.value && !aiEditEndpoint.value.trim()) || (!aiIsCodexCli.value && !aiEditModel.value.trim())) return;
|
||||
if (aiCodexValidationError.value) {
|
||||
aiTestResult.value = "error";
|
||||
aiTestError.value = aiCodexValidationError.value;
|
||||
return;
|
||||
}
|
||||
aiTesting.value = true;
|
||||
aiTestResult.value = "";
|
||||
aiTestError.value = "";
|
||||
@@ -2975,6 +3044,28 @@ watch(
|
||||
<div class="col-span-2 space-y-1.5">
|
||||
<Input v-model="aiEditCodexCliPath" autocomplete="off" class="h-8 text-xs" placeholder="codex" />
|
||||
<p class="text-[11px] text-muted-foreground">{{ t("ai.codexCliPathHint") }}</p>
|
||||
<p v-if="aiCodexPathError" class="text-[11px] text-destructive">{{ aiCodexPathError }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="aiIsCodexCli" class="grid grid-cols-3 items-start gap-3">
|
||||
<Label class="pt-2 text-right text-xs">{{ t("ai.codexCliEnv") }}</Label>
|
||||
<div class="col-span-2 space-y-2">
|
||||
<div class="space-y-1.5">
|
||||
<div v-for="row in aiEditCodexCliEnvRows" :key="row.id" class="grid grid-cols-[minmax(0,0.9fr)_minmax(0,1.3fr)_2rem] gap-2">
|
||||
<Input v-model="row.key" autocomplete="off" class="h-8 font-mono text-xs" :placeholder="t('ai.codexCliEnvKeyPlaceholder')" />
|
||||
<Input v-model="row.value" autocomplete="off" class="h-8 font-mono text-xs" :placeholder="t('ai.codexCliEnvValuePlaceholder')" />
|
||||
<Button type="button" variant="ghost" size="icon" class="h-8 w-8" :title="t('common.remove')" :aria-label="t('common.remove')" @click="removeCodexEnvRow(row.id)">
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button type="button" variant="outline" size="sm" class="h-7 px-2 text-xs" @click="addCodexEnvRow">
|
||||
<Plus class="mr-1 h-3.5 w-3.5" />
|
||||
{{ t("ai.codexCliEnvAdd") }}
|
||||
</Button>
|
||||
<p v-if="aiCodexEnvError" class="text-[11px] text-destructive">{{ aiCodexEnvError }}</p>
|
||||
<p v-else class="text-[11px] text-muted-foreground">{{ t("ai.codexCliEnvHint") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3344,7 +3435,7 @@ watch(
|
||||
|
||||
<DialogFooter v-else-if="activeSettingsTab === 'ai'" class="mx-0 mb-0 flex-row flex-wrap items-center justify-end gap-2 rounded-none border-t border-border/60 bg-transparent px-0 pb-0 pt-3 sm:flex-row sm:gap-2 [&>button]:w-auto [&>button]:shrink-0">
|
||||
<div class="flex flex-1 items-center gap-2">
|
||||
<Button size="sm" variant="outline" :disabled="aiTesting || (aiRequiresApiKey && !aiEditApiKey?.trim()) || (!aiIsCodexCli && !aiEditEndpoint?.trim()) || (!aiIsCodexCli && !aiEditModel?.trim())" @click="aiTestConn">
|
||||
<Button size="sm" variant="outline" :disabled="aiTesting || !!aiCodexValidationError || (aiRequiresApiKey && !aiEditApiKey?.trim()) || (!aiIsCodexCli && !aiEditEndpoint?.trim()) || (!aiIsCodexCli && !aiEditModel?.trim())" @click="aiTestConn">
|
||||
<Loader2 v-if="aiTesting" class="h-3 w-3 animate-spin mr-1" />
|
||||
{{ t("connection.test") }}
|
||||
</Button>
|
||||
@@ -3369,7 +3460,7 @@ watch(
|
||||
</span>
|
||||
</div>
|
||||
<Button variant="outline" @click="emit('update:open', false)">{{ t("common.close") }}</Button>
|
||||
<Button :disabled="!aiHasChanges()" @click="aiApplySettings">{{ t("settings.apply") }}</Button>
|
||||
<Button :disabled="!aiHasChanges() || !!aiCodexValidationError" @click="aiApplySettings">{{ t("settings.apply") }}</Button>
|
||||
</DialogFooter>
|
||||
|
||||
<DialogFooter v-else-if="activeSettingsTab === 'sync'" class="mx-0 mb-0 flex-row flex-wrap items-center justify-end gap-2 rounded-none border-t border-border/60 bg-transparent px-0 pb-0 pt-3 sm:flex-row sm:gap-2 [&>button]:w-auto [&>button]:shrink-0">
|
||||
|
||||
Reference in new issue
Block a user