@ai-setting/roy-agent-core 1.7.53 → 1.7.55
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/dist/env/agent/index.js +3 -3
- package/dist/env/error-reply.d.ts +184 -0
- package/dist/env/event-source/event-source-handlers.d.ts +9 -23
- package/dist/env/event-source/index.js +6 -6
- package/dist/env/index.js +18 -18
- package/dist/env/llm/adapter-pi-ai.d.ts +147 -2
- package/dist/env/llm/adapter.d.ts +21 -1
- package/dist/env/llm/index.js +2 -2
- package/dist/env/llm/invoke.d.ts +41 -1
- package/dist/env/llm/llm.d.ts +25 -0
- package/dist/env/llm/reasoning-defense.d.ts +104 -0
- package/dist/env/llm/types.d.ts +26 -0
- package/dist/env/prompt/index.js +2 -2
- package/dist/env/prompt/prompts-index.d.ts +1 -1
- package/dist/env/session/index.js +4 -4
- package/dist/env/session/storage/index.js +3 -3
- package/dist/env/task/backend/index.js +0 -2
- package/dist/env/task/backend/types.d.ts +0 -11
- package/dist/env/task/delegate/index.js +2 -2
- package/dist/env/task/index.js +3 -3
- package/dist/env/task/plugins/card-chat-routing.d.ts +42 -89
- package/dist/env/task/plugins/index.js +4 -4
- package/dist/env/task/plugins/lark-card-patch-notifier.d.ts +0 -51
- package/dist/env/task/plugins/lark-cli-task-notify-plugin.d.ts +14 -31
- package/dist/env/task/plugins/per-task-group-chat-plugin.d.ts +4 -4
- package/dist/env/workflow/engine/engine.d.ts +65 -0
- package/dist/env/workflow/engine/index.js +2 -2
- package/dist/env/workflow/index.js +9 -9
- package/dist/env/workflow/nodes/index.js +1 -1
- package/dist/env/workflow/nodes/tool-node.d.ts +6 -0
- package/dist/env/workflow/plugins/index.js +2 -2
- package/dist/env/workflow/tools/index.js +6 -6
- package/dist/env/workflow/tools/workflow-validate-tool.d.ts +11 -2
- package/dist/env/workflow/types/run.d.ts +19 -0
- package/dist/env/workflow/utils/agent-session-metadata.d.ts +13 -0
- package/dist/env/workflow/utils/index.js +1 -1
- package/dist/env/workflow/utils/redact-command-secrets.d.ts +70 -0
- package/dist/env/workflow/utils/shell-dialect-lint.d.ts +75 -0
- package/dist/env/workflow/utils/validate-workflow-definition.d.ts +22 -0
- package/dist/index.js +20 -20
- package/dist/shared/@ai-setting/{roy-agent-core-stnddvx9.js → roy-agent-core-12d2xap8.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-pk1b3kvf.js → roy-agent-core-2b8jtv7m.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-x8n1r7h4.js → roy-agent-core-2bscfrr1.js} +188 -7
- package/dist/shared/@ai-setting/{roy-agent-core-gdsqp6j8.js → roy-agent-core-6masakz4.js} +45 -0
- package/dist/shared/@ai-setting/{roy-agent-core-vvxb9522.js → roy-agent-core-6qxh4xrm.js} +67 -6
- package/dist/shared/@ai-setting/{roy-agent-core-h93v6p6y.js → roy-agent-core-7qy3nffr.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-4vpk40ss.js → roy-agent-core-8eqngasy.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-brwzs8wf.js → roy-agent-core-9h72sk1n.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-na73mym1.js → roy-agent-core-a4vzsn0g.js} +212 -1
- package/dist/shared/@ai-setting/{roy-agent-core-35s1w7tc.js → roy-agent-core-aer5kw1j.js} +142 -70
- package/dist/shared/@ai-setting/{roy-agent-core-j8e92gj8.js → roy-agent-core-b5ew8t0k.js} +0 -59
- package/dist/shared/@ai-setting/{roy-agent-core-4sx6sdb2.js → roy-agent-core-cd62e9p1.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-39xejr39.js → roy-agent-core-e2z1etk4.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-hch2geta.js → roy-agent-core-g36jevrk.js} +14 -183
- package/dist/shared/@ai-setting/{roy-agent-core-ph5qbqcx.js → roy-agent-core-j2nvedt6.js} +465 -128
- package/dist/shared/@ai-setting/{roy-agent-core-p2r27era.js → roy-agent-core-mxdc756a.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-p8ak03s0.js → roy-agent-core-w527pcpe.js} +293 -42
- package/dist/shared/@ai-setting/{roy-agent-core-3tv1q6f2.js → roy-agent-core-ytr2w0vb.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-yd21n6c7.js → roy-agent-core-zvemdfx5.js} +1 -1
- package/dist/test-utils/child-process-mock.d.ts +73 -0
- package/package.json +2 -2
- /package/dist/shared/@ai-setting/{roy-agent-core-zy3hqfwg.js → roy-agent-core-0hq21px2.js} +0 -0
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
invoke,
|
|
11
11
|
invokeNonStream,
|
|
12
12
|
toolParametersToJsonSchema
|
|
13
|
-
} from "./roy-agent-core-
|
|
13
|
+
} from "./roy-agent-core-0hq21px2.js";
|
|
14
14
|
import {
|
|
15
15
|
ContextError,
|
|
16
16
|
ErrorCodes
|
|
@@ -51,47 +51,207 @@ import {
|
|
|
51
51
|
stream as piStream,
|
|
52
52
|
getModel
|
|
53
53
|
} from "@earendil-works/pi-ai";
|
|
54
|
+
|
|
55
|
+
// src/env/llm/reasoning-defense.ts
|
|
56
|
+
var DEEPSEEK_PROVIDER_PREFIX = "deepseek";
|
|
57
|
+
var DEEPSEEK_MODEL_MARKER = "deepseek";
|
|
58
|
+
function shouldInjectReasoningContent(provider, modelId) {
|
|
59
|
+
const p = (provider ?? "").toLowerCase();
|
|
60
|
+
const m = (modelId ?? "").toLowerCase();
|
|
61
|
+
if (p === DEEPSEEK_PROVIDER_PREFIX || p.startsWith(`${DEEPSEEK_PROVIDER_PREFIX}-`)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
return m.includes(DEEPSEEK_MODEL_MARKER);
|
|
65
|
+
}
|
|
66
|
+
function injectReasoningContent(messages) {
|
|
67
|
+
return messages.map((msg) => {
|
|
68
|
+
if (msg?.role !== "assistant")
|
|
69
|
+
return msg;
|
|
70
|
+
if ("reasoning_content" in msg)
|
|
71
|
+
return msg;
|
|
72
|
+
return { ...msg, reasoning_content: "" };
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function applyReasoningDefense(payload, target) {
|
|
76
|
+
if (!payload || typeof payload !== "object")
|
|
77
|
+
return payload;
|
|
78
|
+
if (!shouldInjectReasoningContent(target.provider, target.modelId))
|
|
79
|
+
return payload;
|
|
80
|
+
const messages = payload.messages;
|
|
81
|
+
if (!Array.isArray(messages))
|
|
82
|
+
return payload;
|
|
83
|
+
return { ...payload, messages: injectReasoningContent(messages) };
|
|
84
|
+
}
|
|
85
|
+
function createReasoningDefenseHook(target) {
|
|
86
|
+
return (payload) => applyReasoningDefense(payload, target);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/env/llm/adapter-pi-ai.ts
|
|
54
90
|
var logger = createLogger("llm:adapter-pi-ai");
|
|
55
91
|
var KNOWN_PROVIDER_APIS = {
|
|
56
|
-
anthropic: {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
92
|
+
anthropic: {
|
|
93
|
+
api: "anthropic-messages",
|
|
94
|
+
apiProtocol: "anthropic-messages",
|
|
95
|
+
contextLimit: 200000,
|
|
96
|
+
maxTokensLimit: 8192
|
|
97
|
+
},
|
|
98
|
+
openai: {
|
|
99
|
+
api: "openai-responses",
|
|
100
|
+
apiProtocol: "openai-responses",
|
|
101
|
+
contextLimit: 128000,
|
|
102
|
+
maxTokensLimit: 16384
|
|
103
|
+
},
|
|
104
|
+
google: {
|
|
105
|
+
api: "google-generative-ai",
|
|
106
|
+
apiProtocol: "google-generative-ai",
|
|
107
|
+
contextLimit: 1e6,
|
|
108
|
+
maxTokensLimit: 8192
|
|
109
|
+
},
|
|
110
|
+
"google-vertex": {
|
|
111
|
+
api: "google-vertex",
|
|
112
|
+
apiProtocol: "google-vertex",
|
|
113
|
+
contextLimit: 1e6,
|
|
114
|
+
maxTokensLimit: 8192
|
|
115
|
+
},
|
|
116
|
+
deepseek: {
|
|
117
|
+
api: "openai-completions",
|
|
118
|
+
apiProtocol: "openai-completions",
|
|
119
|
+
contextLimit: 64000,
|
|
120
|
+
maxTokensLimit: 8192
|
|
121
|
+
},
|
|
122
|
+
glm: {
|
|
123
|
+
api: "openai-completions",
|
|
124
|
+
apiProtocol: "openai-completions",
|
|
125
|
+
contextLimit: 128000,
|
|
126
|
+
maxTokensLimit: 8192
|
|
127
|
+
},
|
|
128
|
+
xai: {
|
|
129
|
+
api: "openai-completions",
|
|
130
|
+
apiProtocol: "openai-completions",
|
|
131
|
+
contextLimit: 131072,
|
|
132
|
+
maxTokensLimit: 8192
|
|
133
|
+
},
|
|
134
|
+
groq: {
|
|
135
|
+
api: "openai-completions",
|
|
136
|
+
apiProtocol: "openai-completions",
|
|
137
|
+
contextLimit: 32768,
|
|
138
|
+
maxTokensLimit: 8192
|
|
139
|
+
},
|
|
140
|
+
minimax: {
|
|
141
|
+
api: "anthropic-messages",
|
|
142
|
+
apiProtocol: "anthropic-messages",
|
|
143
|
+
contextLimit: 128000,
|
|
144
|
+
maxTokensLimit: 8192
|
|
145
|
+
},
|
|
146
|
+
"minimax-cn": {
|
|
147
|
+
api: "anthropic-messages",
|
|
148
|
+
apiProtocol: "anthropic-messages",
|
|
149
|
+
contextLimit: 128000,
|
|
150
|
+
maxTokensLimit: 8192
|
|
151
|
+
},
|
|
152
|
+
moonshotai: {
|
|
153
|
+
api: "openai-completions",
|
|
154
|
+
apiProtocol: "openai-completions",
|
|
155
|
+
contextLimit: 128000,
|
|
156
|
+
maxTokensLimit: 8192
|
|
157
|
+
},
|
|
158
|
+
"kimi-coding": {
|
|
159
|
+
api: "openai-completions",
|
|
160
|
+
apiProtocol: "openai-completions",
|
|
161
|
+
contextLimit: 128000,
|
|
162
|
+
maxTokensLimit: 8192
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var PROVIDER_ENDPOINT_APIS = {
|
|
166
|
+
minimax: [
|
|
167
|
+
{ pathIncludes: "/anthropic", apiProtocol: "anthropic-messages" },
|
|
168
|
+
{ pathIncludes: "/v1", apiProtocol: "openai-completions" }
|
|
169
|
+
],
|
|
170
|
+
"minimax-cn": [
|
|
171
|
+
{ pathIncludes: "/anthropic", apiProtocol: "anthropic-messages" },
|
|
172
|
+
{ pathIncludes: "/v1", apiProtocol: "openai-completions" }
|
|
173
|
+
]
|
|
174
|
+
};
|
|
175
|
+
var ENDPOINT_RULES_SORTED = Object.fromEntries(Object.entries(PROVIDER_ENDPOINT_APIS).map(([provider, rules]) => [
|
|
176
|
+
provider,
|
|
177
|
+
[...rules].sort((a, b) => b.pathIncludes.length - a.pathIncludes.length)
|
|
178
|
+
]));
|
|
179
|
+
var PROVIDER_API_OVERRIDES = {
|
|
180
|
+
"minimax-responses": "openai-responses",
|
|
181
|
+
"minimax-openai": "openai-completions"
|
|
67
182
|
};
|
|
68
|
-
function
|
|
183
|
+
function resolveApiProtocol(provider, baseURL, explicitProtocol) {
|
|
184
|
+
if (explicitProtocol)
|
|
185
|
+
return explicitProtocol;
|
|
186
|
+
if (PROVIDER_API_OVERRIDES[provider])
|
|
187
|
+
return PROVIDER_API_OVERRIDES[provider];
|
|
188
|
+
const endpointRules = ENDPOINT_RULES_SORTED[provider];
|
|
189
|
+
if (endpointRules) {
|
|
190
|
+
for (const rule of endpointRules) {
|
|
191
|
+
if (baseURL?.includes(rule.pathIncludes))
|
|
192
|
+
return rule.apiProtocol;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const declared = KNOWN_PROVIDER_APIS[provider]?.apiProtocol;
|
|
196
|
+
if (declared)
|
|
197
|
+
return declared;
|
|
198
|
+
if (baseURL?.includes("/responses"))
|
|
199
|
+
return "openai-responses";
|
|
200
|
+
if (baseURL?.includes("anthropic")) {
|
|
201
|
+
logger.warn(`[adapter-pi-ai] provider "${provider}" 的协议靠 baseURL 含 "anthropic" 推断为 anthropic-messages;` + `建议在 KNOWN_PROVIDER_APIS 或调用处显式声明 apiProtocol(Task #4383 P1)`, { provider, inferred_api_protocol: "anthropic-messages", base_url: baseURL });
|
|
202
|
+
return "anthropic-messages";
|
|
203
|
+
}
|
|
204
|
+
return KNOWN_PROVIDER_APIS[provider]?.api || "openai-completions";
|
|
205
|
+
}
|
|
206
|
+
var REASONING_COMPAT_PIN = {
|
|
207
|
+
supportsDeveloperRole: false,
|
|
208
|
+
thinkingFormat: "openai",
|
|
209
|
+
supportsReasoningEffort: false
|
|
210
|
+
};
|
|
211
|
+
var REASONING_SAFE_PROTOCOLS = new Set([
|
|
212
|
+
"openai-completions",
|
|
213
|
+
"anthropic-messages"
|
|
214
|
+
]);
|
|
215
|
+
var warnedReasoningRefusals = new Set;
|
|
216
|
+
function resolveModelReasoning(provider, modelId, capabilities, apiProtocol) {
|
|
217
|
+
const requested = capabilities?.reasoning === true;
|
|
218
|
+
if (!requested)
|
|
219
|
+
return false;
|
|
220
|
+
if (!apiProtocol || !REASONING_SAFE_PROTOCOLS.has(apiProtocol)) {
|
|
221
|
+
const key = `${provider}:${apiProtocol ?? "<unresolved>"}`;
|
|
222
|
+
if (!warnedReasoningRefusals.has(key)) {
|
|
223
|
+
warnedReasoningRefusals.add(key);
|
|
224
|
+
logger.warn(`[adapter-pi-ai] provider "${provider}" (${apiProtocol ?? "unresolved"}) 的 capabilities.reasoning=true 被忽略:` + `该协议无法通过 compat pin 保持请求体字节一致,打开会破坏 prompt cache 前缀(Task #4403)`, { provider, model_id: modelId, api_protocol: apiProtocol });
|
|
225
|
+
}
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
function buildPiCacheOptions(args) {
|
|
231
|
+
const out = {};
|
|
232
|
+
if (args.sessionId)
|
|
233
|
+
out.sessionId = args.sessionId;
|
|
234
|
+
if (args.apiProtocol === "openai-completions") {
|
|
235
|
+
out.cacheRetention = args.cacheRetention ?? "long";
|
|
236
|
+
} else if (args.cacheRetention) {
|
|
237
|
+
out.cacheRetention = args.cacheRetention;
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
function resolvePiModel(provider, modelId, baseURL, capabilities) {
|
|
69
242
|
const builtIn = getModel(provider, modelId);
|
|
70
243
|
if (builtIn)
|
|
71
244
|
return baseURL ? { ...builtIn, baseUrl: baseURL } : builtIn;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
"minimax-openai": "openai-completions"
|
|
75
|
-
};
|
|
76
|
-
let configuredApi;
|
|
77
|
-
if (providerApiOverride[provider]) {
|
|
78
|
-
configuredApi = providerApiOverride[provider];
|
|
79
|
-
} else if (provider === "minimax" || provider === "minimax-cn") {
|
|
80
|
-
configuredApi = baseURL?.includes("anthropic") ? "anthropic-messages" : "openai-completions";
|
|
81
|
-
} else if (baseURL?.includes("/responses")) {
|
|
82
|
-
configuredApi = "openai-responses";
|
|
83
|
-
} else if (baseURL?.includes("anthropic")) {
|
|
84
|
-
configuredApi = "anthropic-messages";
|
|
85
|
-
} else {
|
|
86
|
-
configuredApi = KNOWN_PROVIDER_APIS[provider]?.api || "openai-completions";
|
|
87
|
-
}
|
|
245
|
+
const configuredApi = resolveApiProtocol(provider, baseURL);
|
|
246
|
+
const reasoning = resolveModelReasoning(provider, modelId, capabilities, configuredApi);
|
|
88
247
|
return {
|
|
89
248
|
id: modelId,
|
|
90
249
|
name: modelId,
|
|
91
250
|
api: configuredApi,
|
|
92
251
|
provider,
|
|
93
252
|
baseUrl: baseURL || "",
|
|
94
|
-
reasoning
|
|
253
|
+
reasoning,
|
|
254
|
+
...reasoning ? { compat: { ...REASONING_COMPAT_PIN } } : {},
|
|
95
255
|
input: ["text", "image"],
|
|
96
256
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
97
257
|
contextWindow: KNOWN_PROVIDER_APIS[provider]?.contextLimit || 128000,
|
|
@@ -214,7 +374,7 @@ class EmbeddingNotSupportedError extends Error {
|
|
|
214
374
|
class PiAiAdapter {
|
|
215
375
|
name = "pi-ai";
|
|
216
376
|
async chat(req) {
|
|
217
|
-
const model = resolvePiModel(req.provider, req.modelId, req.baseURL);
|
|
377
|
+
const model = resolvePiModel(req.provider, req.modelId, req.baseURL, req.capabilities);
|
|
218
378
|
const ctx = {
|
|
219
379
|
systemPrompt: [req.systemPrompt, extractSystemPrompt(req.messages)].filter(Boolean).join(`
|
|
220
380
|
|
|
@@ -228,12 +388,21 @@ class PiAiAdapter {
|
|
|
228
388
|
temperature: req.temperature,
|
|
229
389
|
maxTokens: req.maxTokens,
|
|
230
390
|
signal: req.signal,
|
|
391
|
+
...buildPiCacheOptions({
|
|
392
|
+
apiProtocol: model.api,
|
|
393
|
+
sessionId: req.sessionId,
|
|
394
|
+
cacheRetention: req.cacheRetention
|
|
395
|
+
}),
|
|
396
|
+
onPayload: createReasoningDefenseHook({
|
|
397
|
+
provider: req.provider,
|
|
398
|
+
modelId: req.modelId
|
|
399
|
+
}),
|
|
231
400
|
...piRequestExtras(req)
|
|
232
401
|
});
|
|
233
402
|
return fromAssistantMessage(msg);
|
|
234
403
|
}
|
|
235
404
|
async stream(req) {
|
|
236
|
-
const model = resolvePiModel(req.provider, req.modelId, req.baseURL);
|
|
405
|
+
const model = resolvePiModel(req.provider, req.modelId, req.baseURL, req.capabilities);
|
|
237
406
|
const ctx = {
|
|
238
407
|
systemPrompt: [req.systemPrompt, extractSystemPrompt(req.messages)].filter(Boolean).join(`
|
|
239
408
|
|
|
@@ -241,7 +410,23 @@ class PiAiAdapter {
|
|
|
241
410
|
messages: toPiMessages(req.messages),
|
|
242
411
|
tools: toPiTools(req.tools)
|
|
243
412
|
};
|
|
244
|
-
const eventStream = piStream(model, ctx, {
|
|
413
|
+
const eventStream = piStream(model, ctx, {
|
|
414
|
+
apiKey: req.apiKey,
|
|
415
|
+
baseURL: req.baseURL,
|
|
416
|
+
temperature: req.temperature,
|
|
417
|
+
maxTokens: req.maxTokens,
|
|
418
|
+
signal: req.signal,
|
|
419
|
+
...buildPiCacheOptions({
|
|
420
|
+
apiProtocol: model.api,
|
|
421
|
+
sessionId: req.sessionId,
|
|
422
|
+
cacheRetention: req.cacheRetention
|
|
423
|
+
}),
|
|
424
|
+
onPayload: createReasoningDefenseHook({
|
|
425
|
+
provider: req.provider,
|
|
426
|
+
modelId: req.modelId
|
|
427
|
+
}),
|
|
428
|
+
...piRequestExtras(req)
|
|
429
|
+
});
|
|
245
430
|
return { events: eventStream, result: async () => fromAssistantMessage(await eventStream.result()) };
|
|
246
431
|
}
|
|
247
432
|
async embed(_req) {
|
|
@@ -893,6 +1078,39 @@ import * as fs from "node:fs/promises";
|
|
|
893
1078
|
import * as os from "node:os";
|
|
894
1079
|
import * as path from "node:path";
|
|
895
1080
|
var logger2 = createLogger("llm");
|
|
1081
|
+
function normalizeUsage(usage) {
|
|
1082
|
+
if (!usage)
|
|
1083
|
+
return;
|
|
1084
|
+
const normalized = {
|
|
1085
|
+
promptTokens: usage.promptTokens ?? 0,
|
|
1086
|
+
completionTokens: usage.completionTokens ?? 0,
|
|
1087
|
+
totalTokens: usage.totalTokens ?? 0
|
|
1088
|
+
};
|
|
1089
|
+
if (usage.cacheRead !== undefined)
|
|
1090
|
+
normalized.cacheRead = usage.cacheRead;
|
|
1091
|
+
if (usage.cacheWrite !== undefined)
|
|
1092
|
+
normalized.cacheWrite = usage.cacheWrite;
|
|
1093
|
+
if (usage.cacheMiss !== undefined)
|
|
1094
|
+
normalized.cacheMiss = usage.cacheMiss;
|
|
1095
|
+
if (usage.cacheRead !== undefined || usage.cacheWrite !== undefined) {
|
|
1096
|
+
const reads = usage.cacheRead ?? 0;
|
|
1097
|
+
const writes = usage.cacheWrite ?? 0;
|
|
1098
|
+
const cacheSum = reads + writes;
|
|
1099
|
+
const prompt = usage.promptTokens ?? 0;
|
|
1100
|
+
const completion = usage.completionTokens ?? 0;
|
|
1101
|
+
const nonCachedTotal = prompt + completion + cacheSum;
|
|
1102
|
+
const aiSdkTotal = prompt + completion;
|
|
1103
|
+
const isPiAiShape = Math.abs(usage.totalTokens - nonCachedTotal) <= 1;
|
|
1104
|
+
if (isPiAiShape) {
|
|
1105
|
+
normalized.promptTokensTotal = prompt + cacheSum;
|
|
1106
|
+
} else if (Math.abs(usage.totalTokens - aiSdkTotal) <= 1) {
|
|
1107
|
+
normalized.promptTokensTotal = Math.max(prompt, cacheSum);
|
|
1108
|
+
} else {
|
|
1109
|
+
normalized.promptTokensTotal = Math.max(prompt, cacheSum);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return normalized;
|
|
1113
|
+
}
|
|
896
1114
|
async function resolveFallbackProviderConfig(providerId) {
|
|
897
1115
|
const home = os.homedir();
|
|
898
1116
|
const authPath = path.join(home, ".roy-agent", "auth.json");
|
|
@@ -929,6 +1147,7 @@ class LLMComponent extends BaseComponent {
|
|
|
929
1147
|
configWatcher;
|
|
930
1148
|
cachedAdapter;
|
|
931
1149
|
cachedAdapterProvider;
|
|
1150
|
+
adapterOverride;
|
|
932
1151
|
constructor() {
|
|
933
1152
|
super();
|
|
934
1153
|
}
|
|
@@ -1146,6 +1365,7 @@ class LLMComponent extends BaseComponent {
|
|
|
1146
1365
|
model: `${providerId}/${model}`,
|
|
1147
1366
|
apiKey,
|
|
1148
1367
|
baseURL: provider?.baseURL,
|
|
1368
|
+
...capabilities ? { capabilities } : {},
|
|
1149
1369
|
...effectiveThinkingInText?.enabled ? {
|
|
1150
1370
|
options: {
|
|
1151
1371
|
thinkingInText: {
|
|
@@ -1164,6 +1384,8 @@ class LLMComponent extends BaseComponent {
|
|
|
1164
1384
|
};
|
|
1165
1385
|
}
|
|
1166
1386
|
getAdapter() {
|
|
1387
|
+
if (this.adapterOverride)
|
|
1388
|
+
return this.adapterOverride;
|
|
1167
1389
|
const currentProvider = process.env.ROY_LLM_PROVIDER || "pi-ai";
|
|
1168
1390
|
if (this.cachedAdapter && this.cachedAdapterProvider === currentProvider) {
|
|
1169
1391
|
return this.cachedAdapter;
|
|
@@ -1172,6 +1394,9 @@ class LLMComponent extends BaseComponent {
|
|
|
1172
1394
|
this.cachedAdapterProvider = currentProvider;
|
|
1173
1395
|
return this.cachedAdapter;
|
|
1174
1396
|
}
|
|
1397
|
+
__setAdapterForTest(adapter) {
|
|
1398
|
+
this.adapterOverride = adapter;
|
|
1399
|
+
}
|
|
1175
1400
|
invalidateAdapterCache() {
|
|
1176
1401
|
this.cachedAdapter = undefined;
|
|
1177
1402
|
this.cachedAdapterProvider = undefined;
|
|
@@ -1194,7 +1419,8 @@ class LLMComponent extends BaseComponent {
|
|
|
1194
1419
|
apiKey: ctx.invokeConfig.apiKey || "",
|
|
1195
1420
|
baseURL: ctx.invokeConfig.baseURL,
|
|
1196
1421
|
signal: ctx.abortSignal,
|
|
1197
|
-
sessionId: request.context?.sessionId
|
|
1422
|
+
sessionId: request.context?.sessionId,
|
|
1423
|
+
capabilities: ctx.invokeConfig.capabilities
|
|
1198
1424
|
};
|
|
1199
1425
|
let output = {
|
|
1200
1426
|
content: "",
|
|
@@ -1246,11 +1472,14 @@ class LLMComponent extends BaseComponent {
|
|
|
1246
1472
|
output.finishReason = parsed.tool_calls?.length ? "tool-calls" : "stop";
|
|
1247
1473
|
output.toolCalls = parsed.tool_calls || [];
|
|
1248
1474
|
if (parsed.usage) {
|
|
1249
|
-
finishUsage = {
|
|
1475
|
+
finishUsage = normalizeUsage({
|
|
1250
1476
|
promptTokens: parsed.usage.promptTokens ?? parsed.usage.inputTokens ?? 0,
|
|
1251
1477
|
completionTokens: parsed.usage.completionTokens ?? parsed.usage.outputTokens ?? 0,
|
|
1252
|
-
totalTokens: parsed.usage.totalTokens ?? 0
|
|
1253
|
-
|
|
1478
|
+
totalTokens: parsed.usage.totalTokens ?? 0,
|
|
1479
|
+
cacheRead: parsed.usage.cacheRead,
|
|
1480
|
+
cacheWrite: parsed.usage.cacheWrite,
|
|
1481
|
+
cacheMiss: parsed.usage.cacheMiss
|
|
1482
|
+
});
|
|
1254
1483
|
}
|
|
1255
1484
|
output.usage = finishUsage;
|
|
1256
1485
|
} catch {
|
|
@@ -1263,11 +1492,7 @@ class LLMComponent extends BaseComponent {
|
|
|
1263
1492
|
output.finishReason = resp.finishReason;
|
|
1264
1493
|
output.toolCalls = resp.toolCalls || [];
|
|
1265
1494
|
if (resp.usage) {
|
|
1266
|
-
finishUsage =
|
|
1267
|
-
promptTokens: resp.usage.promptTokens ?? 0,
|
|
1268
|
-
completionTokens: resp.usage.completionTokens ?? 0,
|
|
1269
|
-
totalTokens: resp.usage.totalTokens ?? 0
|
|
1270
|
-
};
|
|
1495
|
+
finishUsage = normalizeUsage(resp.usage);
|
|
1271
1496
|
}
|
|
1272
1497
|
output.usage = finishUsage;
|
|
1273
1498
|
return this.finalizeOutput(request, ctx, output, finishUsage, startTime, adapter.name);
|
|
@@ -1362,6 +1587,7 @@ class LLMComponent extends BaseComponent {
|
|
|
1362
1587
|
throw contextError;
|
|
1363
1588
|
}
|
|
1364
1589
|
}
|
|
1590
|
+
this.emitCompletedEvent(request, `${ctx.providerId}/${ctx.model}`, output.content, output.usage, adapterName);
|
|
1365
1591
|
return {
|
|
1366
1592
|
output,
|
|
1367
1593
|
providerId: ctx.providerId,
|
|
@@ -1369,6 +1595,31 @@ class LLMComponent extends BaseComponent {
|
|
|
1369
1595
|
latencyMs
|
|
1370
1596
|
};
|
|
1371
1597
|
}
|
|
1598
|
+
emitCompletedEvent(request, model, content, usage, adapterName) {
|
|
1599
|
+
try {
|
|
1600
|
+
this.env?.pushEnvEvent?.({
|
|
1601
|
+
id: randomUUID(),
|
|
1602
|
+
type: "llm.completed",
|
|
1603
|
+
timestamp: Date.now(),
|
|
1604
|
+
metadata: {
|
|
1605
|
+
source: "llm.component.invoke",
|
|
1606
|
+
sessionId: request.context?.sessionId,
|
|
1607
|
+
messageId: request.context?.messageId
|
|
1608
|
+
},
|
|
1609
|
+
payload: {
|
|
1610
|
+
type: "completed",
|
|
1611
|
+
content,
|
|
1612
|
+
metadata: {
|
|
1613
|
+
model,
|
|
1614
|
+
usage,
|
|
1615
|
+
adapter: adapterName
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
} catch (err) {
|
|
1620
|
+
logger2.warn("Failed to push llm.completed event", { error: String(err) });
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1372
1623
|
getApiKey(providerId) {
|
|
1373
1624
|
const providers = this.getLLMConfig("llm.providers", {});
|
|
1374
1625
|
return providers?.[providerId]?.apiKey;
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
} from "./roy-agent-core-dv714bqh.js";
|
|
11
11
|
import {
|
|
12
12
|
SessionMessageConverter
|
|
13
|
-
} from "./roy-agent-core-
|
|
13
|
+
} from "./roy-agent-core-2b8jtv7m.js";
|
|
14
14
|
import {
|
|
15
15
|
parseToolArgsStrict,
|
|
16
16
|
safeParseToolArgs,
|
|
17
17
|
withTimeout
|
|
18
|
-
} from "./roy-agent-core-
|
|
18
|
+
} from "./roy-agent-core-0hq21px2.js";
|
|
19
19
|
import {
|
|
20
20
|
getCurrentBgTaskId
|
|
21
21
|
} from "./roy-agent-core-c1v263jn.js";
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared `child_process` mock factory for `mock.module(...)`.
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么需要它(Task #4420)
|
|
5
|
+
*
|
|
6
|
+
* Bun 当前对 `mock.module("child_process", factory)` 采用 **ESM exports 语义**:
|
|
7
|
+
* factory 返回的对象被当作模块的**完整导出面**,而不是「在真实模块上打补丁」。
|
|
8
|
+
* 因此只要少提供一个命名导出,任何**静态** `import { x } from "child_process"`
|
|
9
|
+
* 该导出的模块都会在**模块加载期**直接抛:
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* SyntaxError: Export named 'exec' not found in module 'node:child_process'.
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* 更糟的是:
|
|
16
|
+
* 1. 报错指向**被 import 的模块**(如 `event-source-handlers.ts`),
|
|
17
|
+
* 而不是写了不完整 mock 的**测试文件** —— 极难定位。
|
|
18
|
+
* 2. mock.module 在同一 `bun test` 进程内**跨文件泄漏**(bun 不像 jest
|
|
19
|
+
* 每文件独立 registry),所以还会顺带炸掉后面无关的文件。
|
|
20
|
+
*
|
|
21
|
+
* ## 历史事故(同一个坑踩了 3 次)
|
|
22
|
+
*
|
|
23
|
+
* - Task #2372:`event-source-handlers-builtin.test.ts` 只 mock `spawn`,
|
|
24
|
+
* 炸掉后面所有静态 import `spawnSync` 的模块(见
|
|
25
|
+
* `util/process/process-tree-killer.ts` 里的 lazy-import 规避注释)。
|
|
26
|
+
* - Task #4331:源码 `event-source-handlers.ts` 新增 `exec as execSync`,
|
|
27
|
+
* 但该测试的 mock 没跟上 → 整个文件 48/64 条测试挂红(Task #4420 修的就是这个)。
|
|
28
|
+
*
|
|
29
|
+
* ## 解决方式
|
|
30
|
+
*
|
|
31
|
+
* **导出面从真实模块自省派生**,永不手写清单:
|
|
32
|
+
* - `require("node:child_process")` 枚举出真实存在的导出名
|
|
33
|
+
* - 对每个导出,默认**透传真实实现**,只把测试显式 `overrides` 的键替换掉
|
|
34
|
+
* - 额外补一个 `default`(ESM namespace 有 `default`,`require()` 没有 ——
|
|
35
|
+
* 这也是历史 mock 需要手写 `default: {...}` 的原因,现在自动生成)
|
|
36
|
+
*
|
|
37
|
+
* 这样即使未来 Node/Bun 新增导出,或源码新增 `import { execFile }`,
|
|
38
|
+
* mock 也不会因为「缺导出」再次炸掉整个测试文件。
|
|
39
|
+
*
|
|
40
|
+
* ## 用法
|
|
41
|
+
*
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { createChildProcessMockModule } from "../../../test-utils/child-process-mock";
|
|
44
|
+
*
|
|
45
|
+
* beforeEach(() => {
|
|
46
|
+
* mock.module("child_process", () =>
|
|
47
|
+
* createChildProcessMockModule({ spawn: spawnMock }),
|
|
48
|
+
* );
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* 注意:`mock.module` 的 specifier 用 `"child_process"` 还是
|
|
53
|
+
* `"node:child_process"` 都能命中(Bun 会把两者归一),helper 内部统一
|
|
54
|
+
* 从 `node:child_process` 自省。
|
|
55
|
+
*/
|
|
56
|
+
/**
|
|
57
|
+
* 当前 Node/Bun 上 `node:child_process` 的完整命名导出清单(运行时自省)。
|
|
58
|
+
*
|
|
59
|
+
* 在 Bun 上为:`ChildProcess`, `exec`, `execFile`, `execFileSync`,
|
|
60
|
+
* `execSync`, `fork`, `spawn`, `spawnSync`。
|
|
61
|
+
*/
|
|
62
|
+
export declare const CHILD_PROCESS_EXPORT_NAMES: readonly string[];
|
|
63
|
+
type ChildProcessModule = Record<string, any>;
|
|
64
|
+
/**
|
|
65
|
+
* 构造一个**导出面完整**的 `child_process` mock 模块对象。
|
|
66
|
+
*
|
|
67
|
+
* @param overrides 需要替换为测试替身(spy/stub)的导出。其余导出保持真实实现,
|
|
68
|
+
* 既保证导出面完整,又避免把未测行为也一起假掉。
|
|
69
|
+
* @returns 可直接被 `mock.module("child_process", () => ...)` 返回的模块对象,
|
|
70
|
+
* 含 `default` 自引用(`default` 上的键与命名导出保持一致)。
|
|
71
|
+
*/
|
|
72
|
+
export declare function createChildProcessMockModule(overrides?: ChildProcessModule): ChildProcessModule;
|
|
73
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-setting/roy-agent-core",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.55",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core SDK for roy-agent - Environment, Components, Tools, Sessions, Tasks",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"clean": "rm -rf dist",
|
|
36
36
|
"typecheck": "npx tsc --noEmit --skipLibCheck --project tsconfig.json",
|
|
37
37
|
"test:llm": "bun test test/llm/",
|
|
38
|
-
"test:llm:fast": "bun test --parallel
|
|
38
|
+
"test:llm:fast": "bun test --parallel=4 test/llm/"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@ai-sdk/anthropic": "^3.0.0",
|
|
File without changes
|