|
@@ -1,4 +1,5 @@
|
|
|
// Codex 适配器:codex app-server(JSON-RPC)→ 统一事件(docs/design/03-providers.md §3,ADR-0003)
|
|
// Codex 适配器:codex app-server(JSON-RPC)→ 统一事件(docs/design/03-providers.md §3,ADR-0003)
|
|
|
|
|
+import { createHash } from 'node:crypto'
|
|
|
import fs from 'node:fs'
|
|
import fs from 'node:fs'
|
|
|
import path from 'node:path'
|
|
import path from 'node:path'
|
|
|
import type { AppSettings, ProviderStatusDto, UsagePayload } from '@agent-server/shared'
|
|
import type { AppSettings, ProviderStatusDto, UsagePayload } from '@agent-server/shared'
|
|
@@ -12,7 +13,7 @@ import { ALWAYS_DISABLED, skillDisplayId, skillsToDisable } from './skills'
|
|
|
import type {
|
|
import type {
|
|
|
AskForApproval, CommandExecutionRequestApprovalParams, FileChangeRequestApprovalParams, GetAccountResponse,
|
|
AskForApproval, CommandExecutionRequestApprovalParams, FileChangeRequestApprovalParams, GetAccountResponse,
|
|
|
ItemCompletedNotification, ItemStartedNotification, LoginAccountResponse, ModelListResponse, SkillMetadata, SkillsListResponse,
|
|
ItemCompletedNotification, ItemStartedNotification, LoginAccountResponse, ModelListResponse, SkillMetadata, SkillsListResponse,
|
|
|
- McpServerElicitationRequestParams, PermissionsRequestApprovalParams, ThreadItem, ThreadStartResponse, ThreadTokenUsageUpdatedNotification,
|
|
|
|
|
|
|
+ McpServerElicitationRequestParams, PermissionsRequestApprovalParams, ThreadForkResponse, ThreadItem, ThreadStartResponse, ThreadTokenUsageUpdatedNotification,
|
|
|
ToolRequestUserInputParams, TurnCompletedNotification, TurnStartResponse,
|
|
ToolRequestUserInputParams, TurnCompletedNotification, TurnStartResponse,
|
|
|
} from './protocol/v2'
|
|
} from './protocol/v2'
|
|
|
import { AppServerClient, RpcRequestError } from './rpc'
|
|
import { AppServerClient, RpcRequestError } from './rpc'
|
|
@@ -36,6 +37,11 @@ export class CodexProvider implements AgentProvider {
|
|
|
private starting: Promise<AppServerClient> | null = null
|
|
private starting: Promise<AppServerClient> | null = null
|
|
|
private clientKey = ''
|
|
private clientKey = ''
|
|
|
private runs = new Map<string, ThreadRun>()
|
|
private runs = new Map<string, ThreadRun>()
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 当前 app-server 进程中已加载的会话及其加载时的配置指纹。已加载会话的配置不会被 thread/resume 更新
|
|
|
|
|
+ * (已验证,codex 0.164),配置变化时改为 thread/fork:保留历史、采用新配置(ADR-0020)
|
|
|
|
|
+ */
|
|
|
|
|
+ private loaded = new Map<string, string>()
|
|
|
private models: string[] = []
|
|
private models: string[] = []
|
|
|
private login: CodexLoginState | null = null
|
|
private login: CodexLoginState | null = null
|
|
|
private version: string | null = null
|
|
private version: string | null = null
|
|
@@ -72,6 +78,7 @@ export class CodexProvider implements AgentProvider {
|
|
|
})
|
|
})
|
|
|
client.on('exit', code => {
|
|
client.on('exit', code => {
|
|
|
this.log.warn({ code }, 'codex app-server 已退出')
|
|
this.log.warn({ code }, 'codex app-server 已退出')
|
|
|
|
|
+ this.loaded.clear()
|
|
|
if (this.client === client) this.client = null
|
|
if (this.client === client) this.client = null
|
|
|
for (const run of this.runs.values()) run.fail(new ProviderRunError('codex_exited', `codex app-server 意外退出(${code})`))
|
|
for (const run of this.runs.values()) run.fail(new ProviderRunError('codex_exited', `codex app-server 意外退出(${code})`))
|
|
|
})
|
|
})
|
|
@@ -120,8 +127,25 @@ export class CodexProvider implements AgentProvider {
|
|
|
developerInstructions: ctx.systemPromptAppend,
|
|
developerInstructions: ctx.systemPromptAppend,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 模型每轮通过 turn/start 传入,不计入指纹
|
|
|
|
|
+ const fingerprint = createHash('sha256').update(JSON.stringify({ ...threadParams, model: undefined })).digest('hex')
|
|
|
let threadId = ctx.providerSessionId
|
|
let threadId = ctx.providerSessionId
|
|
|
- if (threadId) {
|
|
|
|
|
|
|
+ if (threadId && this.loaded.has(threadId) && this.loaded.get(threadId) !== fingerprint) {
|
|
|
|
|
+ // 配置变了(Profile 的访问策略、权限模式、技能、全局禁止项、提示词等):分叉出新会话以采用新配置
|
|
|
|
|
+ try {
|
|
|
|
|
+ const from = threadId
|
|
|
|
|
+ const r = await client.request<ThreadForkResponse>('thread/fork', { ...threadParams, threadId: from, excludeTurns: true })
|
|
|
|
|
+ ctx.log.info({ from, to: r.thread.id }, 'codex 会话配置已变化,分叉为新会话')
|
|
|
|
|
+ void client.request('thread/unsubscribe', { threadId: from }).catch(() => {})
|
|
|
|
|
+ this.loaded.delete(from)
|
|
|
|
|
+ threadId = r.thread.id
|
|
|
|
|
+ ctx.setProviderSessionId(threadId)
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ // 分叉失败时不能继续使用旧配置,改为新会话(丢失上下文,但配置正确)
|
|
|
|
|
+ ctx.log.warn({ err: (err as Error).message, threadId }, 'codex 会话分叉失败,以新会话继续')
|
|
|
|
|
+ threadId = null
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (threadId && !this.loaded.has(threadId)) {
|
|
|
try {
|
|
try {
|
|
|
await client.request('thread/resume', { ...threadParams, threadId, excludeTurns: true })
|
|
await client.request('thread/resume', { ...threadParams, threadId, excludeTurns: true })
|
|
|
} catch (err) {
|
|
} catch (err) {
|
|
@@ -134,6 +158,7 @@ export class CodexProvider implements AgentProvider {
|
|
|
threadId = r.thread.id
|
|
threadId = r.thread.id
|
|
|
ctx.setProviderSessionId(threadId)
|
|
ctx.setProviderSessionId(threadId)
|
|
|
}
|
|
}
|
|
|
|
|
+ this.loaded.set(threadId, fingerprint)
|
|
|
|
|
|
|
|
const run = new ThreadRun(ctx, threadId)
|
|
const run = new ThreadRun(ctx, threadId)
|
|
|
this.runs.set(threadId, run)
|
|
this.runs.set(threadId, run)
|