@dcrays/dcgchat-test 0.6.12 → 0.6.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +305 -0
- package/openclaw.plugin.json +41 -12
- package/package.json +8 -35
- package/README.md +0 -167
- package/index.ts +0 -35
- package/src/agent.ts +0 -568
- package/src/bot.ts +0 -646
- package/src/channel/index.ts +0 -329
- package/src/channel/outboundMedia.ts +0 -144
- package/src/channel/outboundTarget.ts +0 -62
- package/src/channel/uploadMediaUrl.ts +0 -76
- package/src/cron/message.ts +0 -114
- package/src/cron/params.ts +0 -164
- package/src/cron/toolGuard.ts +0 -466
- package/src/cron/types.ts +0 -15
- package/src/gateway/index.ts +0 -430
- package/src/gateway/security.ts +0 -95
- package/src/gateway/socket.ts +0 -256
- package/src/libs/ali-oss-6.23.0.tgz +0 -0
- package/src/libs/axios-1.13.6.tgz +0 -0
- package/src/libs/md5-2.3.0.tgz +0 -0
- package/src/libs/mime-types-3.0.2.tgz +0 -0
- package/src/libs/unzipper-0.12.3.tgz +0 -0
- package/src/libs/ws-8.19.0.tgz +0 -0
- package/src/monitor.ts +0 -269
- package/src/request/api.ts +0 -80
- package/src/request/oss.ts +0 -200
- package/src/request/request.ts +0 -191
- package/src/request/userInfo.ts +0 -44
- package/src/session.ts +0 -28
- package/src/skill.ts +0 -114
- package/src/tool.ts +0 -603
- package/src/tools/messageTool.ts +0 -334
- package/src/tools/toolCallGuard.ts +0 -327
- package/src/transport.ts +0 -217
- package/src/types.ts +0 -139
- package/src/utils/agentErrors.ts +0 -116
- package/src/utils/constant.ts +0 -60
- package/src/utils/env-config.ts +0 -19
- package/src/utils/formatLlmInputEvent.ts +0 -48
- package/src/utils/gatewayMsgHandler.ts +0 -147
- package/src/utils/global.ts +0 -309
- package/src/utils/inboundTurnState.ts +0 -66
- package/src/utils/log.ts +0 -77
- package/src/utils/mediaAttached.ts +0 -244
- package/src/utils/mediaEmitter.ts +0 -54
- package/src/utils/outboundAssistantText.ts +0 -117
- package/src/utils/params.ts +0 -104
- package/src/utils/passTxt.ts +0 -4
- package/src/utils/resolveRegisterConfig.ts +0 -33
- package/src/utils/searchFile.ts +0 -228
- package/src/utils/sessionState.ts +0 -137
- package/src/utils/sessionTermination.ts +0 -228
- package/src/utils/streamMerge.ts +0 -150
- package/src/utils/subagentRunMap.ts +0 -71
- package/src/utils/undiciFetchInterceptor.ts +0 -346
- package/src/utils/workspaceFilePaths.ts +0 -18
- package/src/utils/wsMessageHandler.ts +0 -124
- package/src/utils/zipExtract.ts +0 -97
- package/src/utils/zipPath.ts +0 -24
package/src/utils/streamMerge.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import type { IMsgParams } from '../types.js'
|
|
2
|
-
import { sendChunk } from '../transport.js'
|
|
3
|
-
import { getWsConnection } from './global.js'
|
|
4
|
-
import { isSessionStreamSuppressed } from './sessionTermination.js'
|
|
5
|
-
import { getInboundGeneration, takeStreamChunkIdxAndAdvance } from './inboundTurnState.js'
|
|
6
|
-
|
|
7
|
-
export const STREAM_MERGE_PROFILES = [
|
|
8
|
-
{ maxAvgDeltaGapMs: 20, flushIntervalMs: 50, deltaCount: 20, charCount: 100 },
|
|
9
|
-
{ maxAvgDeltaGapMs: 60, flushIntervalMs: 30, deltaCount: 15, charCount: 60 },
|
|
10
|
-
{ maxAvgDeltaGapMs: Number.POSITIVE_INFINITY, flushIntervalMs: 20, deltaCount: 6, charCount: 30 }
|
|
11
|
-
] as const
|
|
12
|
-
|
|
13
|
-
export const STREAM_MERGE_ENABLE_WINDOW_MS = 1000
|
|
14
|
-
export const STREAM_MERGE_ENABLE_DELTA_COUNT = 50
|
|
15
|
-
export const STREAM_MERGE_IDLE_RESET_MS = 300
|
|
16
|
-
export const STREAM_BACKPRESSURE_HIGH_WATERMARK = 512 * 1024
|
|
17
|
-
export const STREAM_BACKPRESSURE_RETRY_MS = 50
|
|
18
|
-
|
|
19
|
-
export interface StreamMergeController {
|
|
20
|
-
queueDelta(delta: string): boolean
|
|
21
|
-
flush(force?: boolean): boolean
|
|
22
|
-
dispose(): void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface CreateStreamMergeControllerOptions {
|
|
26
|
-
sessionKey: string
|
|
27
|
-
inboundGenAtStart: number
|
|
28
|
-
outboundCtx: IMsgParams
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function createStreamMergeController(
|
|
32
|
-
options: CreateStreamMergeControllerOptions
|
|
33
|
-
): StreamMergeController {
|
|
34
|
-
const { sessionKey, inboundGenAtStart, outboundCtx } = options
|
|
35
|
-
|
|
36
|
-
let pendingText = ''
|
|
37
|
-
let pendingDeltaCount = 0
|
|
38
|
-
let timer: ReturnType<typeof setTimeout> | undefined
|
|
39
|
-
let mergeMode = false
|
|
40
|
-
let burstWindowStart = 0
|
|
41
|
-
let burstDeltaCount = 0
|
|
42
|
-
let lastDeltaAt = 0
|
|
43
|
-
let deltaGapAvgMs = 0
|
|
44
|
-
|
|
45
|
-
const clearTimer = () => {
|
|
46
|
-
if (timer) {
|
|
47
|
-
clearTimeout(timer)
|
|
48
|
-
timer = undefined
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const isBackpressured = () => (getWsConnection()?.bufferedAmount ?? 0) > STREAM_BACKPRESSURE_HIGH_WATERMARK
|
|
53
|
-
|
|
54
|
-
const currentProfile = () =>
|
|
55
|
-
STREAM_MERGE_PROFILES.find((profile) => deltaGapAvgMs <= profile.maxAvgDeltaGapMs) ?? STREAM_MERGE_PROFILES[1]
|
|
56
|
-
|
|
57
|
-
const isStale = () =>
|
|
58
|
-
getInboundGeneration(sessionKey) !== inboundGenAtStart || isSessionStreamSuppressed(sessionKey)
|
|
59
|
-
|
|
60
|
-
const emit = (text: string) => sendChunk(text, outboundCtx, takeStreamChunkIdxAndAdvance(sessionKey))
|
|
61
|
-
|
|
62
|
-
const resetMerge = () => {
|
|
63
|
-
mergeMode = false
|
|
64
|
-
burstWindowStart = 0
|
|
65
|
-
burstDeltaCount = 0
|
|
66
|
-
deltaGapAvgMs = 0
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const flush = (force = false): boolean => {
|
|
70
|
-
clearTimer()
|
|
71
|
-
if (!pendingText.trim()) {
|
|
72
|
-
pendingText = ''
|
|
73
|
-
pendingDeltaCount = 0
|
|
74
|
-
return false
|
|
75
|
-
}
|
|
76
|
-
if (isStale()) {
|
|
77
|
-
pendingText = ''
|
|
78
|
-
pendingDeltaCount = 0
|
|
79
|
-
return false
|
|
80
|
-
}
|
|
81
|
-
if (!force && isBackpressured()) {
|
|
82
|
-
scheduleFlush(STREAM_BACKPRESSURE_RETRY_MS)
|
|
83
|
-
return false
|
|
84
|
-
}
|
|
85
|
-
const textToSend = pendingText
|
|
86
|
-
pendingText = ''
|
|
87
|
-
pendingDeltaCount = 0
|
|
88
|
-
return emit(textToSend)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const scheduleFlush = (delayMs = currentProfile().flushIntervalMs) => {
|
|
92
|
-
if (timer) return
|
|
93
|
-
timer = setTimeout(() => flush(), delayMs)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const queueDelta = (delta: string): boolean => {
|
|
97
|
-
const now = Date.now()
|
|
98
|
-
const deltaGapMs = lastDeltaAt > 0 ? now - lastDeltaAt : 0
|
|
99
|
-
|
|
100
|
-
if (lastDeltaAt > 0 && deltaGapMs >= STREAM_MERGE_IDLE_RESET_MS) {
|
|
101
|
-
flush(true)
|
|
102
|
-
resetMerge()
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (deltaGapMs > 0) {
|
|
106
|
-
deltaGapAvgMs = deltaGapAvgMs === 0 ? deltaGapMs : deltaGapAvgMs * 0.7 + deltaGapMs * 0.3
|
|
107
|
-
}
|
|
108
|
-
lastDeltaAt = now
|
|
109
|
-
|
|
110
|
-
if (!mergeMode) {
|
|
111
|
-
if (burstWindowStart === 0 || now - burstWindowStart > STREAM_MERGE_ENABLE_WINDOW_MS) {
|
|
112
|
-
burstWindowStart = now
|
|
113
|
-
burstDeltaCount = 1
|
|
114
|
-
} else {
|
|
115
|
-
burstDeltaCount += 1
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
pendingText += delta
|
|
119
|
-
pendingDeltaCount += 1
|
|
120
|
-
flush()
|
|
121
|
-
|
|
122
|
-
if (burstDeltaCount >= STREAM_MERGE_ENABLE_DELTA_COUNT) {
|
|
123
|
-
mergeMode = true
|
|
124
|
-
}
|
|
125
|
-
return true
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
pendingText += delta
|
|
129
|
-
pendingDeltaCount += 1
|
|
130
|
-
const profile = currentProfile()
|
|
131
|
-
if (pendingText.length >= profile.charCount || pendingDeltaCount >= profile.deltaCount) {
|
|
132
|
-
flush()
|
|
133
|
-
return true
|
|
134
|
-
}
|
|
135
|
-
scheduleFlush()
|
|
136
|
-
return true
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const dispose = () => {
|
|
140
|
-
clearTimer()
|
|
141
|
-
pendingText = ''
|
|
142
|
-
pendingDeltaCount = 0
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
queueDelta,
|
|
147
|
-
flush,
|
|
148
|
-
dispose
|
|
149
|
-
}
|
|
150
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
const requesterSessionKeyByRunId = new Map<string, string>()
|
|
2
|
-
const childSessionKeyByRunId = new Map<string, string>()
|
|
3
|
-
|
|
4
|
-
export type SubagentRunMapSnapshot = {
|
|
5
|
-
requesterSessionKeyByRunId?: Array<[string, string]>
|
|
6
|
-
childSessionKeyByRunId?: Array<[string, string]>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function setSubagentRunRequester(runId: string | undefined, requesterSessionKey: string | undefined): void {
|
|
10
|
-
const rid = runId?.trim()
|
|
11
|
-
const req = requesterSessionKey?.trim()
|
|
12
|
-
if (!rid || !req) return
|
|
13
|
-
requesterSessionKeyByRunId.set(rid, req)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function setSubagentRunChild(runId: string | undefined, childSessionKey: string | undefined): void {
|
|
17
|
-
const rid = runId?.trim()
|
|
18
|
-
const child = childSessionKey?.trim()
|
|
19
|
-
if (!rid || !child) return
|
|
20
|
-
childSessionKeyByRunId.set(rid, child)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function deleteSubagentRunMappings(runId: string | undefined): void {
|
|
24
|
-
const rid = runId?.trim()
|
|
25
|
-
if (!rid) return
|
|
26
|
-
requesterSessionKeyByRunId.delete(rid)
|
|
27
|
-
childSessionKeyByRunId.delete(rid)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function clearSubagentRunMappings(): void {
|
|
31
|
-
requesterSessionKeyByRunId.clear()
|
|
32
|
-
childSessionKeyByRunId.clear()
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function resolveRequesterSessionKeyBySubagentRunId(runId: string | undefined): string | undefined {
|
|
36
|
-
const rid = runId?.trim()
|
|
37
|
-
if (!rid) return undefined
|
|
38
|
-
return requesterSessionKeyByRunId.get(rid)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function resolveChildSessionKeyBySubagentRunId(runId: string | undefined): string | undefined {
|
|
42
|
-
const rid = runId?.trim()
|
|
43
|
-
if (!rid) return undefined
|
|
44
|
-
return childSessionKeyByRunId.get(rid)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function cleanupSubagentRunMappingsForSessionKeys(sessionKeys: Set<string>): void {
|
|
48
|
-
for (const [rid, child] of childSessionKeyByRunId.entries()) {
|
|
49
|
-
if (sessionKeys.has(child)) deleteSubagentRunMappings(rid)
|
|
50
|
-
}
|
|
51
|
-
for (const [rid, requester] of requesterSessionKeyByRunId.entries()) {
|
|
52
|
-
if (sessionKeys.has(requester)) deleteSubagentRunMappings(rid)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function snapshotSubagentRunMappings(): SubagentRunMapSnapshot {
|
|
57
|
-
return {
|
|
58
|
-
requesterSessionKeyByRunId: [...requesterSessionKeyByRunId.entries()],
|
|
59
|
-
childSessionKeyByRunId: [...childSessionKeyByRunId.entries()]
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function restoreSubagentRunMappings(snap: SubagentRunMapSnapshot | null | undefined): void {
|
|
64
|
-
clearSubagentRunMappings()
|
|
65
|
-
for (const [k, v] of snap?.requesterSessionKeyByRunId ?? []) {
|
|
66
|
-
if (typeof k === 'string' && typeof v === 'string' && k && v) requesterSessionKeyByRunId.set(k, v)
|
|
67
|
-
}
|
|
68
|
-
for (const [k, v] of snap?.childSessionKeyByRunId ?? []) {
|
|
69
|
-
if (typeof k === 'string' && typeof v === 'string' && k && v) childSessionKeyByRunId.set(k, v)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
import { realpathSync } from 'node:fs'
|
|
2
|
-
import { createRequire } from 'node:module'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { dcgLogger } from './log.js'
|
|
5
|
-
|
|
6
|
-
const UNDICI_WRAPPED_KEY = Symbol.for('dcgchat.undici-fetch-wrapped')
|
|
7
|
-
const GLOBAL_FETCH_WRAPPED_KEY = Symbol.for('dcgchat.global-fetch-wrapped')
|
|
8
|
-
const ORIGINAL_UNDICI_FETCH_KEY = Symbol.for('dcgchat.original-undici-fetch')
|
|
9
|
-
const ORIGINAL_GLOBAL_FETCH_KEY = Symbol.for('dcgchat.original-global-fetch')
|
|
10
|
-
const UNDICI_RUNTIME_DEPS_KEY = '__OPENCLAW_TEST_UNDICI_RUNTIME_DEPS__'
|
|
11
|
-
const PENDING_TTL_MS = 60_000
|
|
12
|
-
const MAX_PENDING_QUEUE = 64
|
|
13
|
-
|
|
14
|
-
type FetchLike = (input: unknown, init?: unknown) => Promise<Response>
|
|
15
|
-
export type PendingLlmBodyExt = { sessionKey: string }
|
|
16
|
-
|
|
17
|
-
type PendingEntry = PendingLlmBodyExt & { enqueuedAt: number }
|
|
18
|
-
|
|
19
|
-
type UndiciLike = {
|
|
20
|
-
fetch?: FetchLike
|
|
21
|
-
Agent?: unknown
|
|
22
|
-
EnvHttpProxyAgent?: unknown
|
|
23
|
-
ProxyAgent?: unknown
|
|
24
|
-
FormData?: unknown
|
|
25
|
-
Headers?: unknown
|
|
26
|
-
Request?: unknown
|
|
27
|
-
Response?: unknown
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type InterceptorGlobal = typeof globalThis & {
|
|
31
|
-
[UNDICI_WRAPPED_KEY]?: boolean
|
|
32
|
-
[GLOBAL_FETCH_WRAPPED_KEY]?: boolean
|
|
33
|
-
[ORIGINAL_UNDICI_FETCH_KEY]?: FetchLike
|
|
34
|
-
[ORIGINAL_GLOBAL_FETCH_KEY]?: typeof globalThis.fetch
|
|
35
|
-
[UNDICI_RUNTIME_DEPS_KEY]?: Record<string, unknown>
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const pendingLlmBodyExtQueue: PendingEntry[] = []
|
|
39
|
-
|
|
40
|
-
function prunePendingQueue(now = Date.now()): void {
|
|
41
|
-
while (pendingLlmBodyExtQueue.length > 0) {
|
|
42
|
-
const head = pendingLlmBodyExtQueue[0]
|
|
43
|
-
if (!head || now - head.enqueuedAt <= PENDING_TTL_MS) break
|
|
44
|
-
pendingLlmBodyExtQueue.shift()
|
|
45
|
-
}
|
|
46
|
-
while (pendingLlmBodyExtQueue.length > MAX_PENDING_QUEUE) {
|
|
47
|
-
pendingLlmBodyExtQueue.shift()
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function logDroppedPending(dropped: PendingEntry | undefined, reason: string): void {
|
|
52
|
-
if (dropped) {
|
|
53
|
-
dcgLogger(`llm body ext pending dropped (${reason}): sessionKey=${dropped.sessionKey}`)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function setPendingLlmBodyExt(pending: PendingLlmBodyExt | undefined): void {
|
|
58
|
-
if (!pending) {
|
|
59
|
-
pendingLlmBodyExtQueue.length = 0
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
const sessionKey = pending.sessionKey.trim()
|
|
63
|
-
if (!sessionKey) return
|
|
64
|
-
prunePendingQueue()
|
|
65
|
-
pendingLlmBodyExtQueue.push({ sessionKey, enqueuedAt: Date.now() })
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function getFetchUrl(input: unknown): string {
|
|
69
|
-
if (typeof input === 'string') return input
|
|
70
|
-
if (input instanceof URL) return input.toString()
|
|
71
|
-
if (input && typeof input === 'object' && 'url' in input) {
|
|
72
|
-
const url = (input as { url?: unknown }).url
|
|
73
|
-
if (typeof url === 'string') return url
|
|
74
|
-
}
|
|
75
|
-
return String(input)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function isLikelyLlmFetch(url: string): boolean {
|
|
79
|
-
return (
|
|
80
|
-
url.includes('/chat/completions') ||
|
|
81
|
-
url.includes('/v1/chat') ||
|
|
82
|
-
url.includes('/v1/completions') ||
|
|
83
|
-
(url.includes('/completions') && !url.includes('/embeddings'))
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function looksLikeChatCompletionJsonBody(parsed: unknown): parsed is Record<string, unknown> {
|
|
88
|
-
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return false
|
|
89
|
-
const record = parsed as Record<string, unknown>
|
|
90
|
-
return Array.isArray(record.messages) || typeof record.prompt === 'string'
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function isReadableStreamLike(body: unknown): boolean {
|
|
94
|
-
return Boolean(body && typeof body === 'object' && typeof (body as { getReader?: unknown }).getReader === 'function')
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function isNodeStreamLike(body: unknown): boolean {
|
|
98
|
-
return Boolean(body && typeof body === 'object' && typeof (body as { pipe?: unknown }).pipe === 'function')
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function isSafeBodyForTextRead(body: unknown): boolean {
|
|
102
|
-
if (typeof body === 'string') return true
|
|
103
|
-
if (typeof Blob !== 'undefined' && body instanceof Blob) return true
|
|
104
|
-
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return true
|
|
105
|
-
return false
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function readBodyAsText(body: unknown): Promise<string | null> {
|
|
109
|
-
if (body == null) return null
|
|
110
|
-
if (typeof body === 'string') return body
|
|
111
|
-
if (isReadableStreamLike(body) || isNodeStreamLike(body) || !isSafeBodyForTextRead(body)) return null
|
|
112
|
-
try {
|
|
113
|
-
return await new Response(body as BodyInit).text()
|
|
114
|
-
} catch {
|
|
115
|
-
return null
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function isRequestLike(input: unknown): input is Request {
|
|
120
|
-
return Boolean(input && typeof input === 'object' && typeof (input as Request).clone === 'function')
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function resolveRequestBodyText(input: unknown, init?: unknown): Promise<string | null> {
|
|
124
|
-
const initObj = init && typeof init === 'object' ? (init as RequestInit) : undefined
|
|
125
|
-
if (initObj?.body != null) {
|
|
126
|
-
return readBodyAsText(initObj.body)
|
|
127
|
-
}
|
|
128
|
-
if (isRequestLike(input)) {
|
|
129
|
-
try {
|
|
130
|
-
return await input.clone().text()
|
|
131
|
-
} catch {
|
|
132
|
-
return null
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return null
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function injectSessionKeyIntoBody(bodyText: string, sessionKey: string): string | null {
|
|
139
|
-
try {
|
|
140
|
-
const parsed: unknown = JSON.parse(bodyText)
|
|
141
|
-
if (!looksLikeChatCompletionJsonBody(parsed)) return null
|
|
142
|
-
|
|
143
|
-
const extBase =
|
|
144
|
-
'ext' in parsed && typeof parsed.ext === 'object' && parsed.ext !== null && !Array.isArray(parsed.ext)
|
|
145
|
-
? { ...(parsed.ext as Record<string, unknown>) }
|
|
146
|
-
: {}
|
|
147
|
-
parsed.ext = { ...extBase, sessionKey }
|
|
148
|
-
return JSON.stringify(parsed)
|
|
149
|
-
} catch {
|
|
150
|
-
return null
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function headersWithoutContentLength(headers: HeadersInit | undefined): HeadersInit | undefined {
|
|
155
|
-
if (!headers) return undefined
|
|
156
|
-
const nextHeaders = new Headers(headers)
|
|
157
|
-
nextHeaders.delete('content-length')
|
|
158
|
-
return nextHeaders
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function rebuildFetchArgs(input: unknown, init: unknown, body: string): { input: unknown; init?: unknown } {
|
|
162
|
-
const initObj = init && typeof init === 'object' ? (init as RequestInit) : undefined
|
|
163
|
-
if (initObj?.body != null) {
|
|
164
|
-
return { input, init: { ...initObj, headers: headersWithoutContentLength(initObj.headers), body } }
|
|
165
|
-
}
|
|
166
|
-
if (isRequestLike(input)) {
|
|
167
|
-
const nextInit: RequestInit = {
|
|
168
|
-
method: input.method,
|
|
169
|
-
headers: headersWithoutContentLength(input.headers),
|
|
170
|
-
body,
|
|
171
|
-
redirect: input.redirect,
|
|
172
|
-
signal: input.signal,
|
|
173
|
-
credentials: input.credentials,
|
|
174
|
-
integrity: input.integrity,
|
|
175
|
-
keepalive: input.keepalive,
|
|
176
|
-
referrer: input.referrer,
|
|
177
|
-
referrerPolicy: input.referrerPolicy,
|
|
178
|
-
mode: input.mode,
|
|
179
|
-
cache: input.cache
|
|
180
|
-
}
|
|
181
|
-
return { input: new Request(input.url, nextInit), init: initObj }
|
|
182
|
-
}
|
|
183
|
-
return { input, init: initObj ? { ...initObj, body } : { body } }
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
async function prepareFetchWithPendingLlmBodyExt(input: unknown, init?: unknown): Promise<{ input: unknown; init?: unknown }> {
|
|
187
|
-
prunePendingQueue()
|
|
188
|
-
if (pendingLlmBodyExtQueue.length === 0) return { input, init }
|
|
189
|
-
|
|
190
|
-
const url = getFetchUrl(input)
|
|
191
|
-
if (!isLikelyLlmFetch(url)) return { input, init }
|
|
192
|
-
|
|
193
|
-
const pending = pendingLlmBodyExtQueue.shift()
|
|
194
|
-
if (!pending) return { input, init }
|
|
195
|
-
|
|
196
|
-
const bodyText = await resolveRequestBodyText(input, init)
|
|
197
|
-
if (bodyText == null) {
|
|
198
|
-
logDroppedPending(pending, 'unreadable body on LLM fetch')
|
|
199
|
-
return { input, init }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const injectedBody = injectSessionKeyIntoBody(bodyText, pending.sessionKey)
|
|
203
|
-
if (!injectedBody) {
|
|
204
|
-
logDroppedPending(pending, 'body shape not injectable')
|
|
205
|
-
return { input, init }
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
dcgLogger(`llm body ext injected: sessionKey=${pending.sessionKey}`)
|
|
209
|
-
return rebuildFetchArgs(input, init, injectedBody)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function resolveUndici(): UndiciLike | null {
|
|
213
|
-
const pluginRequire = createRequire(import.meta.url)
|
|
214
|
-
const candidates: Array<{ label: string; load: () => UndiciLike }> = []
|
|
215
|
-
|
|
216
|
-
if (process.argv[1]) {
|
|
217
|
-
candidates.push({
|
|
218
|
-
label: 'host-entry',
|
|
219
|
-
load: () => {
|
|
220
|
-
const hostEntry = realpathSync(path.resolve(process.argv[1] as string))
|
|
221
|
-
return createRequire(hostEntry)('undici') as UndiciLike
|
|
222
|
-
}
|
|
223
|
-
})
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
candidates.push({
|
|
227
|
-
label: 'openclaw-package',
|
|
228
|
-
load: () => {
|
|
229
|
-
const openclawPackageJson = pluginRequire.resolve('openclaw/package.json')
|
|
230
|
-
return createRequire(openclawPackageJson)('undici') as UndiciLike
|
|
231
|
-
}
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
candidates.push({
|
|
235
|
-
label: 'plugin-local',
|
|
236
|
-
load: () => pluginRequire('undici') as UndiciLike
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
for (const candidate of candidates) {
|
|
240
|
-
try {
|
|
241
|
-
const undici = candidate.load()
|
|
242
|
-
if (typeof undici?.fetch === 'function') {
|
|
243
|
-
dcgLogger(`undici resolved via ${candidate.label}`)
|
|
244
|
-
return undici
|
|
245
|
-
}
|
|
246
|
-
} catch {
|
|
247
|
-
// Try the next resolution root.
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return null
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function copyFunctionShape(target: FetchLike, source: FetchLike): void {
|
|
254
|
-
try {
|
|
255
|
-
Object.assign(target, source)
|
|
256
|
-
} catch {
|
|
257
|
-
// Some runtimes expose non-writable function properties; the wrapper still works without copying them.
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function installGlobalFetchPatch(): boolean {
|
|
262
|
-
const g = globalThis as InterceptorGlobal
|
|
263
|
-
if (g[GLOBAL_FETCH_WRAPPED_KEY]) return true
|
|
264
|
-
if (typeof globalThis.fetch !== 'function') {
|
|
265
|
-
dcgLogger('global fetch body ext patch skipped: globalThis.fetch unavailable', 'error')
|
|
266
|
-
return false
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const originalFetch = globalThis.fetch.bind(globalThis)
|
|
270
|
-
g[ORIGINAL_GLOBAL_FETCH_KEY] = globalThis.fetch
|
|
271
|
-
|
|
272
|
-
const patchedFetch = async function patchedGlobalFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
|
|
273
|
-
const prepared = await prepareFetchWithPendingLlmBodyExt(input, init)
|
|
274
|
-
return originalFetch(prepared.input as RequestInfo | URL, prepared.init as RequestInit | undefined)
|
|
275
|
-
}
|
|
276
|
-
try {
|
|
277
|
-
Object.assign(patchedFetch, globalThis.fetch)
|
|
278
|
-
} catch {
|
|
279
|
-
// The patch only needs call compatibility.
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
globalThis.fetch = patchedFetch as typeof globalThis.fetch
|
|
283
|
-
g[GLOBAL_FETCH_WRAPPED_KEY] = true
|
|
284
|
-
dcgLogger('global fetch body ext patch installed')
|
|
285
|
-
return true
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
export function installUndiciFetchInterceptor(): boolean {
|
|
289
|
-
const g = globalThis as InterceptorGlobal
|
|
290
|
-
const globalFetchPatched = installGlobalFetchPatch()
|
|
291
|
-
if (g[UNDICI_WRAPPED_KEY]) return true
|
|
292
|
-
|
|
293
|
-
const undici = resolveUndici()
|
|
294
|
-
if (!undici) {
|
|
295
|
-
dcgLogger('undici.fetch intercept skipped: undici module unavailable', 'error')
|
|
296
|
-
return globalFetchPatched
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const originalFetch = undici.fetch
|
|
300
|
-
if (typeof originalFetch !== 'function') {
|
|
301
|
-
return globalFetchPatched
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
g[ORIGINAL_UNDICI_FETCH_KEY] = originalFetch
|
|
305
|
-
|
|
306
|
-
let callId = 0
|
|
307
|
-
const wrappedFetch: FetchLike = async function wrappedUndiciFetch(input: unknown, init?: unknown) {
|
|
308
|
-
const url = getFetchUrl(input)
|
|
309
|
-
const prepared = await prepareFetchWithPendingLlmBodyExt(input, init)
|
|
310
|
-
if (!isLikelyLlmFetch(url)) {
|
|
311
|
-
return originalFetch(prepared.input, prepared.init)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const id = ++callId
|
|
315
|
-
const startedAt = Date.now()
|
|
316
|
-
try {
|
|
317
|
-
const response = await originalFetch(prepared.input, prepared.init)
|
|
318
|
-
return response
|
|
319
|
-
} catch (e) {
|
|
320
|
-
dcgLogger(`[undici.fetch #${id}] LLM request failed duration=${Date.now() - startedAt}ms error=${String(e)}`, 'error')
|
|
321
|
-
throw e
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
copyFunctionShape(wrappedFetch, originalFetch)
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
undici.fetch = wrappedFetch
|
|
328
|
-
} catch (e) {
|
|
329
|
-
dcgLogger(`undici.fetch direct patch failed, runtime deps injection will still be attempted: ${String(e)}`, 'error')
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
g[UNDICI_RUNTIME_DEPS_KEY] = {
|
|
333
|
-
...g[UNDICI_RUNTIME_DEPS_KEY],
|
|
334
|
-
Agent: undici.Agent,
|
|
335
|
-
EnvHttpProxyAgent: undici.EnvHttpProxyAgent,
|
|
336
|
-
ProxyAgent: undici.ProxyAgent,
|
|
337
|
-
FormData: undici.FormData,
|
|
338
|
-
Headers: undici.Headers,
|
|
339
|
-
Request: undici.Request,
|
|
340
|
-
Response: undici.Response,
|
|
341
|
-
fetch: wrappedFetch
|
|
342
|
-
}
|
|
343
|
-
g[UNDICI_WRAPPED_KEY] = true
|
|
344
|
-
dcgLogger(`undici.fetch intercept installed via ${UNDICI_RUNTIME_DEPS_KEY}`)
|
|
345
|
-
return true
|
|
346
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import os from 'node:os'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* 将正文里的 `~/...` / `~\...` 展开为绝对路径(`path.resolve` 不会处理 `~`)。
|
|
6
|
-
*/
|
|
7
|
-
export function expandTildePath(input: string): string {
|
|
8
|
-
const s = input.trim()
|
|
9
|
-
if (!s) return s
|
|
10
|
-
if (s === '~') return os.homedir()
|
|
11
|
-
if (s.startsWith('~/')) {
|
|
12
|
-
return path.resolve(os.homedir(), s.slice(2))
|
|
13
|
-
}
|
|
14
|
-
if (s.startsWith('~\\')) {
|
|
15
|
-
return path.resolve(os.homedir(), s.slice(2))
|
|
16
|
-
}
|
|
17
|
-
return s
|
|
18
|
-
}
|