fix: avoid opencode structured output session breakage
Replace server-enforced structured output with local JSON parsing for Git generation Render generated commit and PR JSON responses as compact chat cards Tighten generation prompts while preserving active session context
This commit is contained in:
@@ -33,7 +33,7 @@
|
||||
"@ibm/plex": "^6.4.1",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@opencode-ai/sdk": "^1.4.48",
|
||||
"@opencode-ai/sdk": "^1.4.49",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
@@ -151,7 +151,7 @@
|
||||
"@fontsource/ibm-plex-sans": "^5.1.1",
|
||||
"@ibm/plex": "^6.4.1",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@opencode-ai/sdk": "^1.4.48",
|
||||
"@opencode-ai/sdk": "^1.4.49",
|
||||
"@pierre/diffs": "1.1.0-beta.13",
|
||||
"@simplewebauthn/browser": "13.3.0",
|
||||
"@tanstack/react-virtual": "^3.13.18",
|
||||
@@ -222,7 +222,7 @@
|
||||
"version": "1.10.4",
|
||||
"dependencies": {
|
||||
"@openchamber/ui": "workspace:*",
|
||||
"@opencode-ai/sdk": "^1.4.48",
|
||||
"@opencode-ai/sdk": "^1.4.49",
|
||||
"adm-zip": "^0.5.16",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"react": "^19.1.1",
|
||||
@@ -249,7 +249,7 @@
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^1.1.0",
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@opencode-ai/sdk": "^1.4.48",
|
||||
"@opencode-ai/sdk": "^1.4.49",
|
||||
"@simplewebauthn/server": "13.3.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"better-sqlite3": "^11.7.0",
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@
|
||||
"@ibm/plex": "^6.4.1",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@opencode-ai/sdk": "^1.4.48",
|
||||
"@opencode-ai/sdk": "^1.4.49",
|
||||
"@radix-ui/react-collapsible": "^1.1.12",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"@fontsource/ibm-plex-sans": "^5.1.1",
|
||||
"@ibm/plex": "^6.4.1",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@opencode-ai/sdk": "^1.4.48",
|
||||
"@opencode-ai/sdk": "^1.4.49",
|
||||
"@pierre/diffs": "1.1.0-beta.13",
|
||||
"@base-ui/react": "^1.4.0",
|
||||
"@simplewebauthn/browser": "13.3.0",
|
||||
|
||||
@@ -6,6 +6,8 @@ import type { ContentChangeReason } from '@/hooks/useChatAutoFollow';
|
||||
import { useStreamingTextThrottle } from '../../hooks/useStreamingTextThrottle';
|
||||
import { resolveAssistantDisplayText, shouldRenderAssistantText } from './assistantTextVisibility';
|
||||
import { streamPerfCount, streamPerfObserve } from '@/stores/utils/streamDebug';
|
||||
import { GeneratedJsonResultCard } from './GeneratedJsonResultCard';
|
||||
import { parseGeneratedJsonResult } from './generatedJsonResult';
|
||||
|
||||
type PartWithText = Part & { text?: string; content?: string; value?: string; time?: { start?: number; end?: number } };
|
||||
|
||||
@@ -71,6 +73,18 @@ const AssistantTextPart: React.FC<AssistantTextPartProps> = ({
|
||||
return null;
|
||||
}
|
||||
|
||||
const generatedResult = !isStreaming && isFinalized ? parseGeneratedJsonResult(displayTextContent) : null;
|
||||
if (generatedResult) {
|
||||
return (
|
||||
<div
|
||||
className={`group/assistant-text relative break-words ${chatRenderMode === 'live' ? 'my-1' : ''}`}
|
||||
key={part.id || `${messageId}-text`}
|
||||
>
|
||||
<GeneratedJsonResultCard result={generatedResult} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`group/assistant-text relative break-words ${chatRenderMode === 'live' ? 'my-1' : ''}`}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Icon } from '@/components/icon/Icon';
|
||||
import { copyTextToClipboard } from '@/lib/clipboard';
|
||||
import { useI18n } from '@/lib/i18n';
|
||||
import type { GeneratedResult } from './generatedJsonResult';
|
||||
|
||||
export const GeneratedJsonResultCard: React.FC<{ result: GeneratedResult }> = ({ result }) => {
|
||||
const { t } = useI18n();
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
|
||||
const copyText = React.useMemo(() => {
|
||||
if (result.kind === 'commit') {
|
||||
return [result.subject, ...result.highlights.map((highlight) => `- ${highlight}`)].join('\n');
|
||||
}
|
||||
return [result.title, result.body].filter(Boolean).join('\n\n');
|
||||
}, [result]);
|
||||
|
||||
const handleCopy = React.useCallback(async () => {
|
||||
const copyResult = await copyTextToClipboard(copyText || result.raw);
|
||||
if (!copyResult.ok) return;
|
||||
setCopied(true);
|
||||
window.setTimeout(() => setCopied(false), 2000);
|
||||
}, [copyText, result.raw]);
|
||||
|
||||
return (
|
||||
<div data-component="generated-json-result" className="my-4 group overflow-hidden rounded-2xl border border-border/80 bg-[var(--surface-elevated)]">
|
||||
<div className="flex items-center justify-between border-b border-border/70 px-3 py-1.5">
|
||||
<span className="font-mono text-[13px] text-muted-foreground">
|
||||
{result.kind === 'commit'
|
||||
? t('chat.generatedResult.commit.title')
|
||||
: t('chat.generatedResult.pullRequest.title')}
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
onClick={() => { void handleCopy(); }}
|
||||
title={copied ? t('chat.generatedResult.actions.copied') : t('chat.generatedResult.actions.copy')}
|
||||
aria-label={copied ? t('chat.generatedResult.actions.copied') : t('chat.generatedResult.actions.copy')}
|
||||
className="text-muted-foreground hover:text-foreground md:opacity-0 md:group-hover:opacity-100"
|
||||
>
|
||||
{copied ? <Icon name="check" className="size-3.5" /> : <Icon name="file-copy" className="size-3.5" />}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-3 px-3 py-3">
|
||||
{result.kind === 'commit' ? (
|
||||
<>
|
||||
<div className="typography-ui-label text-foreground">{result.subject}</div>
|
||||
{result.highlights.length > 0 ? (
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
{result.highlights.map((highlight, index) => (
|
||||
<li key={`${index}-${highlight}`} className="flex gap-2">
|
||||
<span className="text-muted-foreground/70">-</span>
|
||||
<span>{highlight}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{result.title ? (
|
||||
<div>
|
||||
<div className="typography-micro uppercase tracking-[0.12em] text-muted-foreground">{t('chat.generatedResult.pullRequest.titleLabel')}</div>
|
||||
<div className="mt-1 typography-ui-label text-foreground">{result.title}</div>
|
||||
</div>
|
||||
) : null}
|
||||
{result.body ? (
|
||||
<div>
|
||||
<div className="typography-micro uppercase tracking-[0.12em] text-muted-foreground">{t('chat.generatedResult.pullRequest.bodyLabel')}</div>
|
||||
<pre className="mt-1 whitespace-pre-wrap break-words text-sm leading-relaxed text-muted-foreground font-sans">{result.body}</pre>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
export type GeneratedCommitResult = {
|
||||
kind: 'commit';
|
||||
subject: string;
|
||||
highlights: string[];
|
||||
raw: string;
|
||||
};
|
||||
|
||||
export type GeneratedPrResult = {
|
||||
kind: 'pr';
|
||||
title: string;
|
||||
body: string;
|
||||
raw: string;
|
||||
};
|
||||
|
||||
export type GeneratedResult = GeneratedCommitResult | GeneratedPrResult;
|
||||
|
||||
const parseJsonObjects = (value: string): Record<string, unknown>[] => {
|
||||
const text = value.trim();
|
||||
const candidates = new Set<string>();
|
||||
|
||||
const fencedMatches = text.matchAll(/```(?:json)?\s*([\s\S]*?)```/gi);
|
||||
for (const match of fencedMatches) {
|
||||
if (match[1]) candidates.add(match[1].trim());
|
||||
}
|
||||
|
||||
const firstObjectStart = text.indexOf('{');
|
||||
if (firstObjectStart >= 0) {
|
||||
for (let end = text.length; end > firstObjectStart; end -= 1) {
|
||||
if (text[end - 1] === '}') {
|
||||
candidates.add(text.slice(firstObjectStart, end));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parsed: Record<string, unknown>[] = [];
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
const item = JSON.parse(candidate) as unknown;
|
||||
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
||||
parsed.push(item as Record<string, unknown>);
|
||||
}
|
||||
} catch {
|
||||
// Ignore non-JSON prose; the model may include markdown before the object.
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
export const parseGeneratedJsonResult = (value: string): GeneratedResult | null => {
|
||||
for (const item of parseJsonObjects(value)) {
|
||||
const subject = typeof item.subject === 'string' ? item.subject.trim() : '';
|
||||
const highlights = Array.isArray(item.highlights)
|
||||
? item.highlights.filter((entry) => typeof entry === 'string').map((entry) => entry.trim()).filter(Boolean).slice(0, 3)
|
||||
: [];
|
||||
if (subject) {
|
||||
return { kind: 'commit', subject, highlights, raw: JSON.stringify({ subject, highlights }, null, 2) };
|
||||
}
|
||||
|
||||
const title = typeof item.title === 'string' ? item.title.trim() : '';
|
||||
const body = typeof item.body === 'string' ? item.body.trim() : '';
|
||||
if (title || body) {
|
||||
return { kind: 'pr', title, body, raw: JSON.stringify({ title, body }, null, 2) };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -58,6 +58,46 @@ const requestChatForceScrollBottom = (sessionId: string) => {
|
||||
}));
|
||||
};
|
||||
|
||||
const extractJsonObject = (value: string): Record<string, unknown> | null => {
|
||||
const text = value.trim();
|
||||
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
||||
const candidate = (fenced?.[1] ?? text).trim();
|
||||
const starts = [candidate.indexOf('{')].filter((index) => index >= 0);
|
||||
|
||||
for (const start of starts) {
|
||||
for (let end = candidate.length; end > start; end -= 1) {
|
||||
if (candidate[end - 1] !== '}') continue;
|
||||
try {
|
||||
const parsed = JSON.parse(candidate.slice(start, end)) as unknown;
|
||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
||||
return parsed as Record<string, unknown>;
|
||||
}
|
||||
} catch {
|
||||
// Keep scanning; models sometimes wrap JSON with prose or fences.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const extractAssistantText = (response: unknown): string => {
|
||||
const data = (response as { data?: { parts?: Array<unknown> } } | null)?.data;
|
||||
const parts = Array.isArray(data?.parts) ? data.parts : [];
|
||||
return parts
|
||||
.map((part) => {
|
||||
const item = part as { type?: unknown; text?: unknown; content?: unknown; value?: unknown };
|
||||
if (item.type !== 'text') return '';
|
||||
if (typeof item.text === 'string') return item.text;
|
||||
if (typeof item.content === 'string') return item.content;
|
||||
if (typeof item.value === 'string') return item.value;
|
||||
return '';
|
||||
})
|
||||
.filter((text) => text.trim().length > 0)
|
||||
.join('\n')
|
||||
.trim();
|
||||
};
|
||||
|
||||
export async function checkIsGitRepository(directory: string): Promise<boolean> {
|
||||
const runtime = getRuntimeGit();
|
||||
if (runtime) return runtime.checkIsGitRepository(directory);
|
||||
@@ -150,20 +190,6 @@ export async function generateCommitMessage(
|
||||
visiblePrompt,
|
||||
hiddenPrompt,
|
||||
generationSession,
|
||||
schema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
subject: { type: 'string', description: 'Conventional commit subject line.' },
|
||||
highlights: {
|
||||
type: 'array',
|
||||
items: { type: 'string', description: 'Short user-facing highlight.' },
|
||||
maxItems: 3,
|
||||
description: 'Optional short user-facing highlights.',
|
||||
},
|
||||
},
|
||||
required: ['subject', 'highlights'],
|
||||
},
|
||||
kind: 'commit',
|
||||
});
|
||||
|
||||
@@ -271,15 +297,6 @@ export async function generatePullRequestDescription(
|
||||
visiblePrompt,
|
||||
hiddenPrompt,
|
||||
generationSession,
|
||||
schema: {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
title: { type: 'string', description: 'Pull request title.' },
|
||||
body: { type: 'string', description: 'Pull request markdown description.' },
|
||||
},
|
||||
required: ['title', 'body'],
|
||||
},
|
||||
kind: 'pr',
|
||||
});
|
||||
|
||||
@@ -347,14 +364,12 @@ const runStructuredGenerationInActiveSession = async ({
|
||||
visiblePrompt,
|
||||
hiddenPrompt,
|
||||
generationSession,
|
||||
schema,
|
||||
kind,
|
||||
}: {
|
||||
directory: string;
|
||||
visiblePrompt: string;
|
||||
hiddenPrompt?: string;
|
||||
generationSession: SessionGenerationContext;
|
||||
schema: Record<string, unknown>;
|
||||
kind: 'commit' | 'pr';
|
||||
}): Promise<Record<string, unknown>> => {
|
||||
const requestStartedAt = Date.now();
|
||||
@@ -371,7 +386,11 @@ const runStructuredGenerationInActiveSession = async ({
|
||||
const hiddenPromptText = typeof hiddenPrompt === 'string' ? hiddenPrompt.trim() : '';
|
||||
const promptParts: Array<{ type: 'text'; text: string; synthetic?: boolean }> = [];
|
||||
if (visiblePromptText) {
|
||||
promptParts.push({ type: 'text', text: visiblePromptText, synthetic: false });
|
||||
promptParts.push({
|
||||
type: 'text',
|
||||
text: hiddenPromptText ? `${visiblePromptText}\n\n` : visiblePromptText,
|
||||
synthetic: false,
|
||||
});
|
||||
}
|
||||
if (hiddenPromptText) {
|
||||
promptParts.push({ type: 'text', text: hiddenPromptText, synthetic: true });
|
||||
@@ -391,11 +410,6 @@ const runStructuredGenerationInActiveSession = async ({
|
||||
modelID: generationSession.modelID,
|
||||
},
|
||||
...(generationSession.agent ? { agent: generationSession.agent } : {}),
|
||||
format: {
|
||||
type: 'json_schema',
|
||||
schema,
|
||||
retryCount: 2,
|
||||
},
|
||||
parts: promptParts,
|
||||
});
|
||||
});
|
||||
@@ -405,21 +419,23 @@ const runStructuredGenerationInActiveSession = async ({
|
||||
throw new Error(responseError?.message || `Failed to generate ${kind} output`);
|
||||
}
|
||||
|
||||
const info = response.data.info as { finish?: string; structured_output?: unknown; structured?: unknown; error?: unknown };
|
||||
const structuredOutput = info?.structured_output || info?.structured;
|
||||
if (!structuredOutput || typeof structuredOutput !== 'object' || Array.isArray(structuredOutput)) {
|
||||
console.error('[git-generation][browser] invalid structured output', {
|
||||
const info = response.data.info as { finish?: string; error?: unknown };
|
||||
const assistantText = extractAssistantText(response);
|
||||
const parsedOutput = extractJsonObject(assistantText);
|
||||
if (!parsedOutput) {
|
||||
console.error('[git-generation][browser] invalid JSON output', {
|
||||
kind,
|
||||
sessionId: generationSession.sessionId,
|
||||
elapsedMs: Date.now() - requestStartedAt,
|
||||
finish: info?.finish,
|
||||
assistantText,
|
||||
messageInfo: response.data.info,
|
||||
messageParts: response.data.parts,
|
||||
});
|
||||
throw new Error('No structured output returned by session');
|
||||
throw new Error('No JSON output returned by session');
|
||||
}
|
||||
|
||||
return structuredOutput as Record<string, unknown>;
|
||||
return parsedOutput;
|
||||
};
|
||||
|
||||
export async function listGitWorktrees(directory: string): Promise<import('./api/types').GitWorktreeInfo[]> {
|
||||
|
||||
@@ -1453,6 +1453,13 @@ export const dict = {
|
||||
'chat.messageBody.actions.saveAsPlan': 'Save as plan',
|
||||
'chat.messageBody.actions.startNewSession': 'Start new session from this answer',
|
||||
'chat.messageBody.actions.startNewMultiRun': 'Start new multi-run from this answer',
|
||||
'chat.generatedResult.actions.copy': 'Copy',
|
||||
'chat.generatedResult.actions.copied': 'Copied',
|
||||
'chat.generatedResult.commit.title': 'Generated commit message',
|
||||
'chat.generatedResult.commit.highlights': 'Highlights',
|
||||
'chat.generatedResult.pullRequest.title': 'Generated pull request',
|
||||
'chat.generatedResult.pullRequest.titleLabel': 'Title',
|
||||
'chat.generatedResult.pullRequest.bodyLabel': 'Body',
|
||||
'chat.messageBody.tts.stopSpeaking': 'Stop speaking',
|
||||
'chat.messageBody.tts.readAloud': 'Read aloud',
|
||||
'chat.messageBody.tts.readAloudWithProvider': 'Read aloud ({provider} voice)',
|
||||
|
||||
@@ -1419,6 +1419,13 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.messageBody.actions.saveAsPlan": "Guardar como plan",
|
||||
"chat.messageBody.actions.startNewSession": "Iniciar nueva sesión desde esta respuesta",
|
||||
"chat.messageBody.actions.startNewMultiRun": "Iniciar nuevo multi-run desde esta respuesta",
|
||||
"chat.generatedResult.actions.copy": "Copiar",
|
||||
"chat.generatedResult.actions.copied": "Copiado",
|
||||
"chat.generatedResult.commit.title": "Mensaje de commit generado",
|
||||
"chat.generatedResult.commit.highlights": "Puntos clave",
|
||||
"chat.generatedResult.pullRequest.title": "Pull request generado",
|
||||
"chat.generatedResult.pullRequest.titleLabel": "Título",
|
||||
"chat.generatedResult.pullRequest.bodyLabel": "Descripción",
|
||||
"chat.messageBody.tts.stopSpeaking": "Dejar de hablar",
|
||||
"chat.messageBody.tts.readAloud": "Leer en voz alta",
|
||||
"chat.messageBody.tts.readAloudWithProvider": "Leer en voz alta ({provider} voz)",
|
||||
|
||||
@@ -1453,6 +1453,13 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.messageBody.actions.saveAsPlan': '플랜으로 저장',
|
||||
'chat.messageBody.actions.startNewSession': '이 응답에서 새 세션 시작',
|
||||
'chat.messageBody.actions.startNewMultiRun': '이 응답에서 새 멀티런 시작',
|
||||
'chat.generatedResult.actions.copy': '복사',
|
||||
'chat.generatedResult.actions.copied': '복사됨',
|
||||
'chat.generatedResult.commit.title': '생성된 커밋 메시지',
|
||||
'chat.generatedResult.commit.highlights': '하이라이트',
|
||||
'chat.generatedResult.pullRequest.title': '생성된 Pull Request',
|
||||
'chat.generatedResult.pullRequest.titleLabel': '제목',
|
||||
'chat.generatedResult.pullRequest.bodyLabel': '본문',
|
||||
'chat.messageBody.tts.stopSpeaking': '읽기 중지',
|
||||
'chat.messageBody.tts.readAloud': '소리 내어 읽기',
|
||||
'chat.messageBody.tts.readAloudWithProvider': '소리 내어 읽기({provider} 음성)',
|
||||
|
||||
@@ -516,6 +516,13 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.messageBody.actions.saveAsPlan': 'Zapisz jako plan',
|
||||
'chat.messageBody.actions.startNewSession': 'Rozpocznij nową sesję z tej odpowiedzi',
|
||||
'chat.messageBody.actions.startNewMultiRun': 'Rozpocznij nowe wielokrotne uruchomienie z tej odpowiedzi',
|
||||
'chat.generatedResult.actions.copy': 'Kopiuj',
|
||||
'chat.generatedResult.actions.copied': 'Skopiowano',
|
||||
'chat.generatedResult.commit.title': 'Wygenerowana wiadomość commita',
|
||||
'chat.generatedResult.commit.highlights': 'Najważniejsze',
|
||||
'chat.generatedResult.pullRequest.title': 'Wygenerowany pull request',
|
||||
'chat.generatedResult.pullRequest.titleLabel': 'Tytuł',
|
||||
'chat.generatedResult.pullRequest.bodyLabel': 'Opis',
|
||||
'chat.messageBody.tts.stopSpeaking': 'Przestań mówić',
|
||||
'chat.messageBody.tts.readAloud': 'Czytaj na głos',
|
||||
'chat.messageBody.tts.readAloudWithProvider': 'Czytaj na głos ({provider} voice)',
|
||||
|
||||
@@ -1419,6 +1419,13 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.messageBody.actions.saveAsPlan": "Salvar como plano",
|
||||
"chat.messageBody.actions.startNewSession": "Iniciar nova sessão a partir desta resposta",
|
||||
"chat.messageBody.actions.startNewMultiRun": "Iniciar novo multi-run a partir desta resposta",
|
||||
"chat.generatedResult.actions.copy": "Copiar",
|
||||
"chat.generatedResult.actions.copied": "Copiado",
|
||||
"chat.generatedResult.commit.title": "Mensagem de commit gerada",
|
||||
"chat.generatedResult.commit.highlights": "Destaques",
|
||||
"chat.generatedResult.pullRequest.title": "Pull request gerado",
|
||||
"chat.generatedResult.pullRequest.titleLabel": "Título",
|
||||
"chat.generatedResult.pullRequest.bodyLabel": "Descrição",
|
||||
"chat.messageBody.tts.stopSpeaking": "Parar de falar",
|
||||
"chat.messageBody.tts.readAloud": "Ler em voz alta",
|
||||
"chat.messageBody.tts.readAloudWithProvider": "Ler em voz alta (voz {provider})",
|
||||
|
||||
@@ -1419,6 +1419,13 @@ export const dict: Record<I18nKey, string> = {
|
||||
"chat.messageBody.actions.saveAsPlan": "Зберегти як план",
|
||||
"chat.messageBody.actions.startNewSession": "Почати нову сесію із цієї відповіді",
|
||||
"chat.messageBody.actions.startNewMultiRun": "Почніть новий Multi-run із цієї відповіді",
|
||||
"chat.generatedResult.actions.copy": "Копіювати",
|
||||
"chat.generatedResult.actions.copied": "Скопійовано",
|
||||
"chat.generatedResult.commit.title": "Згенероване повідомлення коміту",
|
||||
"chat.generatedResult.commit.highlights": "Основне",
|
||||
"chat.generatedResult.pullRequest.title": "Згенерований pull request",
|
||||
"chat.generatedResult.pullRequest.titleLabel": "Заголовок",
|
||||
"chat.generatedResult.pullRequest.bodyLabel": "Опис",
|
||||
"chat.messageBody.tts.stopSpeaking": "Зупинити озвучення",
|
||||
"chat.messageBody.tts.readAloud": "Прочитати вголос",
|
||||
"chat.messageBody.tts.readAloudWithProvider": "Прочитати вголос (голос {provider})",
|
||||
|
||||
@@ -1419,6 +1419,13 @@ export const dict: Record<I18nKey, string> = {
|
||||
'chat.messageBody.actions.saveAsPlan': '保存为计划',
|
||||
'chat.messageBody.actions.startNewSession': '基于此回答开始新会话',
|
||||
'chat.messageBody.actions.startNewMultiRun': '基于此回答开始新的多运行',
|
||||
'chat.generatedResult.actions.copy': '复制',
|
||||
'chat.generatedResult.actions.copied': '已复制',
|
||||
'chat.generatedResult.commit.title': '生成的提交消息',
|
||||
'chat.generatedResult.commit.highlights': '要点',
|
||||
'chat.generatedResult.pullRequest.title': '生成的 Pull Request',
|
||||
'chat.generatedResult.pullRequest.titleLabel': '标题',
|
||||
'chat.generatedResult.pullRequest.bodyLabel': '正文',
|
||||
'chat.messageBody.tts.stopSpeaking': '停止朗读',
|
||||
'chat.messageBody.tts.readAloud': '朗读',
|
||||
'chat.messageBody.tts.readAloudWithProvider': '朗读({provider} 语音)',
|
||||
|
||||
@@ -60,7 +60,9 @@ export const MAGIC_PROMPT_DEFINITIONS: readonly MagicPromptDefinition[] = [
|
||||
placeholders: [
|
||||
{ key: 'selected_files', description: 'Bullet list of currently selected file paths.' },
|
||||
],
|
||||
template: `Return JSON with exactly this shape:
|
||||
template: `Return exactly one JSON object and nothing else. Do not include prose, markdown, explanations, or code fences.
|
||||
|
||||
The JSON object must have exactly this shape:
|
||||
{"subject": string, "highlights": string[]}
|
||||
|
||||
Rules:
|
||||
@@ -69,6 +71,8 @@ Rules:
|
||||
- no scope in subject
|
||||
- keep subject concise and user-facing
|
||||
- highlights: 0-3 concise user-facing points
|
||||
- use double quotes for all JSON strings
|
||||
- do not include trailing commas or comments
|
||||
|
||||
Selected files:
|
||||
{{selected_files}}`,
|
||||
@@ -92,13 +96,18 @@ Selected files:
|
||||
{ key: 'changed_files', description: 'Bullet list of changed files in base...head.' },
|
||||
{ key: 'additional_context_block', description: 'Optional Additional context block (already formatted).' },
|
||||
],
|
||||
template: `Return JSON with exactly this shape:
|
||||
template: `Return exactly one JSON object and nothing else. Do not include prose, markdown outside JSON, explanations, or code fences.
|
||||
|
||||
The JSON object must have exactly this shape:
|
||||
{"title": string, "body": string}
|
||||
|
||||
Rules:
|
||||
- title: concise, outcome-first, conventional style
|
||||
- body: markdown with sections: ## Summary, ## Why, ## Testing
|
||||
- keep output concrete and user-facing
|
||||
- put all markdown inside the body string
|
||||
- use double quotes for all JSON strings and escape newlines as \\n
|
||||
- do not include trailing commas or comments
|
||||
|
||||
Base branch: {{base_branch}}
|
||||
Head branch: {{head_branch}}
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@openchamber/ui": "workspace:*",
|
||||
"@opencode-ai/sdk": "^1.4.48",
|
||||
"@opencode-ai/sdk": "^1.4.49",
|
||||
"adm-zip": "^0.5.16",
|
||||
"jsonc-parser": "^3.3.1",
|
||||
"react": "^19.1.1",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"dependencies": {
|
||||
"@clack/prompts": "^1.1.0",
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@opencode-ai/sdk": "^1.4.48",
|
||||
"@opencode-ai/sdk": "^1.4.49",
|
||||
"@simplewebauthn/server": "13.3.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"better-sqlite3": "^11.7.0",
|
||||
|
||||
Reference in New Issue
Block a user