Skip to content

feat: add Codexpert connector middleware#236

Open
rainhotel wants to merge 6 commits into
xpert-ai:mainfrom
rainhotel:codexpertplugin
Open

feat: add Codexpert connector middleware#236
rainhotel wants to merge 6 commits into
xpert-ai:mainfrom
rainhotel:codexpertplugin

Conversation

@rainhotel
Copy link
Copy Markdown
Contributor

Summary

  • add @xpert-ai/plugin-codexpert-connector middleware
  • proxy Codexpert MCP context tools with service-token and principal headers
  • add runCodexpertTask for synchronous Codexpert execution with visible output projection
  • add lightweight connector run mapping entity and markdown-safe visible projection formatting
  • add package documentation and changeset

Verification

  • ./xpertai/node_modules/.bin/tsc -p xpertai/middlewares/codexpert-connector/tsconfig.lib.json
  • pnpm exec jest --config middlewares/codexpert-connector/jest.config.ts --runInBand --no-watchman

Copilot AI review requested due to automatic review settings April 29, 2026 05:08
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39bb0fe531

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

): CodexpertTaskResult {
const terminalError = error ?? (event?.type === 'error' ? event.message : null)
return {
status: terminalError ? 'failed' : 'success',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Honor terminal Codexpert status when building result

buildResult marks every run as success unless an explicit error was thrown, so a terminal done event with status like timeout, canceled, or backend-reported failure is currently reported as success. This can make the agent continue follow-up actions (for example PR publishing or “completed” messaging) on runs that actually failed or were interrupted.

Useful? React with 👍 / 👎.

...input,
updatedAt: new Date(),
})
await this.repository.save(entity)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Prevent DB write errors from aborting Codexpert task flow

record lets repository write errors bubble up, but runCodexpertTask awaits this call in both normal and error paths. If the mapping table is unavailable (e.g., migration lag, permission issue, transient DB outage), the middleware can fail the whole tool call and even mask the original Codexpert outcome, although this table is intended for recovery/diagnostics metadata.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new @xpert-ai/plugin-codexpert-connector middleware package that proxies Codexpert MCP context tools and provides a synchronous runCodexpertTask tool with user-visible streamed output projection, plus a lightweight run-mapping persistence layer.

Changes:

  • Introduces Codexpert connector middleware strategy, MCP/HTTP client, and visible markdown-safe projection utilities.
  • Adds TypeORM entity + service to persist Xpert execution ↔ Codexpert session/task/thread/execution mappings.
  • Adds new package scaffolding (tsconfigs, jest config, docs) plus lockfile + changeset updates.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
xpertai/pnpm-lock.yaml Adds lock entries for the new middleware workspace package dependencies.
xpertai/middlewares/codexpert-connector/tsconfig.spec.json Adds test tsconfig for the new package.
xpertai/middlewares/codexpert-connector/tsconfig.lib.json Adds library build tsconfig for the new package.
xpertai/middlewares/codexpert-connector/tsconfig.json Adds TS project references for lib/spec builds.
xpertai/middlewares/codexpert-connector/src/lib/types.ts Defines config schema, tool input schema, events, and result types.
xpertai/middlewares/codexpert-connector/src/lib/markdown-format.ts Adds markdown normalization + hard line-break preservation for visible projection.
xpertai/middlewares/codexpert-connector/src/lib/entities/codexpert-connector-run.entity.ts Adds TypeORM entity for run mapping persistence.
xpertai/middlewares/codexpert-connector/src/lib/codexpert-visible-projector.ts Implements event → user-visible chunk projection with dedupe + buffering.
xpertai/middlewares/codexpert-connector/src/lib/codexpert-visible-projector.spec.ts Adds unit tests (currently covering markdown formatting behavior).
xpertai/middlewares/codexpert-connector/src/lib/codexpert-connector.module.ts Registers plugin module, middleware, and run service; ensures schema at bootstrap.
xpertai/middlewares/codexpert-connector/src/lib/codexpert-connector.middleware.ts Main middleware strategy: exposes MCP tools + runCodexpertTask, streams output, records runs.
xpertai/middlewares/codexpert-connector/src/lib/codexpert-connector.client.ts Adds MCP client wrapper + connector HTTP streaming client with identity headers.
xpertai/middlewares/codexpert-connector/src/lib/codexpert-connector-run.service.ts Adds schema bootstrap + record/upsert persistence.
xpertai/middlewares/codexpert-connector/src/index.ts Adds plugin entrypoint + exports.
xpertai/middlewares/codexpert-connector/package.json Declares the new package metadata and peer dependencies.
xpertai/middlewares/codexpert-connector/jest.config.ts Adds Jest config for the new package.
xpertai/middlewares/codexpert-connector/eslint.config.mjs Adds ESLint config placeholder for the new package.
xpertai/middlewares/codexpert-connector/README_zh.md Adds Chinese documentation for config, tool flow, projection, and persistence model.
xpertai/middlewares/codexpert-connector/README.md Adds English documentation for config, tool flow, projection, and persistence model.
xpertai/middlewares/codexpert-connector/.spec.swcrc Adds SWC config for Jest (TS + decorators).
xpertai/.changeset/tidy-codexpert-connectors.md Adds changeset entry for publishing the new package.
Files not reviewed (1)
  • xpertai/pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +392 to +416
