Skip to content

feat(datasource): add DingTalk knowledge base connector (#1707) - #46

Draft
lyingbug wants to merge 7 commits into
mainfrom
cursor/dingtalk-datasource-connector-fa8b
Draft

feat(datasource): add DingTalk knowledge base connector (#1707)#46
lyingbug wants to merge 7 commits into
mainfrom
cursor/dingtalk-datasource-connector-fa8b

Conversation

@lyingbug

@lyingbug lyingbug commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Closes Tencent#1707.

This is @mingri31164's connector from #2263, rebased onto current main (its own branch is ~140 commits behind and conflicts in four locale bundles), with three follow-up fixes on top. The original commit is preserved with its author, so attribution stays intact.

Why this implementation

I reviewed the 19 competing DingTalk submissions and compared their block→Markdown converters by feeding each the payload shapes from DingTalk's official 块元素数据结构 reference. Tencent#2263 was the only one that reproduced every element (headings, bold runs, link and image URLs, blockquote, callout, both list kinds, tables, columns) with no losses. It also uses the documented endpoints — POST /v1.0/oauth2/accessToken, GET /v2.0/wiki/workspaces, GET /v2.0/wiki/nodes, GET /v1.0/doc/suites/documents/{docKey}/blocks — with the x-acs-dingtalk-access-token header and the mandatory operatorId (unionId) on every call, goes through datasource.NewConnectorHTTPClient/ValidateConnectorBaseURL for SSRF safety, and suppresses deletion detection when a traversal is incomplete so a transient listing failure cannot wipe a knowledge base.

What the rebase changed

Only the four locale bundles. Upstream regenerated them in 25f3751 after this branch forked, so the positional hunks no longer applied; the same keys were re-inserted by name. The pruned datasource.syncError bag is recreated for the DingTalk reason codes, which the sync-log view reads dynamically.

Improvements on top

Block rendering. DingTalk emits every list item as its own top-level block, so an ordered run was numbered 1. on each item and never terminated — a following table or paragraph was absorbed into the list as a lazy continuation. The renderer now tracks the run by listId + level, numbers items sequentially, and closes it with a blank line. heading.level is documented as an integer but the string forms ("2", "heading-2") occur in practice and made json.Unmarshal fail for the whole envelope, dropping the heading text along with its level; it is now decoded tolerantly. More generally, a type mismatch anywhere in a block's payload used to discard the entire block, so an unexpected field now costs the block's formatting rather than its text.

Block-range paging. An omitted success field is no longer read as an explicit failure, since the transport already rejects non-2xx. And a server that ignores startIndex/endIndex now ends pagination instead of re-reading the same blocks up to the 1000-page cap and duplicating the document.

Connector localization. The picker resolves its label and description through computed keys (datasource.connector.${def.type}), which the static locale audit cannot see, so the card rendered raw key strings in the UI while every check stayed green. Found by clicking through the running app:

Connector card rendering the raw i18n key datasource.connector.dingtalk

Both entries are added to all four bundles, with a test that checks the two bags against the connector list in DataSourceEditorDialog.vue so a future connector cannot ship an unlocalized card:

Connector card correctly rendering 钉钉文档 with its description

Verification

Backend go build ./internal/..., go vet, and go test ./internal/datasource/... ./internal/container/... all pass, including the DingTalk package. Frontend npm test passes 308/308 (locale audit included) and npm run type-check is clean. Nine new tests cover sequential numbering, per-group restart, list-run termination, non-integer heading levels, salvaged text from undecodable blocks, the three block-range paging behaviours, and connector-name locale coverage.

End-to-end through the UI on a locally built Lite instance (make build-lite, SQLite):

dingtalk_datasource_connector_walkthrough.mp4

The credentials step asks for Client ID (AppKey), Client Secret (AppSecret) and 操作人 UnionID, with the first-run setup guide spelling out the three DingTalk Open Platform steps:

DingTalk credentials form with the setup guide expanded

Known gaps (follow-ups, not regressions)

Non-ALIDOC nodes (locally uploaded files, sheets) are skipped rather than downloaded through the 钉盘 storage APIs, and the connector does not implement the optional FetchStream, so a very large knowledge base restarts from the last cursor instead of resuming mid-traversal.

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

ttommybot and others added 7 commits August 3, 2026 23:54
… RAG wait row

The wait row only appeared once a completed knowledge_search step existed, so
attachment-only turns (attachment_parsing / image_analysis with no KB hit) still
showed nothing between the last step and the first answer token. Model waiting is
now a three-state kind: 'model' after retrieval finished, the neutral 'preparing'
row otherwise.

A dropped SSE connection never sets is_completed (the stream layer only raises a
toast), so the row used to promise an answer forever. It now stops claiming
progress after RAG_WAIT_STALL_DELAY_MS and drops the shimmer.

Also share RAG_RETRIEVAL_TOOL_NAMES instead of repeating the knowledge_search /
search_knowledge alias check, name the reveal delay, and move the reveal/stall
timers into a controller with an injectable scheduler so the timing is covered by
tests instead of by source-matching alone.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
role="status" lived on the wait row itself, so the live region was inserted
together with its own text and screen readers had nothing to announce. Move the
announcement to an sr-only region that stays mounted for the whole turn, which
also covers the pre-pipeline wait row that had no announcement at all.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
Integrate DingTalk knowledge bases through the official OAuth, wiki node, and document block APIs. Add lazy resource selection, protected incremental sync, Markdown rendering, localized frontend configuration, tests, and setup documentation.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
The block→Markdown path lost content in three ways that only show up against
the shapes DingTalk actually returns:

- Every list item arrives as its own top-level block, so an ordered run was
  numbered "1." on each item and never terminated. A following table or
  paragraph was absorbed into the list as a lazy continuation. Track the run
  (listId + level) to number items sequentially and close it with a blank line.
- heading.level is documented as an integer, but the string forms ("2",
  "heading-2") appear in practice and made json.Unmarshal fail for the whole
  envelope, dropping the heading text along with its level.
- A type mismatch anywhere in a block's payload discarded the entire block.
  Fall back to re-reading just the envelope so an unexpected field costs the
  block's formatting rather than its text.

The blocks endpoint is also read more defensively: an omitted `success` field is
no longer read as an explicit failure (the transport already rejects non-2xx),
and a server that ignores startIndex/endIndex now ends pagination instead of
re-reading the same blocks up to the page cap and duplicating the document.

Co-authored-by: lyingbug <lyingbug@users.noreply.github.com>
The picker resolves both strings through computed keys
(`datasource.connector.${def.type}`), so the static locale audit never sees
them and the connector card rendered the raw keys instead of "钉钉文档" and its
description. Add the missing entries to all four bundles, plus a test that
checks both bags against the connector list in DataSourceEditorDialog.vue so a
future connector cannot ship with an unlocalized card.

Co-authored-by: lyingbug <lyingbug@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.

[Feature]: 知识库数据源管理页,请添加钉钉文档数据源

3 participants