akanjs 3.0.0-alpha.41 → 3.0.0-alpha.42
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/dictionary/agent.dictionary.ts +4 -4
- package/dictionary/base.dictionary.ts +4 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/package.json +1 -1
- package/service/predefinedAdaptor/deepseekLlm.ts +33 -4
- package/service/predefinedAdaptor/llm.adaptor.ts +11 -2
- package/signal/agentTurnStream.ts +13 -1
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/service/predefinedAdaptor/deepseekLlm.d.ts +6 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +11 -2
- package/types/signal/agentTurnStream.d.ts +8 -0
- package/types/ui/Agent/Chat.d.ts +7 -2
- package/types/vendor/use-agentic/AgentSession.d.ts +16 -0
- package/types/vendor/use-agentic/Compaction.d.ts +45 -0
- package/types/vendor/use-agentic/index.d.ts +1 -0
- package/types/vendor/use-agentic/types.d.ts +13 -1
- package/ui/Agent/Bubble.tsx +10 -1
- package/ui/Agent/Chat.tsx +14 -1
- package/ui/Agent/ChatCommands.ts +11 -1
- package/ui/Agent/fetchRunner.ts +17 -3
- package/ui/Load/Units.tsx +2 -2
- package/vendor/use-agentic/AgentSession.ts +96 -1
- package/vendor/use-agentic/Compaction.ts +110 -0
- package/vendor/use-agentic/WIRE.md +6 -1
- package/vendor/use-agentic/httpRunner.ts +21 -5
- package/vendor/use-agentic/index.ts +1 -0
- package/vendor/use-agentic/types.ts +11 -1
|
@@ -21,11 +21,11 @@ export const agentDictionary = serviceDictionary(["en", "ko"])
|
|
|
21
21
|
}))
|
|
22
22
|
.error({
|
|
23
23
|
llmUnavailable: [
|
|
24
|
-
"The agent
|
|
25
|
-
"
|
|
24
|
+
"The agent is unavailable — this app has no language model configured",
|
|
25
|
+
"에이전트를 사용할 수 없습니다. 이 앱에 언어 모델이 설정되어 있지 않습니다",
|
|
26
26
|
],
|
|
27
27
|
deepseekRequestFailed: [
|
|
28
|
-
"DeepSeek
|
|
29
|
-
"DeepSeek
|
|
28
|
+
"DeepSeek refused this turn with status {status}. Reason: {reason}",
|
|
29
|
+
"DeepSeek가 이번 턴을 거절했습니다 (status {status}). 사유: {reason}",
|
|
30
30
|
],
|
|
31
31
|
});
|
|
@@ -64,6 +64,7 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
|
|
|
64
64
|
agentKeepGoing: ["Keep going", "계속하기"],
|
|
65
65
|
agentCmdNew: ["Start a new conversation", "새 대화 시작"],
|
|
66
66
|
agentCmdRetry: ["Send the last message again", "마지막 메시지 다시 보내기"],
|
|
67
|
+
agentCmdCompact: ["Summarize the conversation so far", "지금까지의 대화 요약하기"],
|
|
67
68
|
agentCmdCopy: ["Copy this conversation", "이 대화 복사"],
|
|
68
69
|
agentCmdHelp: ["What you can do here", "여기서 할 수 있는 것"],
|
|
69
70
|
agentCmdTools: ["List this screen's tools", "이 화면의 툴 목록"],
|
|
@@ -76,6 +77,9 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
|
|
|
76
77
|
"에이전트는 혼자 결정하면 안 되는 일을 하기 전에 물어보며, 중지는 진행 중인 턴을 끝냅니다.",
|
|
77
78
|
],
|
|
78
79
|
agentNothingToRetry: ["There is no message to send again.", "다시 보낼 메시지가 없습니다."],
|
|
80
|
+
agentNothingToCompact: ["There is nothing to summarize yet.", "아직 요약할 대화가 없습니다."],
|
|
81
|
+
agentCompacted: ["Summarized the conversation so far.", "지금까지의 대화를 요약했습니다."],
|
|
82
|
+
agentSummary: ["Summary of the earlier conversation", "이전 대화 요약"],
|
|
79
83
|
agentBusy: ["A turn is still running. Stop it first.", "진행 중인 턴이 있습니다. 먼저 중지하세요."],
|
|
80
84
|
agentCopied: ["Conversation copied to the clipboard.", "대화를 클립보드에 복사했습니다."],
|
|
81
85
|
agentCopyFailed: ["Could not reach the clipboard.", "클립보드에 접근할 수 없습니다."],
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -54,8 +54,9 @@ export class DeepseekLlm
|
|
|
54
54
|
const body = await this.#apiStream("/chat/completions", DeepseekLlm.requestBody(this.#model, request, true));
|
|
55
55
|
return await DeepseekLlm.consumeStream(body, onDelta);
|
|
56
56
|
} catch (error) {
|
|
57
|
+
|
|
57
58
|
this.logger.error(`DeepSeek turn failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
58
|
-
|
|
59
|
+
throw error;
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -67,7 +68,7 @@ export class DeepseekLlm
|
|
|
67
68
|
|
|
68
69
|
signal: AbortSignal.timeout(120_000),
|
|
69
70
|
});
|
|
70
|
-
if (!response.ok) throw
|
|
71
|
+
if (!response.ok) throw await DeepseekLlm.refusal(response);
|
|
71
72
|
return (await response.json()) as T;
|
|
72
73
|
}
|
|
73
74
|
|
|
@@ -78,11 +79,31 @@ export class DeepseekLlm
|
|
|
78
79
|
body: JSON.stringify(body),
|
|
79
80
|
signal: AbortSignal.timeout(120_000),
|
|
80
81
|
});
|
|
81
|
-
if (!response.ok || !response.body)
|
|
82
|
-
throw new Err("agent.error.deepseekRequestFailed", { status: String(response.status) });
|
|
82
|
+
if (!response.ok || !response.body) throw await DeepseekLlm.refusal(response);
|
|
83
83
|
return response.body;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* The dialect answers a refusal as `{ error: { message } }`, and that sentence is the useful half — a request
|
|
88
|
+
* past the context window says exactly which limit it passed. Carried on the `Err` so the chat can print it.
|
|
89
|
+
*/
|
|
90
|
+
static async refusal(response: Response): Promise<Error> {
|
|
91
|
+
return new Err("agent.error.deepseekRequestFailed", {
|
|
92
|
+
status: String(response.status),
|
|
93
|
+
reason: await DeepseekLlm.reasonOf(response),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
static async reasonOf(response: Response): Promise<string> {
|
|
98
|
+
try {
|
|
99
|
+
const body = (await response.json()) as { error?: { message?: unknown } | string };
|
|
100
|
+
const message = typeof body.error === "string" ? body.error : body.error?.message;
|
|
101
|
+
if (typeof message === "string" && message) return message;
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
return response.statusText || "no reason given";
|
|
105
|
+
}
|
|
106
|
+
|
|
86
107
|
/**
|
|
87
108
|
* The dialect streams `data: {chunk}` SSE lines ending with `data: [DONE]`. Tool calls arrive fragmented — the
|
|
88
109
|
* first fragment of an index carries id/name, later ones append to the arguments string — so they are assembled
|
|
@@ -175,6 +196,14 @@ export class DeepseekLlm
|
|
|
175
196
|
}
|
|
176
197
|
|
|
177
198
|
static providerMessages(message: AgentWireMessage): DeepseekMessage[] {
|
|
199
|
+
|
|
200
|
+
if (message.summary)
|
|
201
|
+
return [
|
|
202
|
+
{
|
|
203
|
+
role: "system" as const,
|
|
204
|
+
content: `Summary of the earlier conversation, standing in for the messages it replaced:\n\n${message.text ?? ""}`,
|
|
205
|
+
},
|
|
206
|
+
];
|
|
178
207
|
if (message.role === "tool")
|
|
179
208
|
return (message.toolResults ?? []).map((result) => ({
|
|
180
209
|
role: "tool" as const,
|
|
@@ -37,6 +37,11 @@ export interface AgentWireMessage {
|
|
|
37
37
|
toolCalls?: AgentWireToolCall[];
|
|
38
38
|
toolResults?: AgentWireToolResult[];
|
|
39
39
|
error?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Stands in for the messages the client's own compaction replaced. It arrives with the user's role because the
|
|
42
|
+
* wire has no other, but it is history rather than an ask, so a provider mapping frames it as one.
|
|
43
|
+
*/
|
|
44
|
+
summary?: boolean;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
47
|
export interface AgentWireTool {
|
|
@@ -68,8 +73,12 @@ export interface LlmTurnAnswer {
|
|
|
68
73
|
/**
|
|
69
74
|
* The provider seam for one stateless agent turn: the whole transcript in, one assistant answer out. The server
|
|
70
75
|
* relays — it never executes a client tool — so this is the only surface a provider integration fills. An
|
|
71
|
-
* implementation is an `adapt()` class in a lib's `srvkit
|
|
72
|
-
*
|
|
76
|
+
* implementation is an `adapt()` class in a lib's `srvkit/`.
|
|
77
|
+
*
|
|
78
|
+
* `null` means this provider is not configured, and the caller turns it into the one sentence that says so. A
|
|
79
|
+
* failure the provider explained is logged and **thrown** instead, as an `Err` whose text the chat prints: a
|
|
80
|
+
* refused turn and an unconfigured app are different things to be told, and collapsing both into `null` left a
|
|
81
|
+
* user reading "no model is configured" about a conversation that had merely outgrown the context window.
|
|
73
82
|
*/
|
|
74
83
|
export interface LlmAdaptor {
|
|
75
84
|
/**
|
|
@@ -16,6 +16,18 @@ export class AgentTurnStream {
|
|
|
16
16
|
return !!request.headers.get("accept")?.includes("text/event-stream");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* A domain `Err` carries its dictionary key as the message and the values its text interpolates as `data`, so
|
|
21
|
+
* both travel: the key alone would reach the chat as `agent.error.…` with its placeholders unfilled.
|
|
22
|
+
*/
|
|
23
|
+
static failure(error: unknown): { message: string; data?: Record<string, string | number> } {
|
|
24
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
25
|
+
const data = (error as { data?: unknown } | null)?.data;
|
|
26
|
+
return data && typeof data === "object" && !Array.isArray(data)
|
|
27
|
+
? { message, data: data as Record<string, string | number> }
|
|
28
|
+
: { message };
|
|
29
|
+
}
|
|
30
|
+
|
|
19
31
|
static response(run: (onDelta: (delta: string) => void) => Promise<StreamedTurn>): Response {
|
|
20
32
|
const encoder = new TextEncoder();
|
|
21
33
|
const stream = new ReadableStream<Uint8Array>({
|
|
@@ -35,7 +47,7 @@ export class AgentTurnStream {
|
|
|
35
47
|
send({ type: "done", stop: turn.stop === "toolUse" || toolCalls.length ? "toolUse" : "end" });
|
|
36
48
|
} catch (error) {
|
|
37
49
|
|
|
38
|
-
send({ type: "error",
|
|
50
|
+
send({ type: "error", ...AgentTurnStream.failure(error) });
|
|
39
51
|
} finally {
|
|
40
52
|
controller.close();
|
|
41
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", never, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">;
|
|
1
|
+
export declare const baseDictionary: import("./dictInfo.d.ts").ServiceDictInfo<[string, string], "ping" | "pingBody" | "pingParam" | "pingQuery" | "wsPing" | "pubsubPing", never, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import type { AgentEndpoint, AgentTurn, BaseEndpoint } from "akanjs/signal";
|
|
2
2
|
export declare const dictionary: {
|
|
3
|
-
base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">, never>;
|
|
3
|
+
base: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view">, never>;
|
|
4
4
|
agentTurn: import("./locale.d.ts").DictModule<import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc`, never>;
|
|
5
5
|
agent: import("./locale.d.ts").DictModule<import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">;
|
|
6
6
|
};
|
|
7
|
-
export declare const Err: import("./trans.d.ts").ErrConstructor<"agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">, translate: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
|
|
8
|
-
info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
9
|
-
success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
10
|
-
error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
11
|
-
warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
12
|
-
loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
13
|
-
}, getDictionary: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed";
|
|
7
|
+
export declare const Err: import("./trans.d.ts").ErrConstructor<"agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed">, translate: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja", key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, data?: import("./trans.d.ts").TranslationData) => string, msg: {
|
|
8
|
+
info: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
9
|
+
success: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
10
|
+
error: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
11
|
+
warning: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
12
|
+
loading: (key: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, option?: import("./trans.d.ts").TransMessageOption) => void;
|
|
13
|
+
}, getDictionary: (lang: "en" | "ko" | "zhChs" | "zhCht" | "ja") => object, getAllDictionary: () => import("./trans.d.ts").RootDictionary, __Dict_Key__: import("./locale.d.ts").ServiceTranslatorKey<"base", BaseEndpoint, "error" | "remove" | "save" | "refresh" | "password" | "stop" | "send" | "skip" | "agent" | "ok" | "connecting" | "new" | "somethingWrong" | "connected" | "serverDisconnected" | "refreshing" | "tryReconnecting" | "serverHasProblem" | "checkServerStatus" | "success" | "failed" | "processing" | "processed" | "noData" | "invalidValueError" | "emailInvalidError" | "phoneInvalidError" | "cancel" | "unauthorized" | "agentIntro" | "agentPlaceholder" | "agentClear" | "agentQuestion" | "agentAnswer" | "agentListen" | "agentVoiceFailed" | "agentAttach" | "agentAttachRemove" | "agentAttachTooLarge" | "agentAttachUnsupported" | "agentContinue" | "agentKeepGoing" | "agentCmdNew" | "agentCmdRetry" | "agentCmdCompact" | "agentCmdCopy" | "agentCmdHelp" | "agentCmdTools" | "agentHelpIntro" | "agentHelpNote" | "agentNothingToRetry" | "agentNothingToCompact" | "agentCompacted" | "agentSummary" | "agentBusy" | "agentCopied" | "agentCopyFailed" | "agentToolsHead" | "agentToolsState" | "approve" | "decline" | "confirmClose" | "textTooShortError" | "textTooLongError" | "selectTooShortError" | "selectTooLongError" | "numberTooSmallError" | "numberTooBigError" | "passwordNotMatchError" | "selectDateError" | "priceUnit" | "passwordConfirm" | "noOptions" | "addModel" | "createModel" | "createSuccess" | "updateModel" | "removeModel" | "updateSuccess" | "removeSuccess" | "sureToRemove" | "irreversibleOps" | "typeNameToRemove" | "yesRemove" | "removeMsg" | "confirmMsg" | "perPage" | "cardView" | "tableView" | "exportCsv" | "exportJson" | "actions" | "edit" | "view"> | import("./locale.d.ts").ScalarTranslatorKey<"agentTurn", AgentTurn, never> | `agentStop.${string}` | `agentStop.${string}.desc` | import("./locale.d.ts").ServiceTranslatorKey<"agent", AgentEndpoint, never>, __Error_Key__: "agent.error.llmUnavailable" | "agent.error.deepseekRequestFailed";
|
|
@@ -34,6 +34,12 @@ declare const DeepseekLlm_base: import("..").AdaptorCls<{}, {
|
|
|
34
34
|
export declare class DeepseekLlm extends DeepseekLlm_base implements LlmAdaptor {
|
|
35
35
|
#private;
|
|
36
36
|
chat(request: LlmTurnRequest, onDelta?: (delta: string) => void): Promise<LlmTurnAnswer | null>;
|
|
37
|
+
/**
|
|
38
|
+
* The dialect answers a refusal as `{ error: { message } }`, and that sentence is the useful half — a request
|
|
39
|
+
* past the context window says exactly which limit it passed. Carried on the `Err` so the chat can print it.
|
|
40
|
+
*/
|
|
41
|
+
static refusal(response: Response): Promise<Error>;
|
|
42
|
+
static reasonOf(response: Response): Promise<string>;
|
|
37
43
|
/**
|
|
38
44
|
* The dialect streams `data: {chunk}` SSE lines ending with `data: [DONE]`. Tool calls arrive fragmented — the
|
|
39
45
|
* first fragment of an index carries id/name, later ones append to the arguments string — so they are assembled
|
|
@@ -34,6 +34,11 @@ export interface AgentWireMessage {
|
|
|
34
34
|
toolCalls?: AgentWireToolCall[];
|
|
35
35
|
toolResults?: AgentWireToolResult[];
|
|
36
36
|
error?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Stands in for the messages the client's own compaction replaced. It arrives with the user's role because the
|
|
39
|
+
* wire has no other, but it is history rather than an ask, so a provider mapping frames it as one.
|
|
40
|
+
*/
|
|
41
|
+
summary?: boolean;
|
|
37
42
|
}
|
|
38
43
|
export interface AgentWireTool {
|
|
39
44
|
name: string;
|
|
@@ -60,8 +65,12 @@ export interface LlmTurnAnswer {
|
|
|
60
65
|
/**
|
|
61
66
|
* The provider seam for one stateless agent turn: the whole transcript in, one assistant answer out. The server
|
|
62
67
|
* relays — it never executes a client tool — so this is the only surface a provider integration fills. An
|
|
63
|
-
* implementation is an `adapt()` class in a lib's `srvkit
|
|
64
|
-
*
|
|
68
|
+
* implementation is an `adapt()` class in a lib's `srvkit/`.
|
|
69
|
+
*
|
|
70
|
+
* `null` means this provider is not configured, and the caller turns it into the one sentence that says so. A
|
|
71
|
+
* failure the provider explained is logged and **thrown** instead, as an `Err` whose text the chat prints: a
|
|
72
|
+
* refused turn and an unconfigured app are different things to be told, and collapsing both into `null` left a
|
|
73
|
+
* user reading "no model is configured" about a conversation that had merely outgrown the context window.
|
|
65
74
|
*/
|
|
66
75
|
export interface LlmAdaptor {
|
|
67
76
|
/**
|
|
@@ -11,6 +11,14 @@ interface StreamedTurn {
|
|
|
11
11
|
*/
|
|
12
12
|
export declare class AgentTurnStream {
|
|
13
13
|
static wants(request: Bun.BunRequest): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* A domain `Err` carries its dictionary key as the message and the values its text interpolates as `data`, so
|
|
16
|
+
* both travel: the key alone would reach the chat as `agent.error.…` with its placeholders unfilled.
|
|
17
|
+
*/
|
|
18
|
+
static failure(error: unknown): {
|
|
19
|
+
message: string;
|
|
20
|
+
data?: Record<string, string | number>;
|
|
21
|
+
};
|
|
14
22
|
static response(run: (onDelta: (delta: string) => void) => Promise<StreamedTurn>): Response;
|
|
15
23
|
}
|
|
16
24
|
export {};
|
package/types/ui/Agent/Chat.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import { type AgentRunner } from "../../vendor/use-agentic.d.ts";
|
|
2
|
+
import { type AgentRunner, type CompactOptions } from "../../vendor/use-agentic.d.ts";
|
|
3
3
|
import { type AttachReader } from "./attachment.d.ts";
|
|
4
4
|
import { type PersistOption } from "./sessionHistory.d.ts";
|
|
5
5
|
import { type VoiceEngine } from "./voice.d.ts";
|
|
@@ -11,6 +11,11 @@ export interface ChatProps {
|
|
|
11
11
|
/** Swap the transport; the default drives the app's `runAgentTurn` endpoint. */
|
|
12
12
|
runner?: AgentRunner;
|
|
13
13
|
maxTurns?: number;
|
|
14
|
+
/**
|
|
15
|
+
* When the conversation summarizes itself to stay inside the model's window — `at` estimated tokens, `keep`
|
|
16
|
+
* messages left verbatim below the summary. Tune it per provider; `{ at: 0 }` turns it off.
|
|
17
|
+
*/
|
|
18
|
+
compact?: CompactOptions;
|
|
14
19
|
defaultOpen?: boolean;
|
|
15
20
|
/** Keeps the transcript across reloads — sessionStorage by default, `{ storage: "local" }` to outlive the tab. */
|
|
16
21
|
persist?: PersistOption;
|
|
@@ -37,6 +42,6 @@ export interface ChatProps {
|
|
|
37
42
|
* no history). An enclosing AgentProvider's session wins, which is how an app isolates a surface or swaps the
|
|
38
43
|
* loop while keeping this UI.
|
|
39
44
|
*/
|
|
40
|
-
export declare const DefaultChat: ({ className, title, instructions, runner, maxTurns, defaultOpen, persist, inline, attach, voice, }: ChatProps) => ReactNode;
|
|
45
|
+
export declare const DefaultChat: ({ className, title, instructions, runner, maxTurns, compact, defaultOpen, persist, inline, attach, voice, }: ChatProps) => ReactNode;
|
|
41
46
|
declare const _default: import("react").ComponentType<ChatProps>;
|
|
42
47
|
export default _default;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentProgressReport } from "./AgentProgress.d.ts";
|
|
2
|
+
import { type CompactOptions } from "./Compaction.d.ts";
|
|
2
3
|
import type { AgentRunner, ChatMessage, ContextBlock, PublishedTool, SurfaceView } from "./types.d.ts";
|
|
3
4
|
export interface PendingApproval {
|
|
4
5
|
callId: string;
|
|
@@ -47,6 +48,11 @@ export interface AgentSessionOptions {
|
|
|
47
48
|
question: string;
|
|
48
49
|
keep: string;
|
|
49
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Keeps a long conversation inside the model's window: past `at` estimated tokens the history above the last
|
|
53
|
+
* few messages is replaced by one summary of itself, before the turn that would have overflowed is sent.
|
|
54
|
+
*/
|
|
55
|
+
compact?: CompactOptions;
|
|
50
56
|
}
|
|
51
57
|
/**
|
|
52
58
|
* The client-side conversation loop: send → model turn → tool calls → approval gate → execute → report diffs → next
|
|
@@ -93,6 +99,16 @@ export declare class AgentSession {
|
|
|
93
99
|
* only way back. Only the trailing message is replayed, so a prompt's own preamble stays where it is.
|
|
94
100
|
*/
|
|
95
101
|
retry: () => Promise<boolean>;
|
|
102
|
+
/**
|
|
103
|
+
* Replaces the history with one summary of itself and returns whether anything was replaced. `keep` leaves that
|
|
104
|
+
* many trailing messages verbatim; the command that a user types keeps none, because a summary of everything is
|
|
105
|
+
* what they asked for. A turn in flight refuses — the transcript it is appending to is not one to rewrite.
|
|
106
|
+
*/
|
|
107
|
+
compact: ({ keep }?: {
|
|
108
|
+
keep?: number;
|
|
109
|
+
}) => Promise<boolean>;
|
|
110
|
+
/** What the transcript is estimated to cost the next turn, in tokens — the number auto-compaction watches. */
|
|
111
|
+
get tokens(): number;
|
|
96
112
|
/** Records a host-side failure (a prompt fetch, an upload) in the transcript, where every other failure lands. */
|
|
97
113
|
report: (error: string) => void;
|
|
98
114
|
/** A line the host wrote — a command's own output. Rendered in the transcript, withheld from the model. */
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ChatMessage } from "./types.d.ts";
|
|
2
|
+
export interface CompactOptions {
|
|
3
|
+
/** Estimated transcript tokens above which a turn summarizes its own history first. `0` never compacts. */
|
|
4
|
+
at?: number;
|
|
5
|
+
/** Messages left verbatim below the summary. The cut rises to the nearest user message above them. */
|
|
6
|
+
keep?: number;
|
|
7
|
+
/** Produces the summary from the digest. Default: one tool-less turn through the session's own runner. */
|
|
8
|
+
summarize?: (digest: string, signal: AbortSignal) => Promise<string>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Turns the part of a transcript that no longer fits into one message standing in for it. The loop runs in the
|
|
12
|
+
* browser and the relay is stateless, so nothing else is keeping the conversation inside the model's window: an
|
|
13
|
+
* uncompacted chat simply grows until the provider refuses the request.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Compaction {
|
|
16
|
+
#private;
|
|
17
|
+
/**
|
|
18
|
+
* Deliberately well under the smallest window a provider is likely to have: what a conversation loses to an
|
|
19
|
+
* early summary is detail, and what it loses to a late one is the conversation. The tools, the screen context
|
|
20
|
+
* and the system prompt ride on top of the transcript on every turn, and none of them is compactable.
|
|
21
|
+
*/
|
|
22
|
+
static readonly defaults: {
|
|
23
|
+
at: number;
|
|
24
|
+
keep: number;
|
|
25
|
+
};
|
|
26
|
+
static readonly instruction: string;
|
|
27
|
+
/**
|
|
28
|
+
* Four characters per token, counted over the JSON the turn actually posts. A rough estimate on purpose: this
|
|
29
|
+
* places a threshold, and shipping a per-provider tokenizer to the browser to place it more exactly would cost
|
|
30
|
+
* more than the slack the estimate leaves.
|
|
31
|
+
*/
|
|
32
|
+
static tokensOf(messages: readonly ChatMessage[]): number;
|
|
33
|
+
/**
|
|
34
|
+
* Where the kept half starts, or `-1` when nothing can be cut. Only a user message opens one: everything before
|
|
35
|
+
* it is settled, so the kept half never begins with a `tool` result whose call was summarized away — a shape
|
|
36
|
+
* every provider dialect rejects. `keep: 0` summarizes the whole transcript, which is what the command does.
|
|
37
|
+
*/
|
|
38
|
+
static cutAt(messages: readonly ChatMessage[], keep: number): number;
|
|
39
|
+
/**
|
|
40
|
+
* The messages as one bounded block of text. Bounded is the point: the transcript being summarized is the one
|
|
41
|
+
* that no longer fits, so feeding it back verbatim would fail exactly where compaction is needed most.
|
|
42
|
+
*/
|
|
43
|
+
static digest(messages: readonly ChatMessage[], budget?: number): string;
|
|
44
|
+
static message(summary: string): ChatMessage;
|
|
45
|
+
}
|
|
@@ -5,6 +5,7 @@ export * from "./AgentProgress.d.ts";
|
|
|
5
5
|
export * from "./AgentProvider.d.ts";
|
|
6
6
|
export * from "./AgentScope.d.ts";
|
|
7
7
|
export * from "./AgentSession.d.ts";
|
|
8
|
+
export * from "./Compaction.d.ts";
|
|
8
9
|
export * from "./httpRunner.d.ts";
|
|
9
10
|
export * from "./surfaceContext.d.ts";
|
|
10
11
|
export * from "./types.d.ts";
|
|
@@ -136,6 +136,12 @@ export interface ChatMessage {
|
|
|
136
136
|
* history the model reads, which would take it for something it had said itself.
|
|
137
137
|
*/
|
|
138
138
|
local?: boolean;
|
|
139
|
+
/**
|
|
140
|
+
* Stands in for the messages compaction replaced. It rides the wire like any other message — it is what the
|
|
141
|
+
* model now remembers of them — but it is not something the user said, so a backend frames it as a summary and
|
|
142
|
+
* a host renders it as one.
|
|
143
|
+
*/
|
|
144
|
+
summary?: boolean;
|
|
139
145
|
}
|
|
140
146
|
/** One block of screen context the host assembles per turn. `kind` is the host's vocabulary; the wire forwards it verbatim. */
|
|
141
147
|
export interface ContextBlock {
|
|
@@ -153,9 +159,15 @@ export type RunnerEvent = {
|
|
|
153
159
|
} | {
|
|
154
160
|
type: "done";
|
|
155
161
|
stop: "end" | "toolUse";
|
|
156
|
-
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* `data` accompanies a message that is a code rather than a sentence — the values whoever resolves the code
|
|
165
|
+
* interpolates into its text. A host that does not know the code shows the message as it stands.
|
|
166
|
+
*/
|
|
167
|
+
| {
|
|
157
168
|
type: "error";
|
|
158
169
|
message: string;
|
|
170
|
+
data?: Record<string, string | number>;
|
|
159
171
|
};
|
|
160
172
|
export interface RunnerRequest {
|
|
161
173
|
messages: ChatMessage[];
|
package/ui/Agent/Bubble.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { cn } from "akanjs/client";
|
|
2
|
+
import { cn, usePage } from "akanjs/client";
|
|
3
3
|
import { type AgentProgressReport, AgentSession, type ChatMessage, type ToolCallResult } from "../../vendor/use-agentic";
|
|
4
4
|
import { Chips } from "./Attach";
|
|
5
5
|
import Markdown from "./Markdown";
|
|
@@ -81,6 +81,15 @@ const Ask = ({ args, result }: AskProps) => {
|
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
export default function Bubble({ className, message, progress, results }: BubbleProps) {
|
|
84
|
+
const { l } = usePage();
|
|
85
|
+
|
|
86
|
+
if (message.summary)
|
|
87
|
+
return (
|
|
88
|
+
<details className={cn("rounded-box border border-border bg-muted/60 px-3 py-2", className)}>
|
|
89
|
+
<summary className="cursor-pointer text-foreground/50 text-xs">{l("base.agentSummary")}</summary>
|
|
90
|
+
<p className="mt-2 whitespace-pre-wrap text-foreground/70 text-xs">{message.text}</p>
|
|
91
|
+
</details>
|
|
92
|
+
);
|
|
84
93
|
if (message.role === "tool")
|
|
85
94
|
return (
|
|
86
95
|
<div className={cn("flex flex-col gap-1", className)}>
|
package/ui/Agent/Chat.tsx
CHANGED
|
@@ -5,7 +5,13 @@ import { AgentContext, type AgentPrompt, AgentPrompts, ensureStoreSurface, Scree
|
|
|
5
5
|
import { type ReactNode, useContext, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { createPortal } from "react-dom";
|
|
7
7
|
import { AiOutlineClear, AiOutlineClose } from "react-icons/ai";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type AgentRunner,
|
|
10
|
+
AgentSession,
|
|
11
|
+
type CompactOptions,
|
|
12
|
+
type MessageAttachment,
|
|
13
|
+
SessionContext,
|
|
14
|
+
} from "../../vendor/use-agentic";
|
|
9
15
|
import { Button } from "../Button";
|
|
10
16
|
import { inputRecipe } from "../recipe";
|
|
11
17
|
import { createOverridable } from "../UiOverride";
|
|
@@ -29,6 +35,11 @@ export interface ChatProps {
|
|
|
29
35
|
/** Swap the transport; the default drives the app's `runAgentTurn` endpoint. */
|
|
30
36
|
runner?: AgentRunner;
|
|
31
37
|
maxTurns?: number;
|
|
38
|
+
/**
|
|
39
|
+
* When the conversation summarizes itself to stay inside the model's window — `at` estimated tokens, `keep`
|
|
40
|
+
* messages left verbatim below the summary. Tune it per provider; `{ at: 0 }` turns it off.
|
|
41
|
+
*/
|
|
42
|
+
compact?: CompactOptions;
|
|
32
43
|
defaultOpen?: boolean;
|
|
33
44
|
/** Keeps the transcript across reloads — sessionStorage by default, `{ storage: "local" }` to outlive the tab. */
|
|
34
45
|
persist?: PersistOption;
|
|
@@ -66,6 +77,7 @@ export const DefaultChat = ({
|
|
|
66
77
|
instructions,
|
|
67
78
|
runner,
|
|
68
79
|
maxTurns,
|
|
80
|
+
compact,
|
|
69
81
|
defaultOpen = false,
|
|
70
82
|
persist,
|
|
71
83
|
inline = false,
|
|
@@ -83,6 +95,7 @@ export const DefaultChat = ({
|
|
|
83
95
|
continueAsk: { question: l("base.agentContinue"), keep: l("base.agentKeepGoing") },
|
|
84
96
|
...(instructions ? { instructions } : {}),
|
|
85
97
|
...(maxTurns ? { maxTurns } : {}),
|
|
98
|
+
...(compact ? { compact } : {}),
|
|
86
99
|
...(persist ? { history: sessionHistoryOf(persist) } : {}),
|
|
87
100
|
});
|
|
88
101
|
const session = held.current;
|
package/ui/Agent/ChatCommands.ts
CHANGED
|
@@ -42,6 +42,15 @@ export class ChatCommands {
|
|
|
42
42
|
else if (!(await session.retry())) session.note(t("base.agentNothingToRetry"));
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
name: "compact",
|
|
47
|
+
description: l("base.agentCmdCompact"),
|
|
48
|
+
run: async ({ session, l: t }) => {
|
|
49
|
+
if (session.isRunning) session.note(t("base.agentBusy"));
|
|
50
|
+
else if (await session.compact()) session.note(t("base.agentCompacted"));
|
|
51
|
+
else session.note(t("base.agentNothingToCompact"));
|
|
52
|
+
},
|
|
53
|
+
},
|
|
45
54
|
{ name: "copy", description: l("base.agentCmdCopy"), run: (context) => ChatCommands.#copy(context) },
|
|
46
55
|
{ name: "help", description: l("base.agentCmdHelp"), run: (context) => ChatCommands.#help(context) },
|
|
47
56
|
{ name: "tools", description: l("base.agentCmdTools"), run: (context) => ChatCommands.#tools(context) },
|
|
@@ -70,7 +79,8 @@ export class ChatCommands {
|
|
|
70
79
|
const lines = [`# Agent conversation`, [where, new Date().toISOString()].filter(Boolean).join(" · "), ""];
|
|
71
80
|
for (const message of messages) {
|
|
72
81
|
if (message.local) continue;
|
|
73
|
-
|
|
82
|
+
|
|
83
|
+
lines.push(`**${message.summary ? "summary" : message.role}**`);
|
|
74
84
|
if (message.text) lines.push(message.text);
|
|
75
85
|
for (const attachment of message.attachments ?? [])
|
|
76
86
|
lines.push(`- attached \`${attachment.name}\` (${attachment.mimeType})`);
|
package/ui/Agent/fetchRunner.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { getEnv } from "akanjs/base";
|
|
2
|
-
import { fetch } from "akanjs/client";
|
|
3
|
-
import { type AgentRunner, httpRunner } from "../../vendor/use-agentic";
|
|
2
|
+
import { fetch, Translator } from "akanjs/client";
|
|
3
|
+
import { type AgentRunner, httpRunner, type RunnerEvent } from "../../vendor/use-agentic";
|
|
4
|
+
|
|
5
|
+
/** `<refName>.error.<key>` — the shape a domain `Err` puts on the wire, its dictionary text being the message. */
|
|
6
|
+
const errorKey = /^[a-zA-Z][A-Za-z0-9]*\.error\.[A-Za-z0-9_]+$/;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A server `Err` travels as its key, because the endpoint has no language to resolve it in — the chat does, so
|
|
10
|
+
* the resolving happens here, one step before the transcript. Anything else is already a sentence somebody wrote,
|
|
11
|
+
* and a key with no entry stays as it is rather than becoming a worse sentence.
|
|
12
|
+
*/
|
|
13
|
+
const readable = (event: RunnerEvent): RunnerEvent => {
|
|
14
|
+
if (event.type !== "error" || !errorKey.test(event.message)) return event;
|
|
15
|
+
const text = Translator.translateByLocale(Translator.getActiveLocale() ?? "en", event.message, event.data);
|
|
16
|
+
return text === event.message ? event : { type: "error", message: text };
|
|
17
|
+
};
|
|
4
18
|
|
|
5
19
|
/**
|
|
6
20
|
* Runs each assistant turn against the app's own `runAgentTurn` route — service signals mount unprefixed, so the
|
|
@@ -25,6 +39,6 @@ export const fetchRunner = (options: { fetcher?: typeof globalThis.fetch } = {})
|
|
|
25
39
|
},
|
|
26
40
|
...(options.fetcher ? { fetcher: options.fetcher } : {}),
|
|
27
41
|
});
|
|
28
|
-
|
|
42
|
+
for await (const event of runner.run(request)) yield readable(event);
|
|
29
43
|
},
|
|
30
44
|
});
|
package/ui/Load/Units.tsx
CHANGED
|
@@ -113,7 +113,6 @@ function Render<RefName extends string, Light extends { id: string }>({
|
|
|
113
113
|
const initModelObjInsight = (init as any)[names.modelObjInsight] as BaseInsight;
|
|
114
114
|
const initLimitOfModel = (init as any)[names.limitOfModel] as number;
|
|
115
115
|
const initPageOfModel = (init as any)[names.pageOfModel] as number;
|
|
116
|
-
const modelStaleAt = storeUse[namesOfSlice.modelStaleAt]() as Date;
|
|
117
116
|
|
|
118
117
|
const useCache =
|
|
119
118
|
!modelListLoading &&
|
|
@@ -152,11 +151,12 @@ function Render<RefName extends string, Light extends { id: string }>({
|
|
|
152
151
|
}, []);
|
|
153
152
|
|
|
154
153
|
useEffect(() => {
|
|
154
|
+
const modelStaleAt = storeGet<Date>()[namesOfSlice.modelStaleAt];
|
|
155
155
|
const staleThreshold = Math.max(modelStaleAt.getTime(), staleTime === undefined ? 0 : Date.now() - staleTime);
|
|
156
156
|
if (storeGet<Date>()[namesOfSlice.modelInitAt].getTime() >= staleThreshold) return;
|
|
157
157
|
if (storeGet<boolean>()[namesOfSlice.modelListLoading]) return;
|
|
158
158
|
void storeDo[namesOfSlice.refreshModel]({ invalidate: true });
|
|
159
|
-
}, [
|
|
159
|
+
}, []);
|
|
160
160
|
|
|
161
161
|
const modelInsight = storeUse[namesOfSlice.modelInsight]() as BaseInsight;
|
|
162
162
|
const limitOfModel = storeUse[namesOfSlice.limitOfModel]() as number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AgentAbort } from "./AgentAbort";
|
|
2
2
|
import { AgentProgress, type AgentProgressReport } from "./AgentProgress";
|
|
3
|
+
import { Compaction, type CompactOptions } from "./Compaction";
|
|
3
4
|
import type {
|
|
4
5
|
AgentRunner,
|
|
5
6
|
ChatMessage,
|
|
@@ -59,6 +60,11 @@ export interface AgentSessionOptions {
|
|
|
59
60
|
* renders no `pendingQuestion` would otherwise wait forever for an answer nobody can give.
|
|
60
61
|
*/
|
|
61
62
|
continueAsk?: { question: string; keep: string };
|
|
63
|
+
/**
|
|
64
|
+
* Keeps a long conversation inside the model's window: past `at` estimated tokens the history above the last
|
|
65
|
+
* few messages is replaced by one summary of itself, before the turn that would have overflowed is sent.
|
|
66
|
+
*/
|
|
67
|
+
compact?: CompactOptions;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
/**
|
|
@@ -107,6 +113,8 @@ export class AgentSession {
|
|
|
107
113
|
#version = 0;
|
|
108
114
|
#listeners = new Set<() => void>();
|
|
109
115
|
#saveTimer: ReturnType<typeof setTimeout> | null = null;
|
|
116
|
+
/** Size below which auto-compaction stays out of the way, raised when a summary failed to shrink anything. */
|
|
117
|
+
#compactFloor = 0;
|
|
110
118
|
|
|
111
119
|
constructor(surface: SurfaceView, runner: AgentRunner, options: AgentSessionOptions = {}) {
|
|
112
120
|
this.#surface = surface;
|
|
@@ -181,6 +189,7 @@ export class AgentSession {
|
|
|
181
189
|
this.#append({ role: "user", text: answer });
|
|
182
190
|
budget = turn + maxTurns;
|
|
183
191
|
}
|
|
192
|
+
await this.#autoCompact(controller.signal);
|
|
184
193
|
const { toolCalls, stop } = await this.#assistantTurn(controller.signal);
|
|
185
194
|
if (controller.signal.aborted || stop !== "toolUse" || !toolCalls.length) return;
|
|
186
195
|
const toolResults: ToolCallResult[] = [];
|
|
@@ -218,6 +227,7 @@ export class AgentSession {
|
|
|
218
227
|
await this.#active;
|
|
219
228
|
}
|
|
220
229
|
this.#messages = [];
|
|
230
|
+
this.#compactFloor = 0;
|
|
221
231
|
|
|
222
232
|
if (this.#saveTimer) {
|
|
223
233
|
clearTimeout(this.#saveTimer);
|
|
@@ -239,7 +249,8 @@ export class AgentSession {
|
|
|
239
249
|
retry = async (): Promise<boolean> => {
|
|
240
250
|
if (this.#active) return false;
|
|
241
251
|
const at = this.#messages.findLastIndex(
|
|
242
|
-
|
|
252
|
+
|
|
253
|
+
(message) => message.role === "user" && !message.summary && (!!message.text || !!message.attachments?.length),
|
|
243
254
|
);
|
|
244
255
|
if (at < 0) return false;
|
|
245
256
|
const again = this.#messages[at];
|
|
@@ -248,6 +259,43 @@ export class AgentSession {
|
|
|
248
259
|
return true;
|
|
249
260
|
};
|
|
250
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Replaces the history with one summary of itself and returns whether anything was replaced. `keep` leaves that
|
|
264
|
+
* many trailing messages verbatim; the command that a user types keeps none, because a summary of everything is
|
|
265
|
+
* what they asked for. A turn in flight refuses — the transcript it is appending to is not one to rewrite.
|
|
266
|
+
*/
|
|
267
|
+
compact = async ({ keep = 0 }: { keep?: number } = {}): Promise<boolean> => {
|
|
268
|
+
if (this.#running) return false;
|
|
269
|
+
const controller = new AbortController();
|
|
270
|
+
this.#controller = controller;
|
|
271
|
+
this.#running = true;
|
|
272
|
+
|
|
273
|
+
this.#notify();
|
|
274
|
+
const run = this.#compact(keep, controller.signal);
|
|
275
|
+
|
|
276
|
+
this.#active = run.then(
|
|
277
|
+
() => undefined,
|
|
278
|
+
() => undefined,
|
|
279
|
+
);
|
|
280
|
+
try {
|
|
281
|
+
return await run;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
|
|
284
|
+
if (controller.signal.aborted) return false;
|
|
285
|
+
throw error;
|
|
286
|
+
} finally {
|
|
287
|
+
this.#running = false;
|
|
288
|
+
this.#controller = null;
|
|
289
|
+
this.#active = null;
|
|
290
|
+
this.#notify();
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/** What the transcript is estimated to cost the next turn, in tokens — the number auto-compaction watches. */
|
|
295
|
+
get tokens() {
|
|
296
|
+
return Compaction.tokensOf(this.#messages);
|
|
297
|
+
}
|
|
298
|
+
|
|
251
299
|
/** Records a host-side failure (a prompt fetch, an upload) in the transcript, where every other failure lands. */
|
|
252
300
|
report = (error: string) => {
|
|
253
301
|
this.#append({ role: "assistant", error });
|
|
@@ -258,6 +306,53 @@ export class AgentSession {
|
|
|
258
306
|
this.#append({ role: "assistant", text, local: true });
|
|
259
307
|
};
|
|
260
308
|
|
|
309
|
+
async #compact(keep: number, signal: AbortSignal): Promise<boolean> {
|
|
310
|
+
const at = Compaction.cutAt(this.#messages, keep);
|
|
311
|
+
if (at <= 0) return false;
|
|
312
|
+
const summary = (await this.#summarize(Compaction.digest(this.#messages.slice(0, at)), signal)).trim();
|
|
313
|
+
if (!summary || signal.aborted) return false;
|
|
314
|
+
this.#messages = [Compaction.message(summary), ...this.#messages.slice(at)];
|
|
315
|
+
this.#notify();
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Runs before the turn that would have overflowed rather than after it fails: the provider answers a request
|
|
321
|
+
* that is too long with a refusal, not with a shorter answer, so there is nothing to recover from afterwards.
|
|
322
|
+
* Best effort — a summary that cannot be produced leaves the transcript as it stands and the turn goes out as
|
|
323
|
+
* it would have, since it may well still fit.
|
|
324
|
+
*/
|
|
325
|
+
async #autoCompact(signal: AbortSignal) {
|
|
326
|
+
const { at = Compaction.defaults.at, keep = Compaction.defaults.keep } = this.#options.compact ?? {};
|
|
327
|
+
if (!at || Compaction.tokensOf(this.#messages) < Math.max(at, this.#compactFloor)) return;
|
|
328
|
+
try {
|
|
329
|
+
await this.#compact(keep, signal);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
console.warn(`[use-agentic] compaction failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
332
|
+
}
|
|
333
|
+
const after = Compaction.tokensOf(this.#messages);
|
|
334
|
+
|
|
335
|
+
this.#compactFloor = after < at ? 0 : after + at;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** No tools and no screen context: this turn summarizes the conversation, and must not act on it. */
|
|
339
|
+
async #summarize(digest: string, signal: AbortSignal): Promise<string> {
|
|
340
|
+
const custom = this.#options.compact?.summarize;
|
|
341
|
+
if (custom) return await custom(digest, signal);
|
|
342
|
+
let text = "";
|
|
343
|
+
for await (const event of this.#runner.run({
|
|
344
|
+
messages: [{ role: "user", text: digest }],
|
|
345
|
+
tools: [],
|
|
346
|
+
context: [],
|
|
347
|
+
instructions: Compaction.instruction,
|
|
348
|
+
signal,
|
|
349
|
+
})) {
|
|
350
|
+
if (event.type === "text") text += event.delta;
|
|
351
|
+
else if (event.type === "error") throw new Error(event.message);
|
|
352
|
+
}
|
|
353
|
+
return text;
|
|
354
|
+
}
|
|
355
|
+
|
|
261
356
|
async #assistantTurn(signal: AbortSignal): Promise<{ toolCalls: ToolCallRequest[]; stop: "end" | "toolUse" }> {
|
|
262
357
|
const { tools, guides } = this.#surface.snapshot();
|
|
263
358
|
const instructions = [this.#options.instructions, ...guides].filter(Boolean).join("\n\n");
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { ChatMessage } from "./types";
|
|
2
|
+
|
|
3
|
+
export interface CompactOptions {
|
|
4
|
+
/** Estimated transcript tokens above which a turn summarizes its own history first. `0` never compacts. */
|
|
5
|
+
at?: number;
|
|
6
|
+
/** Messages left verbatim below the summary. The cut rises to the nearest user message above them. */
|
|
7
|
+
keep?: number;
|
|
8
|
+
/** Produces the summary from the digest. Default: one tool-less turn through the session's own runner. */
|
|
9
|
+
summarize?: (digest: string, signal: AbortSignal) => Promise<string>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Turns the part of a transcript that no longer fits into one message standing in for it. The loop runs in the
|
|
14
|
+
* browser and the relay is stateless, so nothing else is keeping the conversation inside the model's window: an
|
|
15
|
+
* uncompacted chat simply grows until the provider refuses the request.
|
|
16
|
+
*/
|
|
17
|
+
export class Compaction {
|
|
18
|
+
/**
|
|
19
|
+
* Deliberately well under the smallest window a provider is likely to have: what a conversation loses to an
|
|
20
|
+
* early summary is detail, and what it loses to a late one is the conversation. The tools, the screen context
|
|
21
|
+
* and the system prompt ride on top of the transcript on every turn, and none of them is compactable.
|
|
22
|
+
*/
|
|
23
|
+
static readonly defaults = { at: 24_000, keep: 6 };
|
|
24
|
+
|
|
25
|
+
static readonly instruction =
|
|
26
|
+
"Summarize the conversation below so you can carry it on with the summary in place of the messages themselves. " +
|
|
27
|
+
"Keep what the user is trying to do, the decisions taken, the facts and tool results that still matter, and " +
|
|
28
|
+
"anything left unfinished. Drop pleasantries and anything already superseded. Write compact notes, not prose, " +
|
|
29
|
+
"and write nothing but the summary itself.";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Four characters per token, counted over the JSON the turn actually posts. A rough estimate on purpose: this
|
|
33
|
+
* places a threshold, and shipping a per-provider tokenizer to the browser to place it more exactly would cost
|
|
34
|
+
* more than the slack the estimate leaves.
|
|
35
|
+
*/
|
|
36
|
+
static tokensOf(messages: readonly ChatMessage[]): number {
|
|
37
|
+
let chars = 0;
|
|
38
|
+
for (const message of messages) if (!message.local) chars += JSON.stringify(message).length;
|
|
39
|
+
return Math.ceil(chars / 4);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Where the kept half starts, or `-1` when nothing can be cut. Only a user message opens one: everything before
|
|
44
|
+
* it is settled, so the kept half never begins with a `tool` result whose call was summarized away — a shape
|
|
45
|
+
* every provider dialect rejects. `keep: 0` summarizes the whole transcript, which is what the command does.
|
|
46
|
+
*/
|
|
47
|
+
static cutAt(messages: readonly ChatMessage[], keep: number): number {
|
|
48
|
+
if (!messages.length) return -1;
|
|
49
|
+
if (keep <= 0) return messages.length;
|
|
50
|
+
const target = messages.length - keep;
|
|
51
|
+
if (target <= 0) return -1;
|
|
52
|
+
for (let at = target; at < messages.length; at += 1) if (messages[at].role === "user") return at;
|
|
53
|
+
return -1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The messages as one bounded block of text. Bounded is the point: the transcript being summarized is the one
|
|
58
|
+
* that no longer fits, so feeding it back verbatim would fail exactly where compaction is needed most.
|
|
59
|
+
*/
|
|
60
|
+
static digest(messages: readonly ChatMessage[], budget = 12_000): string {
|
|
61
|
+
const lines = messages.filter((message) => !message.local).map((message) => Compaction.#line(message));
|
|
62
|
+
if (lines.reduce((sum, line) => sum + line.length + 1, 0) <= budget) return lines.join("\n");
|
|
63
|
+
|
|
64
|
+
const head: string[] = [];
|
|
65
|
+
const tail: string[] = [];
|
|
66
|
+
let used = 0;
|
|
67
|
+
let low = 0;
|
|
68
|
+
let high = lines.length - 1;
|
|
69
|
+
let fromHead = true;
|
|
70
|
+
while (low <= high) {
|
|
71
|
+
const line = lines[fromHead ? low : high];
|
|
72
|
+
if (used + line.length + 1 > budget) break;
|
|
73
|
+
used += line.length + 1;
|
|
74
|
+
if (fromHead) {
|
|
75
|
+
head.push(line);
|
|
76
|
+
low += 1;
|
|
77
|
+
} else {
|
|
78
|
+
tail.unshift(line);
|
|
79
|
+
high -= 1;
|
|
80
|
+
}
|
|
81
|
+
fromHead = !fromHead;
|
|
82
|
+
}
|
|
83
|
+
return [...head, `[... ${high - low + 1} messages omitted ...]`, ...tail].join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static message(summary: string): ChatMessage {
|
|
87
|
+
return { role: "user", text: summary, summary: true };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static #line(message: ChatMessage): string {
|
|
91
|
+
const parts: string[] = [];
|
|
92
|
+
if (message.text) parts.push(Compaction.#clip(message.text, 1200));
|
|
93
|
+
for (const attachment of message.attachments ?? []) parts.push(`[attached ${attachment.name}]`);
|
|
94
|
+
for (const call of message.toolCalls ?? [])
|
|
95
|
+
parts.push(`[called ${call.name} ${Compaction.#clip(JSON.stringify(call.args), 200)}]`);
|
|
96
|
+
for (const result of message.toolResults ?? [])
|
|
97
|
+
parts.push(
|
|
98
|
+
`[${result.error ? "failed" : "result"} ${result.name}: ${Compaction.#clip(
|
|
99
|
+
result.error ?? JSON.stringify(result.result ?? null),
|
|
100
|
+
400,
|
|
101
|
+
)}]`,
|
|
102
|
+
);
|
|
103
|
+
if (message.error) parts.push(`[turn failed: ${message.error}]`);
|
|
104
|
+
return `${message.role}: ${parts.join(" ")}`.trimEnd();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static #clip(text: string, max: number): string {
|
|
108
|
+
return text.length <= max ? text : `${text.slice(0, max)}...`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -30,6 +30,8 @@ tools, and the loop stay in the client; the server serves exactly one stateless
|
|
|
30
30
|
|
|
31
31
|
- `messages` is the whole transcript in `ChatMessage` shape (`types.ts`). The server maps it to its provider's
|
|
32
32
|
format and maps the answer back; it never executes a tool — `tools` is a schema catalogue, not an offer.
|
|
33
|
+
- A message flagged `"summary": true` stands in for the earlier messages a client-side compaction replaced. It is
|
|
34
|
+
history, not something the user said, so a backend that can frame it as one — a system message — should.
|
|
33
35
|
- `context` blocks are host vocabulary. The server forwards them to the model as data, framed as data.
|
|
34
36
|
- Everything is JSON-serializable by contract; a tool whose `result` is not is the tool's bug.
|
|
35
37
|
|
|
@@ -64,7 +66,10 @@ Nothing here is stored: the wire carries the bytes for exactly one turn's reques
|
|
|
64
66
|
```
|
|
65
67
|
|
|
66
68
|
Any non-2xx status is surfaced to the session as one error event and ends the turn; a string `message` or
|
|
67
|
-
`error` field in a JSON body
|
|
69
|
+
`error` field in a JSON body becomes that event's message verbatim, and any other body is not interpreted. A
|
|
70
|
+
message that is a code rather than a sentence may be accompanied by a flat `data` object of strings and numbers —
|
|
71
|
+
the values whoever resolves the code interpolates into its text; a host that does not know the code shows the
|
|
72
|
+
message as it stands.
|
|
68
73
|
|
|
69
74
|
## Streaming
|
|
70
75
|
|
|
@@ -64,12 +64,28 @@ async function* streamedEvents(body: ReadableStream<Uint8Array>, signal: AbortSi
|
|
|
64
64
|
if (!ended) yield { type: "error", message: "The turn stream ended without a done event." };
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
/** Only a flat record of scalars is forwarded: what a coded message interpolates, never a nested payload. */
|
|
68
|
+
const interpolations = (value: unknown): Record<string, string | number> | undefined => {
|
|
69
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
70
|
+
const entries = Object.entries(value).filter(
|
|
71
|
+
(entry): entry is [string, string | number] => typeof entry[1] === "string" || typeof entry[1] === "number",
|
|
72
|
+
);
|
|
73
|
+
return entries.length ? Object.fromEntries(entries) : undefined;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The server's own message travels verbatim — it was written for whoever is reading the chat, and a prefix in
|
|
78
|
+
* front of it both reads as two sentences and hides a coded message from the host that would have resolved it.
|
|
79
|
+
* The status stands in only when the body says nothing.
|
|
80
|
+
*/
|
|
81
|
+
const turnError = async (response: Response): Promise<RunnerEvent> => {
|
|
82
|
+
const fallback = { type: "error", message: `Agent turn failed: ${response.status}` } as const;
|
|
69
83
|
try {
|
|
70
|
-
const body = (await response.json()) as { message?: unknown; error?: unknown };
|
|
84
|
+
const body = (await response.json()) as { message?: unknown; error?: unknown; data?: unknown };
|
|
71
85
|
const message = typeof body.message === "string" ? body.message : typeof body.error === "string" ? body.error : "";
|
|
72
|
-
|
|
86
|
+
if (!message) return fallback;
|
|
87
|
+
const data = interpolations(body.data);
|
|
88
|
+
return { type: "error", message, ...(data ? { data } : {}) };
|
|
73
89
|
} catch {
|
|
74
90
|
return fallback;
|
|
75
91
|
}
|
|
@@ -100,7 +116,7 @@ export const httpRunner = ({ url, headers, fetcher }: HttpRunnerOptions): AgentR
|
|
|
100
116
|
signal: request.signal,
|
|
101
117
|
});
|
|
102
118
|
if (!response.ok) {
|
|
103
|
-
yield
|
|
119
|
+
yield await turnError(response);
|
|
104
120
|
return;
|
|
105
121
|
}
|
|
106
122
|
if (response.headers.get("content-type")?.includes("text/event-stream") && response.body) {
|
|
@@ -155,6 +155,12 @@ export interface ChatMessage {
|
|
|
155
155
|
* history the model reads, which would take it for something it had said itself.
|
|
156
156
|
*/
|
|
157
157
|
local?: boolean;
|
|
158
|
+
/**
|
|
159
|
+
* Stands in for the messages compaction replaced. It rides the wire like any other message — it is what the
|
|
160
|
+
* model now remembers of them — but it is not something the user said, so a backend frames it as a summary and
|
|
161
|
+
* a host renders it as one.
|
|
162
|
+
*/
|
|
163
|
+
summary?: boolean;
|
|
158
164
|
}
|
|
159
165
|
|
|
160
166
|
/** One block of screen context the host assembles per turn. `kind` is the host's vocabulary; the wire forwards it verbatim. */
|
|
@@ -167,7 +173,11 @@ export type RunnerEvent =
|
|
|
167
173
|
| { type: "text"; delta: string }
|
|
168
174
|
| { type: "toolCall"; id: string; name: string; args: Record<string, unknown> }
|
|
169
175
|
| { type: "done"; stop: "end" | "toolUse" }
|
|
170
|
-
|
|
176
|
+
/**
|
|
177
|
+
* `data` accompanies a message that is a code rather than a sentence — the values whoever resolves the code
|
|
178
|
+
* interpolates into its text. A host that does not know the code shows the message as it stands.
|
|
179
|
+
*/
|
|
180
|
+
| { type: "error"; message: string; data?: Record<string, string | number> };
|
|
171
181
|
|
|
172
182
|
export interface RunnerRequest {
|
|
173
183
|
messages: ChatMessage[];
|