Merge pull request #35 from huangzhenhao90/fix/opencode-go-session-header

fix(ai): 修复 OpenCode Go 缺少会话 header 导致日报生成失败
This commit is contained in:
qingmao
2026-09-14 10:05:18 +08:00
committed by GitHub
5 changed files with 192 additions and 7 deletions
@@ -1,3 +1,4 @@
import { randomUUID } from 'crypto'
import type { Contact, Message } from '../../shared/types'
import type { GroupDailyReport, GroupReportMetadata } from '../../shared/group-report'
import { exportGroupReport } from '../group-report-service'
@@ -120,12 +121,13 @@ export async function generateAgentGroupReport(
const input = await buildGroupReportInput(messages, contact as Contact, true, 'full')
const runtime = aiProvider.getRuntimeConfig()
const sessionId = randomUUID()
const ai = await aiProvider.chat(
[
{ role: 'system', content: GROUP_REPORT_SYSTEM_PROMPT },
{ role: 'user', content: input.prompt }
],
{ timeoutMs: Math.max(30, Math.min(1800, request.timeoutSeconds || 300)) * 1000 }
{ sessionId, timeoutMs: Math.max(30, Math.min(1800, request.timeoutSeconds || 300)) * 1000 }
)
if (!ai.success || !ai.data) {
return {
@@ -153,7 +155,7 @@ export async function generateAgentGroupReport(
{ role: 'system', content: GROUP_REPORT_JSON_REPAIR_SYSTEM_PROMPT },
{ role: 'user', content: ai.data }
],
{ timeoutMs: Math.max(30, Math.min(1800, request.timeoutSeconds || 300)) * 1000 }
{ sessionId, timeoutMs: Math.max(30, Math.min(1800, request.timeoutSeconds || 300)) * 1000 }
)
if (!repaired.success || !repaired.data) {
const cause = parseError instanceof Error ? parseError.message : String(parseError)
+20 -4
View File
@@ -1,4 +1,5 @@
import { app } from 'electron'
import { randomUUID } from 'crypto'
import fs from 'fs-extra'
import path from 'path'
import type {
@@ -486,7 +487,8 @@ export class AIProviderService {
messages,
testing,
signal,
onDelta
onDelta,
options?.sessionId
)
}
@@ -514,14 +516,15 @@ export class AIProviderService {
onDelta?: AIChatDeltaHandler
): Promise<AIRequestResult> {
const provider = deepSeekProvider(options.baseURL, options.model)
return requestOpenAICompatible(
return requestProvider(
provider,
options.apiKey || '',
options.model || provider.defaultModel,
messages,
false,
signal,
onDelta
onDelta,
options?.sessionId
)
}
@@ -736,8 +739,21 @@ function requestProvider(
messages: AIMessage[],
testing = false,
signal?: AbortSignal,
onDelta?: AIChatDeltaHandler
onDelta?: AIChatDeltaHandler,
sessionId?: string
): Promise<AIRequestResult> {
// Match the endpoint, not the editable provider name or model name.
const url = new URL(provider.baseUrl)
if (url.hostname === 'opencode.ai' && /^\/zen\/go(?:\/|$)/.test(url.pathname)) {
const extraHeaders = Object.fromEntries(
Object.entries(provider.advanced.extraHeaders).filter(
([name]) => name.toLowerCase() !== 'x-opencode-session'
)
)
extraHeaders['x-opencode-session'] = sessionId?.trim() || randomUUID()
if (!hasHeader(extraHeaders, 'user-agent')) extraHeaders['user-agent'] = 'TraceMemo'
provider = { ...provider, advanced: { ...provider.advanced, extraHeaders } }
}
if (provider.type === 'anthropic-messages') {
return requestAnthropic(provider, apiKey, model, messages, testing, signal)
}
@@ -95,6 +95,7 @@ interface UseGroupReportGenerationArgs {
}
interface PreparedReportContext {
sessionId: string
input: Awaited<ReturnType<typeof buildGroupReportInput>>
startedAt: number
logs: ReportGenerationLog[]
@@ -507,6 +508,7 @@ export function useGroupReportGeneration({
const result = await trackStep(`AI 生成(${selectedModel.model})`, () =>
withTimeout(
window.api.aiChat(aiMessages, {
sessionId: context.sessionId,
providerId: selectedModel.providerId,
modelId: selectedModel.model,
timeoutMs: reportTimeoutSeconds * 1000
@@ -560,6 +562,7 @@ export function useGroupReportGeneration({
const repairResult = await trackStep(`AI 修复 JSON(${selectedModel.model})`, () =>
withTimeout(
window.api.aiChat(repairMessages, {
sessionId: context.sessionId,
providerId: selectedModel.providerId,
modelId: selectedModel.model,
timeoutMs: reportTimeoutSeconds * 1000
@@ -792,7 +795,12 @@ export function useGroupReportGeneration({
imageInsightsInjectedIntoPrompt:
input.imageInsightSummary.succeeded > 0 && input.prompt.includes('AI 图片识别摘要:')
})
const context: PreparedReportContext = { input, startedAt: startGenerateTime, logs }
const context: PreparedReportContext = {
sessionId: crypto.randomUUID(),
input,
startedAt: startGenerateTime,
logs
}
preparedContextRef.current = context
if (input.imageInsightSummary.failed > 0) {
setPreparationProgress({
+2
View File
@@ -117,6 +117,8 @@ export interface LegacyAIConfig {
}
export interface AIChatRequestOptions {
/** Stable ID shared by requests and retries belonging to one conversation or task. */
sessionId?: string
providerId?: string
modelId?: string
timeoutMs?: number