akanjs 3.0.0-alpha.37 → 3.0.0-alpha.39
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/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/agent.service.ts +48 -2
- package/service/predefinedAdaptor/deepseekLlm.ts +13 -1
- package/service/predefinedAdaptor/llm.adaptor.ts +31 -0
- package/store/agent/AgentPrompts.ts +20 -2
- package/store/agent/ScreenReader.ts +14 -4
- package/store/agentic/useFieldTool.ts +60 -4
- package/store/agentic/useFormTools.ts +13 -2
- package/store/formSetterNames.ts +5 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +8 -8
- package/types/service/agent.service.d.ts +13 -1
- package/types/service/predefinedAdaptor/deepseekLlm.d.ts +6 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +29 -0
- package/types/store/agentic/useFieldTool.d.ts +14 -2
- package/types/store/agentic/useFormTools.d.ts +5 -0
- package/types/store/formSetterNames.d.ts +5 -0
- package/types/ui/Agent/Attach.d.ts +16 -0
- package/types/ui/Agent/Chat.d.ts +9 -1
- package/types/ui/Agent/attachment.d.ts +23 -0
- package/types/ui/Dropdown.d.ts +3 -1
- package/types/ui/Field.d.ts +1 -0
- package/types/ui/index.d.ts +1 -0
- package/types/vendor/use-agentic/types.d.ts +23 -0
- package/ui/Agent/Attach.tsx +77 -0
- package/ui/Agent/Bubble.tsx +6 -7
- package/ui/Agent/Chat.tsx +84 -32
- package/ui/Agent/ChatCommands.ts +2 -0
- package/ui/Agent/attachment.ts +53 -0
- package/ui/Agent/sessionHistory.ts +20 -1
- package/ui/DraggableList.tsx +5 -1
- package/ui/Dropdown.tsx +37 -7
- package/ui/Field.tsx +41 -19
- package/ui/Input.tsx +6 -6
- package/ui/Select.tsx +1 -1
- package/ui/Switch.tsx +1 -1
- package/ui/index.ts +1 -0
- package/vendor/use-agentic/AgentSession.ts +3 -1
- package/vendor/use-agentic/WIRE.md +18 -0
- package/vendor/use-agentic/types.ts +24 -0
|
@@ -54,6 +54,10 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
|
|
|
54
54
|
agentClear: ["Clear conversation", "대화 비우기"],
|
|
55
55
|
agentQuestion: ["The agent needs your decision", "에이전트가 결정을 요청합니다"],
|
|
56
56
|
agentAnswer: ["Type your answer...", "답변을 입력하세요..."],
|
|
57
|
+
agentAttach: ["Attach a file", "파일 첨부"],
|
|
58
|
+
agentAttachRemove: ["Remove attachment", "첨부 제거"],
|
|
59
|
+
agentAttachTooLarge: ["{name} is too large to attach.", "{name}은(는) 용량이 너무 커서 첨부할 수 없습니다."],
|
|
60
|
+
agentAttachUnsupported: ["{name} cannot be attached here.", "{name}은(는) 여기에 첨부할 수 없습니다."],
|
|
57
61
|
agentContinue: ["This is taking a while. Keep going?", "시간이 걸리고 있습니다. 계속할까요?"],
|
|
58
62
|
agentKeepGoing: ["Keep going", "계속하기"],
|
|
59
63
|
agentCmdNew: ["Start a new conversation", "새 대화 시작"],
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/service/agent.service.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Err } from "akanjs/dictionary";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
AgentWireAttachment,
|
|
4
|
+
AgentWireMessage,
|
|
5
|
+
LlmAccepts,
|
|
6
|
+
LlmTurnRequest,
|
|
7
|
+
} from "./predefinedAdaptor/llm.adaptor";
|
|
3
8
|
import { LlmAdaptorRole } from "./predefinedAdaptor/role.adaptor";
|
|
4
9
|
import { serve } from "./serve";
|
|
5
10
|
|
|
@@ -7,8 +12,49 @@ export class AgentService extends serve("agent" as const, ({ plug }) => ({
|
|
|
7
12
|
llm: plug(LlmAdaptorRole),
|
|
8
13
|
})) {
|
|
9
14
|
async runTurn(request: LlmTurnRequest, onDelta?: (delta: string) => void) {
|
|
10
|
-
const answer = await this.llm.chat(request, onDelta);
|
|
15
|
+
const answer = await this.llm.chat(AgentService.readable(request, this.llm.accepts), onDelta);
|
|
11
16
|
if (!answer) throw new Err("agent.error.llmUnavailable");
|
|
12
17
|
return { text: answer.text ?? "", toolCalls: answer.toolCalls ?? [], stop: answer.stop };
|
|
13
18
|
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Replaces every attachment the provider cannot read with a note naming it, so no adaptor has to think about
|
|
22
|
+
* attachments it does not support and none can lose one quietly. The model has to be *told*, not merely spared:
|
|
23
|
+
* a file that vanishes on the way in is one it answers about from the filename, confidently and wrongly.
|
|
24
|
+
*
|
|
25
|
+
* The note rides in the message text because that is the one field every provider mapping already reads.
|
|
26
|
+
*/
|
|
27
|
+
static readable(request: LlmTurnRequest, accepts: LlmAccepts | undefined): LlmTurnRequest {
|
|
28
|
+
if (!request.messages.some((message) => message.attachments?.length)) return request;
|
|
29
|
+
const messages = request.messages.map((message) => AgentService.readableMessage(message, accepts ?? {}));
|
|
30
|
+
return { ...request, messages };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private static readableMessage(message: AgentWireMessage, accepts: LlmAccepts): AgentWireMessage {
|
|
34
|
+
const { attachments = [], ...rest } = message;
|
|
35
|
+
if (!attachments.length) return message;
|
|
36
|
+
const kept = attachments.filter((attachment) => AgentService.isReadable(attachment, accepts));
|
|
37
|
+
if (kept.length === attachments.length) return message;
|
|
38
|
+
const notes = attachments.filter((attachment) => !kept.includes(attachment)).map(AgentService.note);
|
|
39
|
+
return {
|
|
40
|
+
...rest,
|
|
41
|
+
...(kept.length ? { attachments: kept } : {}),
|
|
42
|
+
text: [message.text, ...notes].filter(Boolean).join("\n\n"),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Extracted text is readable by every model there is; bytes and links need the provider to say so. */
|
|
47
|
+
private static isReadable(attachment: AgentWireAttachment, accepts: LlmAccepts): boolean {
|
|
48
|
+
if (attachment.text) return true;
|
|
49
|
+
if (!attachment.data && !attachment.url) return false;
|
|
50
|
+
return attachment.mimeType.startsWith("image/") ? !!accepts.image : !!accepts.document;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private static note(attachment: AgentWireAttachment): string {
|
|
54
|
+
const why =
|
|
55
|
+
attachment.data || attachment.url
|
|
56
|
+
? "this model cannot read that type"
|
|
57
|
+
: "its content is no longer available, as a reloaded conversation keeps the name and not the bytes";
|
|
58
|
+
return `[Attachment not read: ${attachment.name} (${attachment.mimeType}) — ${why}. Tell the user it was not read instead of guessing what it holds, and ask for the text if the answer needs it.]`;
|
|
59
|
+
}
|
|
14
60
|
}
|
|
@@ -201,7 +201,19 @@ export class DeepseekLlm
|
|
|
201
201
|
: {}),
|
|
202
202
|
},
|
|
203
203
|
];
|
|
204
|
-
return [{ role: "user" as const, content: message
|
|
204
|
+
return [{ role: "user" as const, content: DeepseekLlm.userContent(message) }];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* `accepts` is left undeclared, so by the time an attachment reaches here `AgentService.readable` has reduced it
|
|
209
|
+
* to its text and turned everything else into a note. Each block is labelled because a model handed two
|
|
210
|
+
* unlabelled documents can no longer cite either one.
|
|
211
|
+
*/
|
|
212
|
+
static userContent(message: AgentWireMessage): string {
|
|
213
|
+
const blocks = (message.attachments ?? []).flatMap((attachment) =>
|
|
214
|
+
attachment.text ? [`--- attachment: ${attachment.name} (${attachment.mimeType}) ---\n${attachment.text}`] : [],
|
|
215
|
+
);
|
|
216
|
+
return [message.text, ...blocks].filter(Boolean).join("\n\n");
|
|
205
217
|
}
|
|
206
218
|
|
|
207
219
|
static turnAnswer(answer: DeepseekAnswer): LlmTurnAnswer {
|
|
@@ -12,6 +12,20 @@ export interface AgentWireToolResult {
|
|
|
12
12
|
error?: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* A file the caller attached to one message. Exactly one carrier reaches the model — `data` as inlined bytes, `url`
|
|
17
|
+
* as something the provider fetches, `text` as content already extracted — and which of them a given provider can
|
|
18
|
+
* read is what `LlmAccepts` answers.
|
|
19
|
+
*/
|
|
20
|
+
export interface AgentWireAttachment {
|
|
21
|
+
name: string;
|
|
22
|
+
mimeType: string;
|
|
23
|
+
/** Base64, with no `data:` prefix. */
|
|
24
|
+
data?: string;
|
|
25
|
+
url?: string;
|
|
26
|
+
text?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
/**
|
|
16
30
|
* One transcript message of the in-page agent wire (`use-agentic`'s WIRE.md), typed at both ends independently —
|
|
17
31
|
* the wire is the contract, so the server never imports the client package.
|
|
@@ -19,6 +33,7 @@ export interface AgentWireToolResult {
|
|
|
19
33
|
export interface AgentWireMessage {
|
|
20
34
|
role: "user" | "assistant" | "tool";
|
|
21
35
|
text?: string;
|
|
36
|
+
attachments?: AgentWireAttachment[];
|
|
22
37
|
toolCalls?: AgentWireToolCall[];
|
|
23
38
|
toolResults?: AgentWireToolResult[];
|
|
24
39
|
error?: string;
|
|
@@ -62,6 +77,22 @@ export interface LlmAdaptor {
|
|
|
62
77
|
* answer. An adapter may ignore it — the caller treats zero reported deltas as "answered whole".
|
|
63
78
|
*/
|
|
64
79
|
chat(request: LlmTurnRequest, onDelta?: (delta: string) => void): Promise<LlmTurnAnswer | null>;
|
|
80
|
+
/** Which attachment carriers this provider's model can read. Omitted means text only. */
|
|
81
|
+
readonly accepts?: LlmAccepts;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* What an adaptor's model reads beyond text. Declared rather than defaulted to true, because the failure of
|
|
86
|
+
* guessing wrong is the worst one available: a provider handed bytes it cannot decode either rejects the whole
|
|
87
|
+
* turn or accepts it having seen nothing, and the model then answers confidently about a file it never read.
|
|
88
|
+
* `AgentService` degrades what is not accepted into a note the model can repeat back, so a text-only provider
|
|
89
|
+
* needs no attachment code at all — which is every provider until somebody swaps one in for vision.
|
|
90
|
+
*/
|
|
91
|
+
export interface LlmAccepts {
|
|
92
|
+
/** Inlined or linked image bytes. */
|
|
93
|
+
image?: boolean;
|
|
94
|
+
/** Non-image bytes handed over whole — a PDF the model parses itself. */
|
|
95
|
+
document?: boolean;
|
|
65
96
|
}
|
|
66
97
|
|
|
67
98
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Translator } from "akanjs/client";
|
|
2
2
|
import { parseAkanI18nEnv } from "akanjs/common";
|
|
3
3
|
import { FetchClient } from "akanjs/fetch";
|
|
4
|
-
import type { PromptContent, PromptResult, SerializedSignal } from "akanjs/signal";
|
|
4
|
+
import type { PromptContent, PromptMessage, PromptResult, SerializedSignal } from "akanjs/signal";
|
|
5
5
|
import type { ChatMessage } from "../../vendor/use-agentic";
|
|
6
6
|
|
|
7
7
|
export interface AgentPrompt {
|
|
@@ -59,7 +59,25 @@ export class AgentPrompts {
|
|
|
59
59
|
/** The messages a prompt returns become the user's turn, the way an MCP client sends a `prompts/get` result. */
|
|
60
60
|
static messagesOf(result: PromptResult): ChatMessage[] {
|
|
61
61
|
if (typeof result === "string") return [{ role: "user", text: result }];
|
|
62
|
-
return result.map((message) =>
|
|
62
|
+
return result.map((message) => AgentPrompts.#messageOf(message));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A binary block becomes an attachment. It used to become the string `[image]`, which a model reads as having
|
|
67
|
+
* been shown a picture — so a prompt built with `Msg.imageOf` produced confident answers about bytes that never
|
|
68
|
+
* left the server. The other block types are text already and stay text.
|
|
69
|
+
*/
|
|
70
|
+
static #messageOf(message: PromptMessage): ChatMessage {
|
|
71
|
+
const { role, content } = message;
|
|
72
|
+
if (content.type !== "image" && content.type !== "audio") return { role, text: AgentPrompts.textOf(content) };
|
|
73
|
+
const name = AgentPrompts.#binaryName(content.mimeType);
|
|
74
|
+
return { role, attachments: [{ name, mimeType: content.mimeType, data: content.data }] };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** `Msg.image` carries no filename — the protocol has nowhere to put one — so the type is the label. */
|
|
78
|
+
static #binaryName(mimeType: string) {
|
|
79
|
+
const [kind, subtype] = mimeType.split("/");
|
|
80
|
+
return subtype ? `${kind}.${subtype.split("+")[0]}` : mimeType;
|
|
63
81
|
}
|
|
64
82
|
|
|
65
83
|
static textOf(content: PromptContent): string {
|
|
@@ -248,6 +248,15 @@ export class ScreenReader {
|
|
|
248
248
|
if (href && href !== "#" && !href.startsWith("javascript:") && text !== href) this.#buffer += ` (${href})`;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
/**
|
|
252
|
+
* A control the person cannot use publishes no tool, so saying so here is what turns a silent refusal into a
|
|
253
|
+
* fact the agent could have read. It reads `aria-disabled` too: a styled-off div carries no native property.
|
|
254
|
+
*/
|
|
255
|
+
static #off(el: HTMLElement) {
|
|
256
|
+
const native = (el as HTMLInputElement | HTMLButtonElement).disabled;
|
|
257
|
+
return native || el.getAttribute("aria-disabled") === "true" ? " (disabled)" : "";
|
|
258
|
+
}
|
|
259
|
+
|
|
251
260
|
#button(el: HTMLElement) {
|
|
252
261
|
const before = this.#buffer;
|
|
253
262
|
this.#walkChildren(el);
|
|
@@ -255,7 +264,7 @@ export class ScreenReader {
|
|
|
255
264
|
this.#buffer = before;
|
|
256
265
|
const label = inner || el.getAttribute("aria-label") || "";
|
|
257
266
|
const action = el.getAttribute("data-akan-action");
|
|
258
|
-
if (label || action) this.#buffer += ` [button: ${label}${action ? ` → ${action}` : ""}]`;
|
|
267
|
+
if (label || action) this.#buffer += ` [button${ScreenReader.#off(el)}: ${label}${action ? ` → ${action}` : ""}]`;
|
|
259
268
|
}
|
|
260
269
|
|
|
261
270
|
#control(el: HTMLElement, tag: string) {
|
|
@@ -268,12 +277,13 @@ export class ScreenReader {
|
|
|
268
277
|
el.getAttribute("placeholder") ??
|
|
269
278
|
el.getAttribute("name") ??
|
|
270
279
|
type;
|
|
280
|
+
const off = ScreenReader.#off(el);
|
|
271
281
|
if (type === "password") {
|
|
272
|
-
this.#buffer += ` [input ${name}]`;
|
|
282
|
+
this.#buffer += ` [input ${name}${off}]`;
|
|
273
283
|
return;
|
|
274
284
|
}
|
|
275
285
|
if (type === "checkbox" || type === "radio") {
|
|
276
|
-
this.#buffer += ` [${type} ${name}: ${input.checked ? "on" : "off"}]`;
|
|
286
|
+
this.#buffer += ` [${type} ${name}${off}: ${input.checked ? "on" : "off"}]`;
|
|
277
287
|
return;
|
|
278
288
|
}
|
|
279
289
|
const raw =
|
|
@@ -282,7 +292,7 @@ export class ScreenReader {
|
|
|
282
292
|
(el as unknown as HTMLSelectElement).value)
|
|
283
293
|
: input.value;
|
|
284
294
|
const value = (raw ?? "").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
285
|
-
this.#buffer += ` [${tag === "SELECT" ? "select" : "input"} ${name}: ${JSON.stringify(value)}]`;
|
|
295
|
+
this.#buffer += ` [${tag === "SELECT" ? "select" : "input"} ${name}${off}: ${JSON.stringify(value)}]`;
|
|
286
296
|
}
|
|
287
297
|
|
|
288
298
|
#pre(el: HTMLElement) {
|
|
@@ -89,24 +89,77 @@ const rowEntries = (ref: FormFieldRef, arraySchema: JsonSchema): ToolEntry[] =>
|
|
|
89
89
|
];
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
export interface FieldToolOptions {
|
|
93
|
+
/** The control's own normalizer. Applied to the agent's write exactly as it is to the person's typing. */
|
|
94
|
+
transform?: unknown;
|
|
95
|
+
/** True while the person cannot use the control. Publishes nothing, so the agent gets no lever the screen withholds. */
|
|
96
|
+
disabled?: boolean;
|
|
97
|
+
/** The person can drag entries into a new order, so `move<Field>On<Model>` is a lever the screen really has. */
|
|
98
|
+
sortable?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Reorder-by-position for a list the person can drag, beside the whole-array setter.
|
|
103
|
+
*
|
|
104
|
+
* The drag is the lever the screen actually offers, and it changes no row's content — so an agent asked to move one
|
|
105
|
+
* row should not have to retype the nine it is leaving alone, which is the same argument that gives an embedded-row
|
|
106
|
+
* array its `add`/`sub`. There is no store action behind it: reordering *is* a whole-array write, so this splices
|
|
107
|
+
* the live rows and hands them to the setter the drag hands them to, `transform` deliberately not applied — the
|
|
108
|
+
* values are already stored, and normalizing them again is not something dragging does.
|
|
109
|
+
*/
|
|
110
|
+
const moveEntry = (ref: FormFieldRef, onChange: () => (value: unknown) => unknown): ToolEntry => {
|
|
111
|
+
const name = formSetterNames(capitalize(ref.refName), ref.key).moveFieldOnModel;
|
|
112
|
+
return {
|
|
113
|
+
name,
|
|
114
|
+
description: `Move one entry of ${ref.key} on the ${ref.refName} form to another position, counting from 0. Reorders only — no entry's content changes.`,
|
|
115
|
+
parameters: {
|
|
116
|
+
type: "object",
|
|
117
|
+
properties: { from: { type: "integer" }, to: { type: "integer" } },
|
|
118
|
+
required: ["from", "to"],
|
|
119
|
+
additionalProperties: false,
|
|
120
|
+
},
|
|
121
|
+
effect: "state",
|
|
122
|
+
guard: (args) => {
|
|
123
|
+
const length = rowsOf(ref).length;
|
|
124
|
+
const outside = ["from", "to"].filter((key) => {
|
|
125
|
+
const idx = args[key];
|
|
126
|
+
return typeof idx !== "number" || !Number.isInteger(idx) || idx < 0 || idx >= length;
|
|
127
|
+
});
|
|
128
|
+
if (!outside.length) return true;
|
|
129
|
+
return `${ref.key} has ${length} ${length === 1 ? "entry" : "entries"}, so ${outside.join(" and ")} is out of range.`;
|
|
130
|
+
},
|
|
131
|
+
run: (args) => {
|
|
132
|
+
const rows = [...rowsOf(ref)];
|
|
133
|
+
const [moved] = rows.splice(args.from as number, 1);
|
|
134
|
+
rows.splice(args.to as number, 0, moved);
|
|
135
|
+
return onChange()(rows);
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
92
140
|
/**
|
|
93
|
-
* Publishes the setter a form control is already holding, for exactly as long as the control is
|
|
141
|
+
* Publishes the setter a form control is already holding, for exactly as long as the control is usable.
|
|
94
142
|
*
|
|
95
143
|
* The control is the declaration — the same rule the rest of the surface follows. A handler passed by reference
|
|
96
144
|
* (`onChange={st.do.setTitleOnTask}`) names the field it writes, so the tool and the person press one function;
|
|
97
145
|
* an inline arrow names nothing and publishes nothing, which is the existing `data-akan-action` rule with
|
|
98
146
|
* consequences. Publishing from the form's subscription instead would offer every field of the model, including
|
|
99
147
|
* the ones this template draws no control for.
|
|
148
|
+
*
|
|
149
|
+
* `disabled` withdraws the tool for the same reason the whole surface is declaration-only: a field the person
|
|
150
|
+
* cannot type into is not one an agent may write in their place. It also closes the field to `fill<Model>Form`,
|
|
151
|
+
* whose guard offers only what a control published — so one gate covers both writers.
|
|
100
152
|
*/
|
|
101
|
-
export const useFieldTool = (onChange: unknown, transform
|
|
153
|
+
export const useFieldTool = (onChange: unknown, { transform, disabled, sortable }: FieldToolOptions = {}) => {
|
|
102
154
|
const surface = useSurface();
|
|
103
155
|
const scope = useScopePath();
|
|
104
156
|
const action = actionTagOf(onChange)?.action ?? null;
|
|
157
|
+
const off = !!disabled;
|
|
105
158
|
const live = useRef({ onChange, transform });
|
|
106
159
|
live.current = { onChange, transform };
|
|
107
160
|
const scopeKey = scope.join(".");
|
|
108
161
|
useEffect(() => {
|
|
109
|
-
if (!action) return;
|
|
162
|
+
if (!action || off) return;
|
|
110
163
|
const ref = FormFields.ref(action);
|
|
111
164
|
const schema = ref && FormFields.schema(ref.field);
|
|
112
165
|
if (!ref || !schema) return;
|
|
@@ -122,10 +175,13 @@ export const useFieldTool = (onChange: unknown, transform?: unknown) => {
|
|
|
122
175
|
},
|
|
123
176
|
},
|
|
124
177
|
...rowEntries(ref, schema),
|
|
178
|
+
...(sortable && ref.field.arrDepth > 0
|
|
179
|
+
? [moveEntry(ref, () => live.current.onChange as (value: unknown) => unknown)]
|
|
180
|
+
: []),
|
|
125
181
|
];
|
|
126
182
|
const registered = entries.map((entry) => surface.registerTool(scope, entry));
|
|
127
183
|
return () => {
|
|
128
184
|
for (const unregister of registered) unregister();
|
|
129
185
|
};
|
|
130
|
-
}, [surface, scopeKey, action]);
|
|
186
|
+
}, [surface, scopeKey, action, off, !!sortable]);
|
|
131
187
|
};
|
|
@@ -17,6 +17,11 @@ import { FormFields } from "./formFields";
|
|
|
17
17
|
* controls will render. The **guard** is where the screen gets its say: a plain field has to have published its
|
|
18
18
|
* own setter, and a composite is waved through because its rows are written with `writeOn<Model>(path, value)`,
|
|
19
19
|
* which no control can annotate — so this is the one place an agent can reach a field the screen may not show.
|
|
20
|
+
*
|
|
21
|
+
* Registered `shared`, because the entry is a pure function of `refName`: the schema comes from the model, the
|
|
22
|
+
* guard re-reads the live surface, and every `write` reaches the one store instance. So a form put on screen by a
|
|
23
|
+
* shell that subscribes it (`Model.EditModal`) and by the `Template` inside it registers one declaration twice,
|
|
24
|
+
* which is interchangeable in the exact sense `shared` means — not a clash an app could fix by suppressing one.
|
|
20
25
|
*/
|
|
21
26
|
export const useFormTools = (refName: string | null, write: (action: string, value: unknown) => void) => {
|
|
22
27
|
const surface = useSurface();
|
|
@@ -44,6 +49,7 @@ export const useFormTools = (refName: string | null, write: (action: string, val
|
|
|
44
49
|
additionalProperties: false,
|
|
45
50
|
},
|
|
46
51
|
effect: "state",
|
|
52
|
+
shared: true,
|
|
47
53
|
guard: (args) => {
|
|
48
54
|
const keys = Object.keys(args);
|
|
49
55
|
if (!keys.length) return "Name at least one field to fill.";
|
|
@@ -59,9 +65,14 @@ export const useFormTools = (refName: string | null, write: (action: string, val
|
|
|
59
65
|
const patch = Object.entries(args).map(([key, value]) => {
|
|
60
66
|
const entry = byKey.get(key);
|
|
61
67
|
if (!entry) throw new Error(`The ${refName} form has no field "${key}".`);
|
|
62
|
-
return
|
|
68
|
+
return { entry, value: FormFields.checked(name, key, entry.field, value) };
|
|
63
69
|
});
|
|
64
|
-
|
|
70
|
+
|
|
71
|
+
for (const { entry, value } of patch) {
|
|
72
|
+
const control = surface.tool(AgenticSurface.fullName(scope, entry.action), scope);
|
|
73
|
+
if (control) void control.run({ value });
|
|
74
|
+
else live.current(entry.action, value);
|
|
75
|
+
}
|
|
65
76
|
},
|
|
66
77
|
});
|
|
67
78
|
}, [surface, scopeKey, refName]);
|
package/store/formSetterNames.ts
CHANGED
|
@@ -16,6 +16,11 @@ export const formSetterNames = (className: string, key: string) => {
|
|
|
16
16
|
addFieldOnModel: `add${classKeyName}On${className}`,
|
|
17
17
|
subFieldOnModel: `sub${classKeyName}On${className}`,
|
|
18
18
|
addOrSubFieldOnModel: `addOrSub${classKeyName}On${className}`,
|
|
19
|
+
/**
|
|
20
|
+
* The agent tool a drag-sortable list publishes. No store action answers to it: reordering *is* the whole-array
|
|
21
|
+
* write the drag already performs, so the tool splices the live rows and hands them to the same setter.
|
|
22
|
+
*/
|
|
23
|
+
moveFieldOnModel: `move${classKeyName}On${className}`,
|
|
19
24
|
uploadFieldOnModel: `upload${classKeyName}On${className}`,
|
|
20
25
|
/**
|
|
21
26
|
* The optional hook a store declares to run after this field is written.
|
|
@@ -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" | "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" | "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,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" | "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" | "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>;
|
|
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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LlmTurnRequest } from "./predefinedAdaptor/llm.adaptor";
|
|
1
|
+
import type { LlmAccepts, LlmTurnRequest } from "./predefinedAdaptor/llm.adaptor";
|
|
2
2
|
declare const AgentService_base: import("./serve.d.ts").ServiceCls<"agent", {}, {
|
|
3
3
|
llm: import("./injectInfo.d.ts").InjectInfo<"plug", import("./predefinedAdaptor.d.ts").LlmAdaptor, never, never>;
|
|
4
4
|
}>;
|
|
@@ -8,5 +8,17 @@ export declare class AgentService extends AgentService_base {
|
|
|
8
8
|
toolCalls: import("./predefinedAdaptor.d.ts").AgentWireToolCall[];
|
|
9
9
|
stop: "end" | "toolUse";
|
|
10
10
|
}>;
|
|
11
|
+
/**
|
|
12
|
+
* Replaces every attachment the provider cannot read with a note naming it, so no adaptor has to think about
|
|
13
|
+
* attachments it does not support and none can lose one quietly. The model has to be *told*, not merely spared:
|
|
14
|
+
* a file that vanishes on the way in is one it answers about from the filename, confidently and wrongly.
|
|
15
|
+
*
|
|
16
|
+
* The note rides in the message text because that is the one field every provider mapping already reads.
|
|
17
|
+
*/
|
|
18
|
+
static readable(request: LlmTurnRequest, accepts: LlmAccepts | undefined): LlmTurnRequest;
|
|
19
|
+
private static readableMessage;
|
|
20
|
+
/** Extracted text is readable by every model there is; bytes and links need the provider to say so. */
|
|
21
|
+
private static isReadable;
|
|
22
|
+
private static note;
|
|
11
23
|
}
|
|
12
24
|
export {};
|
|
@@ -57,6 +57,12 @@ export declare class DeepseekLlm extends DeepseekLlm_base implements LlmAdaptor
|
|
|
57
57
|
/** Context rides below the instructions framed as data — screen state must never read as directives. */
|
|
58
58
|
static systemPrompt({ instructions, context }: LlmTurnRequest): string;
|
|
59
59
|
static providerMessages(message: AgentWireMessage): DeepseekMessage[];
|
|
60
|
+
/**
|
|
61
|
+
* `accepts` is left undeclared, so by the time an attachment reaches here `AgentService.readable` has reduced it
|
|
62
|
+
* to its text and turned everything else into a note. Each block is labelled because a model handed two
|
|
63
|
+
* unlabelled documents can no longer cite either one.
|
|
64
|
+
*/
|
|
65
|
+
static userContent(message: AgentWireMessage): string;
|
|
60
66
|
static turnAnswer(answer: DeepseekAnswer): LlmTurnAnswer;
|
|
61
67
|
/** The provider sends arguments as a JSON string; an unparsable one becomes an empty call rather than a crash. */
|
|
62
68
|
static parsedArgs(raw: string | undefined): Record<string, unknown>;
|
|
@@ -10,6 +10,19 @@ export interface AgentWireToolResult {
|
|
|
10
10
|
changes?: unknown[];
|
|
11
11
|
error?: string;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* A file the caller attached to one message. Exactly one carrier reaches the model — `data` as inlined bytes, `url`
|
|
15
|
+
* as something the provider fetches, `text` as content already extracted — and which of them a given provider can
|
|
16
|
+
* read is what `LlmAccepts` answers.
|
|
17
|
+
*/
|
|
18
|
+
export interface AgentWireAttachment {
|
|
19
|
+
name: string;
|
|
20
|
+
mimeType: string;
|
|
21
|
+
/** Base64, with no `data:` prefix. */
|
|
22
|
+
data?: string;
|
|
23
|
+
url?: string;
|
|
24
|
+
text?: string;
|
|
25
|
+
}
|
|
13
26
|
/**
|
|
14
27
|
* One transcript message of the in-page agent wire (`use-agentic`'s WIRE.md), typed at both ends independently —
|
|
15
28
|
* the wire is the contract, so the server never imports the client package.
|
|
@@ -17,6 +30,7 @@ export interface AgentWireToolResult {
|
|
|
17
30
|
export interface AgentWireMessage {
|
|
18
31
|
role: "user" | "assistant" | "tool";
|
|
19
32
|
text?: string;
|
|
33
|
+
attachments?: AgentWireAttachment[];
|
|
20
34
|
toolCalls?: AgentWireToolCall[];
|
|
21
35
|
toolResults?: AgentWireToolResult[];
|
|
22
36
|
error?: string;
|
|
@@ -55,6 +69,21 @@ export interface LlmAdaptor {
|
|
|
55
69
|
* answer. An adapter may ignore it — the caller treats zero reported deltas as "answered whole".
|
|
56
70
|
*/
|
|
57
71
|
chat(request: LlmTurnRequest, onDelta?: (delta: string) => void): Promise<LlmTurnAnswer | null>;
|
|
72
|
+
/** Which attachment carriers this provider's model can read. Omitted means text only. */
|
|
73
|
+
readonly accepts?: LlmAccepts;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* What an adaptor's model reads beyond text. Declared rather than defaulted to true, because the failure of
|
|
77
|
+
* guessing wrong is the worst one available: a provider handed bytes it cannot decode either rejects the whole
|
|
78
|
+
* turn or accepts it having seen nothing, and the model then answers confidently about a file it never read.
|
|
79
|
+
* `AgentService` degrades what is not accepted into a note the model can repeat back, so a text-only provider
|
|
80
|
+
* needs no attachment code at all — which is every provider until somebody swaps one in for vision.
|
|
81
|
+
*/
|
|
82
|
+
export interface LlmAccepts {
|
|
83
|
+
/** Inlined or linked image bytes. */
|
|
84
|
+
image?: boolean;
|
|
85
|
+
/** Non-image bytes handed over whole — a PDF the model parses itself. */
|
|
86
|
+
document?: boolean;
|
|
58
87
|
}
|
|
59
88
|
/**
|
|
60
89
|
* Settings for whichever adaptor fills `LlmAdaptorRole`, registered with `option.setLlm(...)` and injected as the
|