const terminalError = error ?? (event?.type === 'error' ? event.message : null)
return {
status: terminalError ? 'failed' : 'success',
codingSessionId: pickString(
event && 'codingSessionId' in event ? event.codingSessionId : null,
sessionId,
sessionSnapshot?.codingSessionId,
sessionSnapshot?.id,
),
taskId: pickString(event && 'taskId' in event ? event.taskId : null, input.taskId),
threadId: pickString(event && 'threadId' in event ? event.threadId : null, input.threadId),
executionId: pickString(event && 'executionId' in event ? event.executionId : null),
repo: {
id: pickString(input.repoId, sessionSnapshot?.repoId),
name: pickString(sessionSnapshot?.repoName),
owner: pickString(sessionSnapshot?.repoOwner),
slug: pickString(sessionSnapshot?.repoSlug),
},
branch: pickString(input.branchName, sessionSnapshot?.branchName),
environmentId: pickString(event && 'environmentId' in event ? event.environmentId : null, sessionSnapshot?.environmentId),
environmentReused: null,
summary: event?.type === 'done' ? pickString(event.summary, event.output) : null,
error: terminalError,
...(event?.type === 'done' && event.prUrl ? { prUrl: event.prUrl } : {}),
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildResult always returns status: 'success' | 'failed' based only on whether an error message exists, ignoring event.status (and the timeout / canceled variants declared in CodexpertTaskResult). Also, if the stream ends without a terminal done/error event (event is null), this currently reports success, which can mask incomplete/aborted executions. Map done.status (and any timeout/canceled semantics) into the returned result and treat a missing terminal event as a failure/timeout so callers can react correctly.

Copilot uses AI. Check for mistakes.
Comment on lines +404 to +410
repo: {
id: pickString(input.repoId, sessionSnapshot?.repoId),
name: pickString(sessionSnapshot?.repoName),
owner: pickString(sessionSnapshot?.repoOwner),
slug: pickString(sessionSnapshot?.repoSlug),
},
branch: pickString(input.branchName, sessionSnapshot?.branchName),
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildResult always returns a non-null repo object, even when neither the input nor the session snapshot has any repo fields (it becomes { id: null, name: null, ... }). Since CodexpertTaskResult allows repo: ... | null (and buildInitialResult already returns null when repo is unknown), consider returning null here too when no repo data is available to keep the API consistent and avoid downstream null-vs-empty-object ambiguity.

Copilot uses AI. Check for mistakes.
Comment on lines +270 to +275
subscriber.next({
data: {
type: ChatMessageTypeEnum.MESSAGE,
data: text,
},
})
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In emitSubscriberMessage, the fallback subscriber.next(...) inside the catch is not itself protected. If subscriber.next throws (e.g., subscriber is closed / backpressure errors), this will still bubble up and can fail the whole projection/tool call. Wrap the fallback subscriber.next in its own try/catch (or otherwise swallow/log) to keep projection best-effort.

Suggested change
subscriber.next({
data: {
type: ChatMessageTypeEnum.MESSAGE,
data: text,
},
})
try {
subscriber.next({
data: {
type: ChatMessageTypeEnum.MESSAGE,
data: text,
},
})
} catch {
// Best-effort subscriber delivery; ignore fallback emission errors.
}

Copilot uses AI. Check for mistakes.
Comment on lines +441 to +446
data: {
type: 'text',
text: `Codexpert failed: ${message}`,
xpertName: 'Codexpert',
agentKey: 'codexpert',
} satisfies TMessageContentText,
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

projectImmediateError hard-codes xpertName: 'Codexpert' and agentKey: 'codexpert' even though the module defines CODEXPERT_XPERT_NAME / CODEXPERT_AGENT_KEY constants. Reuse the constants here to avoid drift if these identifiers change elsewhere.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +8
import { formatVisibleMarkdown, preserveMarkdownLineBreaks } from './markdown-format.js'

describe('preserveMarkdownLineBreaks', () => {
it('keeps visible single line breaks in markdown text', () => {
expect(preserveMarkdownLineBreaks('第一行\n第二行\n第三行')).toBe('第一行 \n第二行 \n第三行')
})

it('keeps a trailing line break when a flush boundary ends after newline', () => {
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file is named codexpert-visible-projector.spec.ts, but it only tests markdown-format.ts. Renaming it (or adding projector-specific tests here) would make test intent clearer and help keep coverage organized.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +45
"dependencies": {},
"peerDependencies": {
"@langchain/core": "0.3.72",
"@metad/contracts": "^3.8.0",
"@modelcontextprotocol/sdk": "^1.17.5",
"@nestjs/common": "^11.1.6",
"@nestjs/typeorm": "^11.0.0",
"@xpert-ai/chatkit-types": "^0.0.12",
"@xpert-ai/plugin-sdk": "^3.8.0",
"chalk": "4.1.2",
"typeorm": "^0.3.20",
"zod": "3.25.67"
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pnpm-lock.yaml lists tslib as a direct dependency for this workspace package, but package.json does not declare it in either dependencies or peerDependencies. This manifest/lock mismatch can break installs with --frozen-lockfile. Either add the missing tslib entry to package.json (matching the lock specifier), or regenerate the lockfile after aligning the manifest.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated 7 comments.

Files not reviewed (1)
  • xpertai/pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +50 to +66
{ name: 'id', type: 'uuid', isPrimary: true, generationStrategy: 'uuid', default: 'gen_random_uuid()' },
{ name: 'tenant_id', type: 'varchar', length: '64' },
{ name: 'organization_id', type: 'varchar', length: '64' },
{ name: 'user_id', type: 'varchar', length: '64' },
{ name: 'xpert_id', type: 'varchar', length: '64', isNullable: true },
{ name: 'conversation_id', type: 'varchar', length: '128', isNullable: true },
{ name: 'execution_id', type: 'varchar', length: '128', isNullable: true },
{ name: 'coding_session_id', type: 'varchar', length: '128', isNullable: true },
{ name: 'task_id', type: 'varchar', length: '128', isNullable: true },
{ name: 'thread_id', type: 'varchar', length: '128', isNullable: true },
{ name: 'codexpert_execution_id', type: 'varchar', length: '128', isNullable: true },
{ name: 'status', type: 'varchar', length: '32' },
{ name: 'last_error', type: 'text', isNullable: true },
{ name: 'metadata', type: 'jsonb', isNullable: true },
{ name: 'created_at', type: 'timestamp with time zone', default: 'now()' },
{ name: 'updated_at', type: 'timestamp with time zone', default: 'now()' },
],
@Column({ name: 'last_error', type: 'text', nullable: true })
lastError?: string | null

@Column({ name: 'metadata', type: 'jsonb', nullable: true })
Comment on lines +108 to +114
let newlineIndex = buffer.indexOf('\n')
while (newlineIndex >= 0) {
const line = buffer.slice(0, newlineIndex).trim()
buffer = buffer.slice(newlineIndex + 1)
if (line) {
yield JSON.parse(line) as CodexpertConnectorEvent
}
Comment on lines +33 to +60
export async function projectVisibleCodexpertEvent(
event: CodexpertConnectorEvent,
state: ProjectionState,
config?: RunnableConfig,
enableStatusEvents = true,
) {
if (event.type !== 'text_delta') {
flushVisibleCodexpertProjection(state, config)
}

const text = resolveVisibleText(event, state, enableStatusEvents)
if (!text) {
return
}

if (event.type === 'text_delta') {
appendBufferedText(state, text)
if (shouldFlushBufferedText(state.pendingText)) {
flushVisibleCodexpertProjection(state, config)
} else {
scheduleBufferedTextFlush(state, config)
}
return
}

emitSubscriberMessage(config, text)
}

Comment on lines +254 to +257
Compile the plugin:

```bash
cd /Users/lilinhao/project/code-xpert/codexpertplugin/xpertplugins
Comment thread xpertai/middlewares/codexpert-connector/README_zh.md Outdated
@@ -0,0 +1 @@
export default []
rainhotel and others added 2 commits May 7, 2026 09:40
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants