openclaw-lark-multi-agent 1.6.0 → 1.7.0
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/README.md +11 -0
- package/README.zh-CN.md +10 -0
- package/dist/feishu-bot.d.ts +16 -0
- package/dist/feishu-bot.js +303 -32
- package/dist/go-controller.d.ts +86 -12
- package/dist/go-controller.js +435 -131
- package/dist/go-execution.d.ts +10 -0
- package/dist/go-execution.js +1 -0
- package/dist/go-runtime-gate.d.ts +32 -0
- package/dist/go-runtime-gate.js +56 -0
- package/dist/message-store.d.ts +83 -0
- package/dist/message-store.js +142 -2
- package/dist/openclaw-client.d.ts +114 -0
- package/dist/openclaw-client.js +166 -12
- package/dist/provider-errors.d.ts +24 -0
- package/dist/provider-errors.js +344 -0
- package/dist/terminal-errors.d.ts +3 -0
- package/dist/terminal-errors.js +2 -1
- package/package.json +8 -3
- package/plugins/lma-go/README.md +22 -0
- package/plugins/lma-go/dist/index.js +35 -0
- package/plugins/lma-go/dist/policy.js +25 -0
- package/plugins/lma-go/dist/registry.js +37 -0
- package/plugins/lma-go/openclaw.plugin.json +1 -0
- package/plugins/lma-go/package.json +1 -0
package/dist/openclaw-client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import WebSocket from "ws";
|
|
2
|
+
import { safeErrorSummary } from "./provider-errors.js";
|
|
2
3
|
import { inspectError, genericFailure } from "./terminal-errors.js";
|
|
3
4
|
import { SessionWaitPaused, normalizeSessionRuntimeStatus, isWaitTimeout, FOREGROUND_WAIT_MS } from "./session-status.js";
|
|
4
5
|
import { createHash, randomUUID } from "crypto";
|
|
@@ -37,6 +38,12 @@ const MAX_INLINE_CONTEXT_BYTES = Number(process.env.OPENCLAW_LARK_MULTI_AGENT_MA
|
|
|
37
38
|
* Full agent pipeline — tools, memory, skills, context management by OpenClaw.
|
|
38
39
|
*/
|
|
39
40
|
export class OpenClawClient {
|
|
41
|
+
goEvidenceObservers = new Set();
|
|
42
|
+
/** Raw full event observer. Observation is NOT a Feishu delivery receipt. */
|
|
43
|
+
observeGoEvidence(callback) {
|
|
44
|
+
this.goEvidenceObservers.add(callback);
|
|
45
|
+
return () => { this.goEvidenceObservers.delete(callback); };
|
|
46
|
+
}
|
|
40
47
|
config;
|
|
41
48
|
ws = null;
|
|
42
49
|
pending = new Map();
|
|
@@ -270,6 +277,16 @@ export class OpenClawClient {
|
|
|
270
277
|
this.agentEvents.get(sk).push(frame.payload);
|
|
271
278
|
}
|
|
272
279
|
}
|
|
280
|
+
if (frame.type === "event" && ["agent", "chat", "session.message"].includes(frame.event)) {
|
|
281
|
+
for (const observer of this.goEvidenceObservers) {
|
|
282
|
+
try {
|
|
283
|
+
observer({ channel: frame.event, payload: frame.payload });
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
console.warn("[OpenClaw] Go evidence observer failed:", error.message);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
273
290
|
// Log all events for debugging
|
|
274
291
|
if (frame.type === "event" && frame.event !== "tick") {
|
|
275
292
|
console.log(`[OpenClaw] Event: ${frame.event}`, JSON.stringify(frame.payload || {}).substring(0, 200));
|
|
@@ -307,7 +324,7 @@ export class OpenClawClient {
|
|
|
307
324
|
data: { deltaText: visibleAssistantText, delta: visibleAssistantText, replace: true },
|
|
308
325
|
});
|
|
309
326
|
}
|
|
310
|
-
this.handleProactiveSessionMessage(rawKey, msg, frame.payload.runId);
|
|
327
|
+
this.handleProactiveSessionMessage(rawKey, msg, frame.payload.runId, frame.payload.sessionId);
|
|
311
328
|
// Tool calls in assistant messages — skip, using agent item events instead
|
|
312
329
|
// (session.message toolCall events are batched, not real-time)
|
|
313
330
|
}
|
|
@@ -403,7 +420,7 @@ export class OpenClawClient {
|
|
|
403
420
|
if (!handshakeDone)
|
|
404
421
|
reject(err);
|
|
405
422
|
else
|
|
406
|
-
console.error("[OpenClaw] WS error:", err
|
|
423
|
+
console.error("[OpenClaw] WS error:", safeErrorSummary(err));
|
|
407
424
|
});
|
|
408
425
|
this.ws.on("close", () => {
|
|
409
426
|
this.connected = false;
|
|
@@ -651,7 +668,7 @@ export class OpenClawClient {
|
|
|
651
668
|
this.flushVerboseAssistantState(rawKey);
|
|
652
669
|
}
|
|
653
670
|
}
|
|
654
|
-
handleProactiveSessionMessage(rawKey, msg, runId) {
|
|
671
|
+
handleProactiveSessionMessage(rawKey, msg, runId, sessionId) {
|
|
655
672
|
const shortKey = rawKey.replace(/^agent:[^:]+:/, "");
|
|
656
673
|
// Proactive assistant text messages. Cron/session-targeted runs often emit
|
|
657
674
|
// structured content arrays rather than a plain string. Extract only visible
|
|
@@ -688,7 +705,7 @@ export class OpenClawClient {
|
|
|
688
705
|
const cb = this.sessionMessageCallbacks.get(rawKey) || this.sessionMessageCallbacks.get(shortKey);
|
|
689
706
|
if (cb) {
|
|
690
707
|
if (runId)
|
|
691
|
-
cb(proactiveText, { runId, sessionKey: this.canonicalSessionKey(rawKey), final: msg?.stopReason === "stop",
|
|
708
|
+
cb(proactiveText, { runId, sessionId, sessionKey: this.canonicalSessionKey(rawKey), final: msg?.stopReason === "stop",
|
|
692
709
|
...(!msg?.stopReason && genericFailure(proactiveText) ? { genericErrorCandidate: true } : {}) });
|
|
693
710
|
else
|
|
694
711
|
cb(proactiveText);
|
|
@@ -706,7 +723,7 @@ export class OpenClawClient {
|
|
|
706
723
|
console.log("[OpenClaw] Reconnected successfully");
|
|
707
724
|
}
|
|
708
725
|
catch (err) {
|
|
709
|
-
console.error("[OpenClaw] Reconnect failed:", err
|
|
726
|
+
console.error("[OpenClaw] Reconnect failed:", safeErrorSummary(err));
|
|
710
727
|
if (this.shouldReconnect)
|
|
711
728
|
this.scheduleReconnect();
|
|
712
729
|
}
|
|
@@ -1067,6 +1084,10 @@ export class OpenClawClient {
|
|
|
1067
1084
|
const evRunId = typeof ev.runId === "string" ? ev.runId : "";
|
|
1068
1085
|
const eventSessionMatches = Boolean(sessionKey && (ev.sessionKey === sessionKey || ev.sessionKey === targetSessionKey || ev.sessionKey === shortSessionKey));
|
|
1069
1086
|
const matchesRun = evRunId ? activeRunIds.has(evRunId) : false;
|
|
1087
|
+
if (options?.exactRunIdentity && !matchesRun) {
|
|
1088
|
+
i++;
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1070
1091
|
if (!sessionKey && ev.sessionKey) {
|
|
1071
1092
|
shortSessionKey = String(ev.sessionKey).replace(/^agent:[^:]+:/, "");
|
|
1072
1093
|
sessionKey = `agent:main:${shortSessionKey}`;
|
|
@@ -1114,7 +1135,7 @@ export class OpenClawClient {
|
|
|
1114
1135
|
continue;
|
|
1115
1136
|
}
|
|
1116
1137
|
let matchesSession = false;
|
|
1117
|
-
if (eventSessionMatches && anchorSeen) {
|
|
1138
|
+
if (!options?.exactRunIdentity && eventSessionMatches && anchorSeen) {
|
|
1118
1139
|
if (!expectedUserText) {
|
|
1119
1140
|
// Backward-compatible mode for tests/internal callers that do not
|
|
1120
1141
|
// provide an anchor: allow session-key matching as before.
|
|
@@ -1233,7 +1254,7 @@ export class OpenClawClient {
|
|
|
1233
1254
|
// Never abort a yielded original run, nor an empty final.
|
|
1234
1255
|
if (!foregroundYielded && latestFinalText)
|
|
1235
1256
|
this.abortChat(targetSessionKey || sessionKey, runId).catch((err) => {
|
|
1236
|
-
console.warn(`[OpenClaw] abort after chatFinal fallback failed:`, err
|
|
1257
|
+
console.warn(`[OpenClaw] abort after chatFinal fallback failed:`, safeErrorSummary(err));
|
|
1237
1258
|
});
|
|
1238
1259
|
if (latestFinalText) {
|
|
1239
1260
|
finish(latestFinalText);
|
|
@@ -1330,8 +1351,8 @@ export class OpenClawClient {
|
|
|
1330
1351
|
// real final wins; if the run truly stalls, the idle timeout falls
|
|
1331
1352
|
// back to this text.
|
|
1332
1353
|
if (this.isRecoverableAgentError(errText)) {
|
|
1333
|
-
pendingRuntimeFailureText = `⚠️ Agent 未正常完成\n原因: ${errText}`;
|
|
1334
|
-
console.warn(`[OpenClaw] recoverable lifecycle error for runId=${evRunId || runId}; deferring (agent may auto-recover): ${errText
|
|
1354
|
+
pendingRuntimeFailureText = `⚠️ Agent 未正常完成\n原因: ${safeErrorSummary(errText)}`;
|
|
1355
|
+
console.warn(`[OpenClaw] recoverable lifecycle error for runId=${evRunId || runId}; deferring (agent may auto-recover): ${safeErrorSummary(errText)}`);
|
|
1335
1356
|
return;
|
|
1336
1357
|
}
|
|
1337
1358
|
if (isWaitTimeout(errText)) {
|
|
@@ -1387,8 +1408,8 @@ export class OpenClawClient {
|
|
|
1387
1408
|
continue;
|
|
1388
1409
|
}
|
|
1389
1410
|
if (this.isRecoverableAgentError(errText)) {
|
|
1390
|
-
pendingRuntimeFailureText = `⚠️ Agent 未正常完成\n原因: ${errText}`;
|
|
1391
|
-
console.warn(`[OpenClaw] recoverable chat error for runId=${evRunId || runId}; deferring: ${errText
|
|
1411
|
+
pendingRuntimeFailureText = `⚠️ Agent 未正常完成\n原因: ${safeErrorSummary(errText)}`;
|
|
1412
|
+
console.warn(`[OpenClaw] recoverable chat error for runId=${evRunId || runId}; deferring: ${safeErrorSummary(errText)}`);
|
|
1392
1413
|
bucket.splice(i, 1);
|
|
1393
1414
|
continue;
|
|
1394
1415
|
}
|
|
@@ -1412,7 +1433,7 @@ export class OpenClawClient {
|
|
|
1412
1433
|
// reject, so an auto-retry probe does not race a zombie run on the
|
|
1413
1434
|
// same session. Idempotent/best-effort.
|
|
1414
1435
|
this.abortChat(targetSessionKey || sessionKey, evRunId || runId).catch((abortErr) => {
|
|
1415
|
-
console.warn(`[OpenClaw] abort after chat error failed:`, abortErr
|
|
1436
|
+
console.warn(`[OpenClaw] abort after chat error failed:`, safeErrorSummary(abortErr));
|
|
1416
1437
|
});
|
|
1417
1438
|
reject(new Error(`Agent error: ${errText}`));
|
|
1418
1439
|
return;
|
|
@@ -2075,6 +2096,139 @@ export class OpenClawClient {
|
|
|
2075
2096
|
if (next)
|
|
2076
2097
|
next();
|
|
2077
2098
|
}
|
|
2099
|
+
/** Public native Goal protocol. Completion deliberately unavailable without host revision CAS. */
|
|
2100
|
+
async createGoNativeGoal(p) {
|
|
2101
|
+
return this.rpc("chat.send", { sessionKey: this.canonicalSessionKey(p.sessionKey), sessionId: p.sessionId,
|
|
2102
|
+
message: p.objective, idempotencyKey: p.operationId, deliver: false,
|
|
2103
|
+
intent: { kind: "session-goal-start", version: 1, issuedAtMs: p.issuedAtMs } });
|
|
2104
|
+
}
|
|
2105
|
+
async readGoNativeGoal(sessionKey, sessionId) {
|
|
2106
|
+
const info = await this.getSessionInfo(sessionKey);
|
|
2107
|
+
if (info?.session?.sessionId !== sessionId || info?.session?.archived)
|
|
2108
|
+
throw new Error("Go session generation changed or archived");
|
|
2109
|
+
return { session: info.session, goal: info.session.goal ?? null, revisionCAS: "unsupported" };
|
|
2110
|
+
}
|
|
2111
|
+
async updateGoNativeGoal(p) {
|
|
2112
|
+
if (!["edit", "pause", "resume", "block"].includes(p.action))
|
|
2113
|
+
throw new Error("Native Goal completion CAS unsupported");
|
|
2114
|
+
const { objective, note, ...identity } = p;
|
|
2115
|
+
return this.rpc("sessions.goal.update", { ...identity, sessionKey: this.canonicalSessionKey(p.sessionKey),
|
|
2116
|
+
...(p.action === "edit" ? { objective } : note === undefined ? {} : { note }) });
|
|
2117
|
+
}
|
|
2118
|
+
/** Admission receipt, not delivery. No restore, random retry key, or generation fallback. */
|
|
2119
|
+
async sendGoGuarded(p) {
|
|
2120
|
+
if (!p.sessionId || !p.idempotencyKey || p.expectedLeafEntryId === undefined)
|
|
2121
|
+
throw new Error("Missing Go send guards");
|
|
2122
|
+
// Own delivery before admission: events can beat the RPC response. Mute only
|
|
2123
|
+
// this brief admission window; afterwards exact run ownership avoids hiding
|
|
2124
|
+
// unrelated proactive work. A lost receipt retains the conservative fence.
|
|
2125
|
+
const release = this.muteProactiveDelivery(p.sessionKey);
|
|
2126
|
+
this.ownDeliveryRun(p.idempotencyKey);
|
|
2127
|
+
try {
|
|
2128
|
+
const result = await this.rpc("chat.send", { ...p, message: p.message + this.bridgeAttachmentHint(p.message),
|
|
2129
|
+
sessionKey: this.canonicalSessionKey(p.sessionKey), deliver: false, suppressCommandInterpretation: true });
|
|
2130
|
+
if (typeof result?.runId !== "string" || !result.runId)
|
|
2131
|
+
throw new Error("Unknown Go admission receipt");
|
|
2132
|
+
this.ownDeliveryRun(result.runId);
|
|
2133
|
+
return { runId: result.runId, sessionId: p.sessionId, idempotencyKey: p.idempotencyKey, raw: result };
|
|
2134
|
+
}
|
|
2135
|
+
finally {
|
|
2136
|
+
release();
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
/** Checker collection accepts only the admitted run, never a session-key fallback. */
|
|
2140
|
+
async collectGoRun(p) {
|
|
2141
|
+
if (p.sessionId && (await this.getGoSessionInfo(p.sessionKey))?.session?.sessionId !== p.sessionId)
|
|
2142
|
+
throw new Error("Go result generation changed");
|
|
2143
|
+
const text = await this.collectReply(p.runId, p.timeoutMs, p.sessionKey, { exactRunIdentity: true });
|
|
2144
|
+
if (p.sessionId && (await this.getGoSessionInfo(p.sessionKey))?.session?.sessionId !== p.sessionId)
|
|
2145
|
+
throw new Error("Go result generation changed");
|
|
2146
|
+
return text;
|
|
2147
|
+
}
|
|
2148
|
+
/** Public committed terminalReply is the authority, including explicit empty
|
|
2149
|
+
* and terminal error. Stream prefaces, lifecycle end alone and timeout are not. */
|
|
2150
|
+
async readGoExecutorRunResult(p, waitMs = 1) {
|
|
2151
|
+
const before = await this.getGoSessionInfo(p.sessionKey);
|
|
2152
|
+
if (before?.session?.sessionId !== p.sessionId || before.session.archived)
|
|
2153
|
+
return null;
|
|
2154
|
+
this.ownDeliveryRun(p.runId);
|
|
2155
|
+
const result = await this.rpc("agent.wait", { runId: p.runId, timeoutMs: waitMs }, Math.max(2500, waitMs + 1500));
|
|
2156
|
+
const after = await this.getGoSessionInfo(p.sessionKey);
|
|
2157
|
+
if (after?.session?.sessionId !== p.sessionId || after.session.archived || result?.runId !== p.runId ||
|
|
2158
|
+
(result.sessionId !== undefined && result.sessionId !== p.sessionId) || !Number.isFinite(result.endedAt) || result.endedAt <= 0 ||
|
|
2159
|
+
(Number.isFinite(result.startedAt) && result.endedAt < result.startedAt) || result.yielded || result.pendingError)
|
|
2160
|
+
return null;
|
|
2161
|
+
const identity = { sessionKey: this.canonicalSessionKey(p.sessionKey), sessionId: p.sessionId, runId: p.runId };
|
|
2162
|
+
if (result.status === "error" || result.status === "timeout")
|
|
2163
|
+
return { ...identity, disposition: "error", text: "", error: String(result.error || `Run ended: ${result.status}`) };
|
|
2164
|
+
if (result.status !== "ok")
|
|
2165
|
+
return null;
|
|
2166
|
+
const reply = result.terminalReply;
|
|
2167
|
+
if (reply?.disposition === "silent")
|
|
2168
|
+
return { ...identity, disposition: "silent", text: "" };
|
|
2169
|
+
if (reply?.disposition === "empty")
|
|
2170
|
+
return { ...identity, disposition: "empty", text: "" };
|
|
2171
|
+
if (reply?.disposition !== "visible" || typeof reply.text !== "string")
|
|
2172
|
+
return null;
|
|
2173
|
+
return { ...identity, disposition: reply.text.trim() === "NO_REPLY" ? "silent" : reply.text.trim() ? "visible" : "empty", text: reply.text };
|
|
2174
|
+
}
|
|
2175
|
+
async collectGoExecutorRun(p) {
|
|
2176
|
+
const deadline = Date.now() + p.timeoutMs;
|
|
2177
|
+
do {
|
|
2178
|
+
const result = await this.readGoExecutorRunResult(p, Math.min(1500, Math.max(1, deadline - Date.now())));
|
|
2179
|
+
if (result)
|
|
2180
|
+
return result;
|
|
2181
|
+
if (Date.now() < deadline)
|
|
2182
|
+
await new Promise(r => setTimeout(r, 250));
|
|
2183
|
+
} while (Date.now() < deadline);
|
|
2184
|
+
throw new Error("Exact Go executor terminal result unknown; no replay");
|
|
2185
|
+
}
|
|
2186
|
+
async readGoRunResult(p) {
|
|
2187
|
+
const result = await this.readGoExecutorRunResult(p);
|
|
2188
|
+
return !result || result.disposition === "error" || result.disposition === "empty" ? null : result.disposition === "silent" ? "NO_REPLY" : result.text;
|
|
2189
|
+
}
|
|
2190
|
+
/** Public generation-pinned original input receipts; absence is unknown, not permission to resend. */
|
|
2191
|
+
async reconcileGoSend(p) {
|
|
2192
|
+
const result = await this.rpc("chat.history", { sessionKey: this.canonicalSessionKey(p.sessionKey),
|
|
2193
|
+
sessionId: p.sessionId, inputRunIds: [p.idempotencyKey], limit: 1 });
|
|
2194
|
+
const receipt = result?.inputReceipts?.find((r) => r.runId === p.idempotencyKey);
|
|
2195
|
+
return receipt ? { runId: receipt.runId, sessionId: p.sessionId, raw: receipt } : null;
|
|
2196
|
+
}
|
|
2197
|
+
async bindGoPolicy(binding) { return this.rpc("lma.go.bind", { binding }); }
|
|
2198
|
+
/** History publicly projects active leaf/run/recovery; describe alone does not. */
|
|
2199
|
+
async getGoSessionInfo(sessionKey) {
|
|
2200
|
+
const history = await this.rpc("chat.history", { sessionKey: this.canonicalSessionKey(sessionKey), limit: 1 });
|
|
2201
|
+
return { session: history?.sessionInfo };
|
|
2202
|
+
}
|
|
2203
|
+
async verifyGoSafety(_executorKey) {
|
|
2204
|
+
const capability = await this.rpc("lma.go.capabilities", {});
|
|
2205
|
+
return { checkerToolsIsolated: capability?.mode === "independent" && capability.checkerToolsIsolated === true,
|
|
2206
|
+
exactTaskAndDeliveryGate: capability?.taskQuery === true, guardedSend: true };
|
|
2207
|
+
}
|
|
2208
|
+
async initializeGoRuntime(p) {
|
|
2209
|
+
const binding = { checkerSessionKey: this.canonicalSessionKey(p.checkerSessionKey), checkerSessionId: p.checkerSessionId,
|
|
2210
|
+
executorSessionKey: this.canonicalSessionKey(p.executorSessionKey), executorSessionId: p.executorSessionId };
|
|
2211
|
+
const result = await this.bindGoPolicy(binding);
|
|
2212
|
+
if (result?.persisted !== true || JSON.stringify(result.binding) !== JSON.stringify(binding))
|
|
2213
|
+
throw new Error("Checker restriction binding not confirmed");
|
|
2214
|
+
return { executorSessionId: p.executorSessionId, checkerSessionId: p.checkerSessionId, checkerRunId: "", executorRunIds: [] };
|
|
2215
|
+
}
|
|
2216
|
+
async readGoRuntimeSnapshot(sessionKey, sessionId) {
|
|
2217
|
+
const key = this.canonicalSessionKey(sessionKey);
|
|
2218
|
+
const before = await this.getGoSessionInfo(key);
|
|
2219
|
+
const query = await this.rpc("lma.go.snapshot", { sessionKey: key, sessionId });
|
|
2220
|
+
const history = await this.rpc("chat.history", { sessionKey: key, sessionId, limit: 1 });
|
|
2221
|
+
const live = history?.sessionInfo;
|
|
2222
|
+
const after = await this.getGoSessionInfo(key);
|
|
2223
|
+
return { sessionKey: key, sessionId, archived: after?.session?.archived === true,
|
|
2224
|
+
complete: false, independent: true, atomicSnapshot: false,
|
|
2225
|
+
queryAvailable: query?.queryAvailable === true && query.sessionKey === key && query.requestedSessionId === sessionId && live?.sessionId === sessionId && before?.session?.sessionId === sessionId && after?.session?.sessionId === sessionId &&
|
|
2226
|
+
before.session.activeLeafEntryId === after.session.activeLeafEntryId && typeof live?.hasActiveRun === "boolean",
|
|
2227
|
+
running: history?.inFlightRun ? true : live?.hasActiveRun, activeRunIds: live?.activeRunIds,
|
|
2228
|
+
queuedInputs: history?.pendingInputs?.total,
|
|
2229
|
+
tasks: (query?.tasks ?? []).map((t) => ({ taskId: t.id, runId: t.runId ?? "", parentTaskId: t.parentTaskId ?? null,
|
|
2230
|
+
state: t.status === "succeeded" ? "completed" : t.status, deliveryState: t.deliveryStatus === "not_applicable" ? "not_required" : t.deliveryStatus })) };
|
|
2231
|
+
}
|
|
2078
2232
|
async chatSend(params) {
|
|
2079
2233
|
const sk = this.shortKey(params.sessionKey);
|
|
2080
2234
|
const fullSessionKey = this.canonicalSessionKey(params.sessionKey);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Locale } from "./i18n.js";
|
|
2
|
+
export type ErrorCategory = "cancelled" | "model" | "model_unavailable" | "model_policy" | "tools" | "context" | "compaction_failed" | "quota" | "rate_limit" | "auth" | "network" | "ttft_timeout" | "total_timeout" | "collector_timeout" | "timeout" | "unknown";
|
|
3
|
+
export interface SafeProviderError {
|
|
4
|
+
category: ErrorCategory;
|
|
5
|
+
status?: number;
|
|
6
|
+
code?: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
param?: string;
|
|
9
|
+
requestId?: string;
|
|
10
|
+
safeMessage?: string;
|
|
11
|
+
}
|
|
12
|
+
/** Bounded parsing of SDK strings (e.g. Agent error: 400 {"error":...})
|
|
13
|
+
* and nested JSON errors. Deeper structured provider fields beat wrapper fields.
|
|
14
|
+
* Message text is used for classification, never returned verbatim. */
|
|
15
|
+
export declare function classifyProviderError(error: unknown): SafeProviderError;
|
|
16
|
+
/** Transparency (user directive 2026-10-09): the visible notice must always
|
|
17
|
+
* carry the original diagnostic so "unclassified" is still actionable. The
|
|
18
|
+
* excerpt is extracted from the error's diagnostic envelope fields and
|
|
19
|
+
* sanitized (no HTML, no URLs, no keys/tokens, capped) — never verbatim
|
|
20
|
+
* arbitrary provider prose. Kept out of classifyProviderError so the
|
|
21
|
+
* classification shape stays stable. */
|
|
22
|
+
export declare function rawErrorExcerpt(error: unknown): string | undefined;
|
|
23
|
+
export declare function safeErrorSummary(error: unknown): string;
|
|
24
|
+
export declare function formatProviderError(error: unknown, botName: string, model: string, locale: Locale): string;
|