@leeoohoo/ui-apps-devkit 0.1.13 → 0.1.14
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/package.json +1 -1
- package/src/sandbox/server.js +169 -2
package/package.json
CHANGED
package/src/sandbox/server.js
CHANGED
|
@@ -66,11 +66,124 @@ function resolveSandboxConfigPath({ primaryRoot, legacyRoot }) {
|
|
|
66
66
|
|
|
67
67
|
const DEFAULT_LLM_BASE_URL = 'https://api.openai.com/v1';
|
|
68
68
|
const UI_PROMPTS_FILENAME = 'ui-prompts.jsonl';
|
|
69
|
+
const DEFAULT_ASYNC_POLL_MS = 1000;
|
|
70
|
+
const DEFAULT_ASYNC_TIMEOUT_MS = 2 * 60 * 60 * 1000;
|
|
71
|
+
|
|
72
|
+
const sleepMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
69
73
|
|
|
70
74
|
function normalizeText(value) {
|
|
71
75
|
return typeof value === 'string' ? value.trim() : '';
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
function normalizeAsyncToolName(value) {
|
|
79
|
+
const normalized = normalizeText(value);
|
|
80
|
+
return normalized ? normalized.toLowerCase() : '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalizeAsyncTaskConfig(raw, toolName) {
|
|
84
|
+
if (!raw || typeof raw !== 'object') return null;
|
|
85
|
+
const tools = Array.isArray(raw.tools) ? raw.tools.map(normalizeAsyncToolName).filter(Boolean) : [];
|
|
86
|
+
const toolKey = normalizeAsyncToolName(toolName);
|
|
87
|
+
if (tools.length > 0 && toolKey && !tools.includes(toolKey)) return null;
|
|
88
|
+
const taskIdKey = typeof raw.taskIdKey === 'string' ? raw.taskIdKey.trim() : 'taskId';
|
|
89
|
+
if (!taskIdKey) return null;
|
|
90
|
+
const resultSource = typeof raw.resultSource === 'string' ? raw.resultSource.trim().toLowerCase() : 'ui_prompts';
|
|
91
|
+
const uiPromptFile = typeof raw.uiPromptFile === 'string' ? raw.uiPromptFile.trim() : UI_PROMPTS_FILENAME;
|
|
92
|
+
const pollIntervalMs = Number.isFinite(Number(raw.pollIntervalMs))
|
|
93
|
+
? Math.max(200, Math.min(5000, Number(raw.pollIntervalMs)))
|
|
94
|
+
: DEFAULT_ASYNC_POLL_MS;
|
|
95
|
+
return {
|
|
96
|
+
taskIdKey,
|
|
97
|
+
resultSource,
|
|
98
|
+
uiPromptFile,
|
|
99
|
+
pollIntervalMs,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function generateTaskId({ sessionId, serverName, toolName } = {}) {
|
|
104
|
+
const parts = [];
|
|
105
|
+
const sid = normalizeText(sessionId);
|
|
106
|
+
if (sid) parts.push(sid);
|
|
107
|
+
const server = normalizeText(serverName);
|
|
108
|
+
const tool = normalizeText(toolName);
|
|
109
|
+
if (server || tool) parts.push([server, tool].filter(Boolean).join('.'));
|
|
110
|
+
let token = '';
|
|
111
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
112
|
+
token = globalThis.crypto.randomUUID();
|
|
113
|
+
} else {
|
|
114
|
+
token = Date.now().toString(36) + '_' + Math.random().toString(16).slice(2, 10);
|
|
115
|
+
}
|
|
116
|
+
parts.push(token);
|
|
117
|
+
return parts.filter(Boolean).join('_');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resolveAsyncTimeoutMs(options) {
|
|
121
|
+
const timeout = Number(options?.timeoutMs || options?.maxTotalTimeout || options?.timeout || 0);
|
|
122
|
+
if (Number.isFinite(timeout) && timeout > 0) return timeout;
|
|
123
|
+
return DEFAULT_ASYNC_TIMEOUT_MS;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function extractUiPromptResult(entries, requestIds) {
|
|
127
|
+
const ids = new Set(
|
|
128
|
+
(Array.isArray(requestIds) ? requestIds : [])
|
|
129
|
+
.map((id) => (typeof id === 'string' ? id.trim() : ''))
|
|
130
|
+
.filter(Boolean)
|
|
131
|
+
);
|
|
132
|
+
if (ids.size === 0) return null;
|
|
133
|
+
const list = Array.isArray(entries) ? entries : [];
|
|
134
|
+
for (let i = list.length - 1; i >= 0; i -= 1) {
|
|
135
|
+
const entry = list[i];
|
|
136
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
137
|
+
if (entry.type !== 'ui_prompt') continue;
|
|
138
|
+
if (entry.action !== 'request') continue;
|
|
139
|
+
const requestId = typeof entry.requestId === 'string' ? entry.requestId.trim() : '';
|
|
140
|
+
if (!requestId || !ids.has(requestId)) continue;
|
|
141
|
+
const prompt = entry.prompt && typeof entry.prompt === 'object' ? entry.prompt : null;
|
|
142
|
+
if (!prompt || typeof prompt.kind !== 'string' || prompt.kind.trim() !== 'result') continue;
|
|
143
|
+
const markdown =
|
|
144
|
+
typeof prompt.markdown === 'string'
|
|
145
|
+
? prompt.markdown
|
|
146
|
+
: typeof prompt.result === 'string'
|
|
147
|
+
? prompt.result
|
|
148
|
+
: typeof prompt.content === 'string'
|
|
149
|
+
? prompt.content
|
|
150
|
+
: '';
|
|
151
|
+
return markdown;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function waitForUiPromptResult({ taskId, config, callMeta, options } = {}) {
|
|
157
|
+
const id = typeof taskId === 'string' ? taskId.trim() : '';
|
|
158
|
+
if (!id) return { found: false, text: '' };
|
|
159
|
+
|
|
160
|
+
const stateDir = callMeta?.chatos?.uiApp?.stateDir
|
|
161
|
+
? String(callMeta.chatos.uiApp.stateDir)
|
|
162
|
+
: '';
|
|
163
|
+
const uiPromptFile =
|
|
164
|
+
typeof config?.uiPromptFile === 'string' && config.uiPromptFile.trim()
|
|
165
|
+
? config.uiPromptFile.trim()
|
|
166
|
+
: UI_PROMPTS_FILENAME;
|
|
167
|
+
const filePath = path.isAbsolute(uiPromptFile) ? uiPromptFile : stateDir ? path.join(stateDir, uiPromptFile) : '';
|
|
168
|
+
if (!filePath) return { found: false, text: '' };
|
|
169
|
+
|
|
170
|
+
const requestIds = [id, 'mcp-task:' + id];
|
|
171
|
+
const pollIntervalMs = config?.pollIntervalMs || DEFAULT_ASYNC_POLL_MS;
|
|
172
|
+
const timeoutMs = resolveAsyncTimeoutMs(options);
|
|
173
|
+
const deadline = Date.now() + timeoutMs;
|
|
174
|
+
|
|
175
|
+
while (Date.now() < deadline) {
|
|
176
|
+
const entries = readUiPromptsEntries(filePath);
|
|
177
|
+
const text = extractUiPromptResult(entries, requestIds);
|
|
178
|
+
if (text !== null) {
|
|
179
|
+
return { found: true, text };
|
|
180
|
+
}
|
|
181
|
+
await sleepMs(pollIntervalMs);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { found: false, text: '' };
|
|
185
|
+
}
|
|
186
|
+
|
|
74
187
|
function ensureUiPromptsFile(filePath) {
|
|
75
188
|
const normalized = typeof filePath === 'string' ? filePath.trim() : '';
|
|
76
189
|
if (!normalized) return '';
|
|
@@ -2444,6 +2557,7 @@ export async function startSandboxServer({ pluginDir, port = 4399, appId = '' })
|
|
|
2444
2557
|
const toolEntry = toolName ? toolMap.get(toolName) : null;
|
|
2445
2558
|
let args = {};
|
|
2446
2559
|
let resultText = '';
|
|
2560
|
+
let toolCallMeta = effectiveCallMeta;
|
|
2447
2561
|
if (!toolEntry) {
|
|
2448
2562
|
resultText = `[error] Tool not registered: ${toolName || 'unknown'}`;
|
|
2449
2563
|
} else {
|
|
@@ -2455,12 +2569,65 @@ export async function startSandboxServer({ pluginDir, port = 4399, appId = '' })
|
|
|
2455
2569
|
args = {};
|
|
2456
2570
|
}
|
|
2457
2571
|
if (!resultText) {
|
|
2572
|
+
const asyncTaskConfig = normalizeAsyncTaskConfig(effectiveCallMeta?.asyncTask, toolEntry.toolName);
|
|
2573
|
+
let taskId = '';
|
|
2574
|
+
if (asyncTaskConfig) {
|
|
2575
|
+
const taskIdKey = asyncTaskConfig.taskIdKey || '';
|
|
2576
|
+
const existingTaskId =
|
|
2577
|
+
taskIdKey && typeof effectiveCallMeta?.[taskIdKey] === 'string'
|
|
2578
|
+
? effectiveCallMeta[taskIdKey].trim()
|
|
2579
|
+
: '';
|
|
2580
|
+
taskId =
|
|
2581
|
+
existingTaskId ||
|
|
2582
|
+
generateTaskId({
|
|
2583
|
+
sessionId: normalizeText(effectiveCallMeta?.sessionId),
|
|
2584
|
+
serverName: toolEntry.serverName,
|
|
2585
|
+
toolName: toolEntry.toolName,
|
|
2586
|
+
});
|
|
2587
|
+
toolCallMeta = mergeCallMeta(effectiveCallMeta, {
|
|
2588
|
+
...(taskIdKey ? { [taskIdKey]: taskId } : null),
|
|
2589
|
+
stream: false,
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2458
2592
|
const toolResult = await toolEntry.client.callTool({
|
|
2459
2593
|
name: toolEntry.toolName,
|
|
2460
2594
|
arguments: args,
|
|
2461
|
-
...(
|
|
2595
|
+
...(toolCallMeta ? { _meta: toolCallMeta } : {}),
|
|
2462
2596
|
});
|
|
2463
|
-
|
|
2597
|
+
if (asyncTaskConfig) {
|
|
2598
|
+
if (toolResult?.isError) {
|
|
2599
|
+
resultText = formatMcpToolResult(toolEntry.serverName, toolEntry.toolName, toolResult);
|
|
2600
|
+
} else if (asyncTaskConfig.resultSource && asyncTaskConfig.resultSource !== 'ui_prompts') {
|
|
2601
|
+
resultText =
|
|
2602
|
+
'[' +
|
|
2603
|
+
toolEntry.serverName +
|
|
2604
|
+
'/' +
|
|
2605
|
+
toolEntry.toolName +
|
|
2606
|
+
'] ❌ 不支持的异步结果源: ' +
|
|
2607
|
+
asyncTaskConfig.resultSource;
|
|
2608
|
+
} else {
|
|
2609
|
+
const asyncResult = await waitForUiPromptResult({
|
|
2610
|
+
taskId,
|
|
2611
|
+
config: asyncTaskConfig,
|
|
2612
|
+
callMeta: toolCallMeta,
|
|
2613
|
+
});
|
|
2614
|
+
if (asyncResult.found) {
|
|
2615
|
+
const text = typeof asyncResult.text === 'string' ? asyncResult.text.trim() : '';
|
|
2616
|
+
resultText = text || '(无结果内容)';
|
|
2617
|
+
} else {
|
|
2618
|
+
resultText =
|
|
2619
|
+
'[' +
|
|
2620
|
+
toolEntry.serverName +
|
|
2621
|
+
'/' +
|
|
2622
|
+
toolEntry.toolName +
|
|
2623
|
+
'] ❌ 等待交互待办结果超时 (taskId=' +
|
|
2624
|
+
(taskId || 'unknown') +
|
|
2625
|
+
')';
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
} else {
|
|
2629
|
+
resultText = formatMcpToolResult(toolEntry.serverName, toolEntry.toolName, toolResult);
|
|
2630
|
+
}
|
|
2464
2631
|
}
|
|
2465
2632
|
}
|
|
2466
2633
|
toolTrace.push({ tool: toolName || 'unknown', args, result: resultText });
|