akanjs 3.0.0-alpha.30 → 3.0.0-alpha.32

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.
Files changed (38) hide show
  1. package/dictionary/base.dictionary.ts +19 -0
  2. package/fetch/client/fetchClient.ts +1 -1
  3. package/fetch/client/httpClient.ts +12 -10
  4. package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
  5. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
  6. package/package.json +1 -1
  7. package/server/akanOption.ts +9 -8
  8. package/server/devtools/signalSerializer.ts +1 -1
  9. package/server/devtools/types.ts +2 -2
  10. package/server/di/diLifecycle.ts +6 -3
  11. package/server/resolver/signal.resolver.ts +53 -18
  12. package/signal/guard.ts +15 -0
  13. package/signal/guards.ts +21 -16
  14. package/signal/openapi/openapi.ts +3 -2
  15. package/signal/serializer/fetch.serializer.ts +1 -0
  16. package/signal/signalContext.ts +3 -16
  17. package/signal/types.ts +8 -0
  18. package/types/dictionary/base.dictionary.d.ts +1 -1
  19. package/types/dictionary/dictionary.d.ts +8 -8
  20. package/types/fetch/client/httpClient.d.ts +2 -1
  21. package/types/server/akanOption.d.ts +7 -6
  22. package/types/server/devtools/types.d.ts +2 -2
  23. package/types/server/resolver/signal.resolver.d.ts +3 -1
  24. package/types/signal/guard.d.ts +1 -0
  25. package/types/signal/guards.d.ts +10 -7
  26. package/types/signal/types.d.ts +7 -0
  27. package/types/ui/Agent/Chat.d.ts +4 -1
  28. package/types/ui/Agent/ChatCommands.d.ts +34 -0
  29. package/types/ui/Agent/Menu.d.ts +12 -0
  30. package/types/ui/Signal/endpointEntries.d.ts +5 -1
  31. package/types/vendor/use-agentic/AgentSession.d.ts +14 -2
  32. package/types/vendor/use-agentic/types.d.ts +5 -0
  33. package/ui/Agent/Chat.tsx +66 -33
  34. package/ui/Agent/ChatCommands.ts +129 -0
  35. package/ui/Agent/Menu.tsx +58 -0
  36. package/ui/Signal/endpointEntries.ts +15 -4
  37. package/vendor/use-agentic/AgentSession.ts +40 -5
  38. package/vendor/use-agentic/types.ts +5 -0
@@ -56,6 +56,25 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
56
56
  agentAnswer: ["Type your answer...", "답변을 입력하세요..."],
57
57
  agentContinue: ["This is taking a while. Keep going?", "시간이 걸리고 있습니다. 계속할까요?"],
58
58
  agentKeepGoing: ["Keep going", "계속하기"],
59
+ agentCmdNew: ["Start a new conversation", "새 대화 시작"],
60
+ agentCmdRetry: ["Send the last message again", "마지막 메시지 다시 보내기"],
61
+ agentCmdCopy: ["Copy this conversation", "이 대화 복사"],
62
+ agentCmdHelp: ["What you can do here", "여기서 할 수 있는 것"],
63
+ agentCmdTools: ["List this screen's tools", "이 화면의 툴 목록"],
64
+ agentHelpIntro: [
65
+ "Ask about this page, or tell the agent what to do. Commands:",
66
+ "이 화면에 대해 묻거나 할 일을 지시하세요. 커맨드:",
67
+ ],
68
+ agentHelpNote: [
69
+ "The agent asks before anything it should not decide alone, and Stop ends a running turn.",
70
+ "에이전트는 혼자 결정하면 안 되는 일을 하기 전에 물어보며, 중지는 진행 중인 턴을 끝냅니다.",
71
+ ],
72
+ agentNothingToRetry: ["There is no message to send again.", "다시 보낼 메시지가 없습니다."],
73
+ agentBusy: ["A turn is still running. Stop it first.", "진행 중인 턴이 있습니다. 먼저 중지하세요."],
74
+ agentCopied: ["Conversation copied to the clipboard.", "대화를 클립보드에 복사했습니다."],
75
+ agentCopyFailed: ["Could not reach the clipboard.", "클립보드에 접근할 수 없습니다."],
76
+ agentToolsHead: ["What the agent can do on this screen:", "이 화면에서 에이전트가 할 수 있는 일:"],
77
+ agentToolsState: ["Readable state:", "읽을 수 있는 상태:"],
59
78
  send: ["Send", "보내기"],
60
79
  stop: ["Stop", "중지"],
61
80
  skip: ["Skip", "건너뛰기"],
@@ -297,7 +297,7 @@ export class FetchClient {
297
297
  const argMap = new Map(serializerMap.entries().map(([key, serializer], idx) => [key, serializer(args[idx])]));
298
298
  const url = FetchClient.makeHttpUrl(key, endpoint, prefix, argMap);
299
299
  const body = HttpClient.makeBody(bodyArgs, uploadArgs, argMap);
300
- const response = await this.http.post(url, body, {
300
+ const response = await this.http.send(endpoint.method ?? "POST", url, body, {
301
301
  headers: this.#makeAuthHeaders(option),
302
302
  baseUrl: option?.origin,
303
303
  });
@@ -1,4 +1,4 @@
1
- import type { SerializedArg } from "akanjs/signal";
1
+ import type { HttpMutationMethod, SerializedArg } from "akanjs/signal";
2
2
 
3
3
  export interface ErrorResponsePayload {
4
4
  error: string;
@@ -53,31 +53,33 @@ export class HttpClient {
53
53
  if (data instanceof FormData) return { body: data, headers: {} };
54
54
  return { body: JSON.stringify(data), headers: { "Content-Type": "application/json" } };
55
55
  }
56
- async put<Returns = unknown>(
56
+ async send<Returns = unknown>(
57
+ method: HttpMutationMethod,
57
58
  url: string,
58
59
  data: FormData | Record<string, unknown>,
59
60
  options: FetchOptions = {},
60
61
  ): Promise<Returns> {
61
62
  const { body, headers } = this.#makeReqContent(data);
62
63
  const res = await fetch(`${this.#resolveBaseUrl(options.baseUrl)}${url}`, {
63
- method: "PUT",
64
+ method,
64
65
  body,
65
66
  headers: { ...headers, ...options.headers },
66
67
  });
67
68
  return await this.#readJsonResponse<Returns>(res);
68
69
  }
70
+ async put<Returns = unknown>(
71
+ url: string,
72
+ data: FormData | Record<string, unknown>,
73
+ options: FetchOptions = {},
74
+ ): Promise<Returns> {
75
+ return await this.send<Returns>("PUT", url, data, options);
76
+ }
69
77
  async post<Returns = unknown>(
70
78
  url: string,
71
79
  data: FormData | Record<string, unknown>,
72
80
  options: FetchOptions = {},
73
81
  ): Promise<Returns> {
74
- const { body, headers } = this.#makeReqContent(data);
75
- const res = await fetch(`${this.#resolveBaseUrl(options.baseUrl)}${url}`, {
76
- method: "POST",
77
- body,
78
- headers: { ...headers, ...options.headers },
79
- });
80
- return await this.#readJsonResponse<Returns>(res);
82
+ return await this.send<Returns>("POST", url, data, options);
81
83
  }
82
84
  async delete<Returns = unknown>(url: string, options: FetchOptions = {}): Promise<Returns> {
83
85
  const res = await fetch(`${this.#resolveBaseUrl(options.baseUrl)}${url}`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-alpha.30",
3
+ "version": "3.0.0-alpha.32",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -1,6 +1,6 @@
1
1
  import type { BackendEnv, PromiseOrObject } from "akanjs/base";
2
2
  import type { Adaptor, AdaptorCls, LlmOption } from "akanjs/service";
3
- import type { AgentRelayPolicy, MiddlewareCls } from "akanjs/signal";
3
+ import type { GuardCls, MiddlewareCls } from "akanjs/signal";
4
4
  import type { McpServerOption } from "./akanServer";
5
5
  import type { WebProxyRegistration } from "./proxy";
6
6
  import { HostBasePathWebProxy, LocaleWebProxy } from "./proxy";
@@ -12,7 +12,7 @@ export interface AdaptorOverride {
12
12
 
13
13
  /**
14
14
  * App/library server option builder: use objects, signal middleware, adaptor overrides, web proxies, and the
15
- * server settings an app owns — MCP, the agent relay's access policy, and the LLM the relay speaks to.
15
+ * server settings an app owns — MCP, the agent relay's access guards, and the LLM the relay speaks to.
16
16
  */
17
17
  export class AkanOption<Env extends BackendEnv = BackendEnv> {
18
18
  readonly #getUses: ((env: Env) => Record<string, PromiseOrObject<unknown>>)[];
@@ -21,7 +21,7 @@ export class AkanOption<Env extends BackendEnv = BackendEnv> {
21
21
  readonly #webProxies: WebProxyRegistration[] = [];
22
22
  readonly #getLlms: ((env: Env) => LlmOption)[] = [];
23
23
  #mcp: boolean | McpServerOption | undefined;
24
- #agentAccess: AgentRelayPolicy | null | undefined;
24
+ #agentAccess: GuardCls | GuardCls[] | null | undefined;
25
25
  constructor() {
26
26
  this.#getUses = [];
27
27
  }
@@ -54,11 +54,12 @@ export class AkanOption<Env extends BackendEnv = BackendEnv> {
54
54
  return this;
55
55
  }
56
56
  /**
57
- * Who may spend the LLM key through the `runAgentTurn` relay. With no policy the call is refused the same
58
- * answer `None` gives — because the framework has no account model to gate on. `null` clears the policy.
57
+ * Who may spend the LLM key through the `runAgentTurn` relay, named as the guards any other endpoint would
58
+ * name. With none the call is refused — the same answer `None` gives — because the framework has no account
59
+ * model to gate on. Several are ANDed; `null` clears what a library set.
59
60
  */
60
- setAgentAccess(policy: AgentRelayPolicy | null) {
61
- this.#agentAccess = policy;
61
+ setAgentAccess(guards: GuardCls | GuardCls[] | null) {
62
+ this.#agentAccess = guards;
62
63
  return this;
63
64
  }
64
65
  /** Settings for whichever adaptor fills `LlmAdaptorRole`, injected into it as the `llmOption` use. */
@@ -83,7 +84,7 @@ export class AkanOption<Env extends BackendEnv = BackendEnv> {
83
84
  getMcp(): boolean | McpServerOption | undefined {
84
85
  return this.#mcp;
85
86
  }
86
- getAgentAccess(): AgentRelayPolicy | null | undefined {
87
+ getAgentAccess(): GuardCls | GuardCls[] | null | undefined {
87
88
  return this.#agentAccess;
88
89
  }
89
90
  getLlm(env: Env): LlmOption {
@@ -212,7 +212,7 @@ export class SignalSerializer {
212
212
  source: typeof source === "function" ? source(key) : source,
213
213
  type: node.type,
214
214
  transport,
215
- method: node.type === "mutation" ? "POST" : transport === "http" ? "GET" : null,
215
+ method: node.type === "mutation" ? (info.signalOption.method ?? "POST") : transport === "http" ? "GET" : null,
216
216
  path:
217
217
  transport === "ws"
218
218
  ? SignalSerializer.#joinPath(prefix, websocketPrefix)
@@ -10,7 +10,7 @@
10
10
 
11
11
  import type { ConstantType, TextFieldRole } from "akanjs/constant";
12
12
  import type { RootDictionary } from "akanjs/dictionary";
13
- import type { ArgType, SerializedArg, SerializedReturns } from "akanjs/signal";
13
+ import type { ArgType, HttpMutationMethod, SerializedArg, SerializedReturns } from "akanjs/signal";
14
14
 
15
15
  export interface DevtoolsIndex {
16
16
  version: 1;
@@ -194,7 +194,7 @@ export interface RouteRow {
194
194
  source: "declared" | "crud" | "slice";
195
195
  type: "query" | "mutation" | "message" | "pubsub" | "prompt";
196
196
  transport: "http" | "ws";
197
- method: "GET" | "POST" | null;
197
+ method: "GET" | HttpMutationMethod | null;
198
198
  /** Fully prefixed, `:param` placeholders intact — e.g. `/api/user/:userId`. */
199
199
  path: string;
200
200
  guards: string[];
@@ -211,8 +211,11 @@ export class DiLifecycle {
211
211
  wsRoutes: endpointWsRoutes,
212
212
  routeOptions: endpointRouteOptions,
213
213
  } = await this.#initializeEndpoint();
214
+ const routes: SignalRoutes["routes"] = {};
215
+ SignalResolver.mergeHttpRoutes(routes, sliceRoutes);
216
+ SignalResolver.mergeHttpRoutes(routes, endpointRoutes);
214
217
  return {
215
- routes: { ...sliceRoutes, ...endpointRoutes },
218
+ routes,
216
219
  wsRoutes: { ...sliceWsRoutes, ...endpointWsRoutes },
217
220
  routeOptions: { ...(sliceRouteOptions ?? {}), ...(endpointRouteOptions ?? {}) },
218
221
  };
@@ -549,7 +552,7 @@ export class DiLifecycle {
549
552
  live: this.live,
550
553
  middleware: this.#middleware,
551
554
  });
552
- Object.assign(routes, sliceRoutes);
555
+ SignalResolver.mergeHttpRoutes(routes, sliceRoutes);
553
556
  Object.assign(routeOptions, sliceRouteOptions);
554
557
  Object.assign(wsRoutes, sliceWsRoutes);
555
558
  this.registry.endpointCls.set(refName, sliceEndpointCls);
@@ -586,7 +589,7 @@ export class DiLifecycle {
586
589
  live: this.live,
587
590
  middleware: this.#middleware,
588
591
  });
589
- Object.assign(routes, endpointRoutes);
592
+ SignalResolver.mergeHttpRoutes(routes, endpointRoutes);
590
593
  Object.assign(routeOptions, endpointRouteOptions);
591
594
  Object.assign(wsRoutes, endpointWsRoutes);
592
595
  this.registry.endpointCls.set(refName, endpointCls);
@@ -33,6 +33,9 @@ import type { SliceInfo } from "../../signal/sliceInfo";
33
33
  import type { WebsocketMessageData, WebsocketSubscribeAck } from "../../signal/types";
34
34
  import type { HttpRoutes, SignalRoutes, WebsocketRoutes } from "../types";
35
35
 
36
+ type HttpRouteHandler = (req: Bun.BunRequest) => Response | Promise<Response | undefined> | undefined;
37
+ type HttpMethodRoutes = Record<string, HttpRouteHandler>;
38
+
36
39
  export class SignalResolver {
37
40
  static logger = new Logger("SignalResolver");
38
41
 
@@ -308,6 +311,28 @@ export class SignalResolver {
308
311
  Bun.ServerWebSocket<unknown>,
309
312
  Map<string, SignalContext<WebSocketExecutionContext>>
310
313
  >();
314
+ /**
315
+ * A path may legitimately carry several methods — a `query` GET and a `mutation` POST sharing a custom `path` —
316
+ * so methods merge rather than replace. The same method twice leaves one of the two endpoints unreachable with
317
+ * nothing said about it, and the shadowed half is as easily the guarded one, so it fails the boot instead.
318
+ */
319
+ static #mountHttpRoute(routes: HttpRoutes, path: string, handlers: HttpMethodRoutes, owner?: string) {
320
+ const table = routes as Record<string, HttpMethodRoutes | undefined>;
321
+ const existing = table[path];
322
+ const conflict = Object.keys(handlers).find((method) => !!existing?.[method]);
323
+ if (conflict)
324
+ throw new Error(
325
+ `Route conflict: ${conflict} ${path} is declared more than once${owner ? ` (by "${owner}")` : ""}.`,
326
+ );
327
+ table[path] = { ...existing, ...handlers };
328
+ }
329
+
330
+ /** Same rule across endpoint classes, which are resolved one at a time and then folded into one table. */
331
+ static mergeHttpRoutes(target: HttpRoutes, source: HttpRoutes) {
332
+ for (const [path, handlers] of Object.entries((source ?? {}) as Record<string, HttpMethodRoutes>))
333
+ SignalResolver.#mountHttpRoute(target, path, handlers);
334
+ }
335
+
311
336
  static resolveEndpoint(
312
337
  endpointCls: EndpointCls,
313
338
  endpoint: Endpoint,
@@ -341,26 +366,38 @@ export class SignalResolver {
341
366
  }).init();
342
367
  return await context.exec();
343
368
  });
369
+ if (endpointInfo.signalOption.method && endpointInfo.type !== "mutation")
370
+ SignalResolver.logger.warn(
371
+ `"${key}" declares method ${endpointInfo.signalOption.method} on a ${endpointInfo.type}, which is ignored.`,
372
+ );
344
373
  switch (endpointInfo.type) {
345
374
  case "query":
346
- routes[path] = SignalResolver.#canUsePrimitiveQueryFastPath(endpointInfo, middleware)
347
- ? {
348
- GET: async (req) => {
349
- if (SignalResolver.#hasAuthCredential(req)) return await normalHttpHandler(req);
350
- return await SignalContext.try(endpoint, endpointInfo, key, async () => {
351
- const result = await endpointInfo.execFn?.call(endpoint);
352
- return result instanceof Response ? result : Response.json(result);
353
- });
375
+ SignalResolver.#mountHttpRoute(
376
+ routes,
377
+ path,
378
+ SignalResolver.#canUsePrimitiveQueryFastPath(endpointInfo, middleware)
379
+ ? {
380
+ GET: async (req) => {
381
+ if (SignalResolver.#hasAuthCredential(req)) return await normalHttpHandler(req);
382
+ return await SignalContext.try(endpoint, endpointInfo, key, async () => {
383
+ const result = await endpointInfo.execFn?.call(endpoint);
384
+ return result instanceof Response ? result : Response.json(result);
385
+ });
386
+ },
387
+ }
388
+ : {
389
+ GET: normalHttpHandler,
354
390
  },
355
- }
356
- : {
357
- GET: normalHttpHandler,
358
- };
391
+ key,
392
+ );
359
393
  break;
360
394
  case "mutation":
361
- routes[path] = {
362
- POST: normalHttpHandler,
363
- };
395
+ SignalResolver.#mountHttpRoute(
396
+ routes,
397
+ path,
398
+ { [endpointInfo.signalOption.method ?? "POST"]: normalHttpHandler },
399
+ key,
400
+ );
364
401
  break;
365
402
  case "prompt":
366
403
 
@@ -368,9 +405,7 @@ export class SignalResolver {
368
405
  SignalResolver.logger.warn(
369
406
  `Prompt "${key}" declares no guards, and its GET route is mounted whether or not this app enables MCP.`,
370
407
  );
371
- routes[path] = {
372
- GET: normalHttpHandler,
373
- };
408
+ SignalResolver.#mountHttpRoute(routes, path, { GET: normalHttpHandler }, key);
374
409
  break;
375
410
  case "pubsub":
376
411
  wsRoutes[key] = async (ws, message, event) => {
package/signal/guard.ts CHANGED
@@ -28,3 +28,18 @@ export const guard = <T extends string>(name: T): GuardCls<T> => {
28
28
  }
29
29
  };
30
30
  };
31
+
32
+ /**
33
+ * Guards read everything from the context they are handed and are already required to be side-effect free and
34
+ * safe to re-run — `SignalResolver.revalidateWsRooms` re-runs them outside of any request — so one instance per
35
+ * class serves every call instead of one per guard per request. Built on first use, not at registration: a guard
36
+ * may be declared long before the container it reads from is up.
37
+ */
38
+ const instances = new WeakMap<GuardCls, Guard>();
39
+ export const guardOf = (GuardCls: GuardCls): Guard => {
40
+ const cached = instances.get(GuardCls);
41
+ if (cached) return cached;
42
+ const guard = new GuardCls();
43
+ instances.set(GuardCls, guard);
44
+ return guard;
45
+ };
package/signal/guards.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Logger } from "akanjs/common";
2
- import type { Guard, GuardScope } from "./guard";
2
+ import { type Guard, type GuardCls, type GuardScope, guardOf } from "./guard";
3
3
  import type { SignalContext } from "./signalContext";
4
4
 
5
5
  export class Public implements Guard {
@@ -18,40 +18,45 @@ export class None implements Guard {
18
18
  }
19
19
  }
20
20
 
21
- export type AgentRelayPolicy = (context: SignalContext) => boolean | Promise<boolean>;
22
-
23
21
  /**
24
22
  * Gate for the `runAgentTurn` relay. Every tool runs in the caller's own browser session, so the LLM key is the
25
23
  * one thing this endpoint spends — ungated, any visitor can bill the app's provider through fetch alone.
26
24
  *
27
- * The framework has no account model to gate on, so with no policy registered it refuses every call — the same
28
- * answer `None` gives. An app opens it at boot, e.g.
29
- * `AgentRelayAccess.use((context) => !!context.get("account"))`. The policy is the app's; the framework cannot know it.
25
+ * The framework has no account model to gate on, so with no guard registered it refuses every call — the same
26
+ * answer `None` gives. An app names its own at boot, e.g. `AgentRelayAccess.use(Every)`, usually through
27
+ * `option.setAgentAccess(...)`. Several are ANDed, as an endpoint's own `guards` array is.
30
28
  */
31
29
  export class AgentRelayAccess implements Guard {
32
30
 
33
31
  static name = "AgentRelayAccess";
34
-
35
- static scope: GuardScope = "account";
36
- static #policy: AgentRelayPolicy | null = null;
32
+ static #guards: GuardCls[] = [];
37
33
  static #logger = new Logger("AgentRelayAccess");
38
34
 
39
- static use(policy: AgentRelayPolicy | null) {
40
- AgentRelayAccess.#policy = policy;
35
+ static use(guards: GuardCls | GuardCls[] | null) {
36
+ AgentRelayAccess.#guards = guards ? (Array.isArray(guards) ? [...guards] : [guards]) : [];
41
37
  }
42
38
 
43
39
  static get hasPolicy() {
44
- return !!AgentRelayAccess.#policy;
40
+ return !!AgentRelayAccess.#guards.length;
41
+ }
42
+
43
+ /**
44
+ * Whatever the registered guards need, since this one only forwards to them. With none registered it reads the
45
+ * caller and nothing else — the refusal is unconditional — so a listing may still evaluate it argument-free.
46
+ */
47
+ static get scope(): GuardScope {
48
+ return AgentRelayAccess.#guards.some((GuardCls) => GuardCls.scope === "resource") ? "resource" : "account";
45
49
  }
46
50
 
47
51
  async canPass(context: SignalContext): Promise<boolean> {
48
- const policy = AgentRelayAccess.#policy;
49
- if (!policy) return false;
52
+ const guards = AgentRelayAccess.#guards;
53
+ if (!guards.length) return false;
50
54
  try {
51
- return await policy(context);
55
+ for (const GuardCls of guards) if (!(await guardOf(GuardCls).canPass(context))) return false;
56
+ return true;
52
57
  } catch (error) {
53
58
  AgentRelayAccess.#logger.warn(
54
- `agent relay policy threw, failing closed: ${error instanceof Error ? error.message : String(error)}`,
59
+ `agent relay guard threw, failing closed: ${error instanceof Error ? error.message : String(error)}`,
55
60
  );
56
61
  return false;
57
62
  }
@@ -61,8 +61,9 @@ export const createOpenApiDocument = (
61
61
  for (const [refName, signal] of Object.entries(serializedSignal)) {
62
62
  if (excludeSignals.has(refName)) continue;
63
63
  for (const [endpointKey, endpoint] of collectRestEndpoints(refName, signal)) {
64
- const method = httpMethods[endpoint.type as keyof typeof httpMethods];
65
- if (!method) continue;
64
+ const declaredMethod = httpMethods[endpoint.type as keyof typeof httpMethods];
65
+ if (!declaredMethod) continue;
66
+ const method = endpoint.type === "mutation" ? (endpoint.method?.toLowerCase() ?? declaredMethod) : declaredMethod;
66
67
 
67
68
  const path = toOpenApiPath(FetchClient.makeHttpUrl(endpointKey, endpoint, signal.prefix, new Map()));
68
69
  if (!options.includeNonStandardPaths && isNonStandardOpenApiPath(path)) continue;
@@ -65,6 +65,7 @@ export class FetchSerializer {
65
65
  args: endpointInfo.args.map(FetchSerializer.#serializeArg),
66
66
  returns: FetchSerializer.#serializeReturns(endpointInfo),
67
67
  ...(endpointInfo.signalOption.path ? { path: endpointInfo.signalOption.path } : {}),
68
+ ...(endpointInfo.signalOption.method ? { method: endpointInfo.signalOption.method } : {}),
68
69
  ...(endpointInfo.signalOption.fileUpload ? { fileUpload: true } : {}),
69
70
  ...(guards?.length ? { guards } : {}),
70
71
  };
@@ -18,7 +18,7 @@ import type { Adaptor, AdaptorCls, DatabaseService, InjectRegistry, LiveRegistry
18
18
  import type { Internal, InternalCls, InternalInfo, MiddlewareCls } from ".";
19
19
  import type { EndpointInfo, EndpointType } from "./endpointInfo";
20
20
  import { Exception } from "./exception";
21
- import type { Guard, GuardCls } from "./guard";
21
+ import { guardOf } from "./guard";
22
22
 
23
23
  import { Msg } from "./mcp/Msg";
24
24
  import { isTraceEnabled, runWithTrace, SignalTrace, traceSpan } from "./trace";
@@ -128,25 +128,12 @@ export class SignalContext<
128
128
  }
129
129
  return this;
130
130
  }
131
- /**
132
- * Guards read everything from the context they are handed and are already required to be side-effect free and
133
- * safe to re-run — `SignalResolver.revalidateWsRooms` re-runs them outside of any request — so one instance per
134
- * class serves every call instead of one per guard per request.
135
- */
136
- static #guards = new WeakMap<GuardCls, Guard>();
137
- static #getGuard(GuardCls: GuardCls): Guard {
138
- const cached = SignalContext.#guards.get(GuardCls);
139
- if (cached) return cached;
140
- const guard = new GuardCls();
141
- SignalContext.#guards.set(GuardCls, guard);
142
- return guard;
143
- }
144
131
  async #checkGuards() {
145
132
  const guards = this.endpointInfo.signalOption.guards ?? [];
146
133
  if (guards.length === 0) return;
147
134
  await Promise.all(
148
135
  guards.map(async (GuardCls) => {
149
- const canPass = await SignalContext.#getGuard(GuardCls).canPass(this);
136
+ const canPass = await guardOf(GuardCls).canPass(this);
150
137
  if (!canPass) throw new Exception.Forbidden(`Access denied by guard: ${GuardCls.name}`);
151
138
  }),
152
139
  );
@@ -179,7 +166,7 @@ export class SignalContext<
179
166
  await this.#withMiddleware(
180
167
  async () => {
181
168
  for (const GuardCls of guards) {
182
- if (!(await SignalContext.#getGuard(GuardCls).canPass(this)))
169
+ if (!(await guardOf(GuardCls).canPass(this)))
183
170
  throw new Exception.Forbidden(`Access denied by guard: ${GuardCls.name}`);
184
171
  }
185
172
  },
package/signal/types.ts CHANGED
@@ -61,6 +61,8 @@ interface TimerOption {
61
61
  enabled?: boolean;
62
62
  }
63
63
 
64
+ export type HttpMutationMethod = "POST" | "PATCH" | "PUT" | "DELETE";
65
+
64
66
  export interface SignalOption<Response = any, Nullable extends boolean = false, _Key = keyof UnCls<Response>>
65
67
  extends InitOption,
66
68
  TimerOption {
@@ -76,6 +78,11 @@ export interface SignalOption<Response = any, Nullable extends boolean = false,
76
78
  middlewares?: MiddlewareCls[];
77
79
  prefix?: false | string;
78
80
  globalPrefix?: false;
81
+ /**
82
+ * HTTP verb for a `mutation`, `POST` unless named. Only a foreign wire protocol needs the others — a client
83
+ * that cannot be changed and sends `PATCH /rest/v1/<table>`. Every other endpoint type ignores it.
84
+ */
85
+ method?: HttpMutationMethod;
79
86
  /** Marks this mutation as the framework file-upload endpoint (see resolveFileUploadCapability). */
80
87
  fileUpload?: boolean;
81
88
 
@@ -92,6 +99,7 @@ interface SerializedSignalOption {
92
99
  prefix?: false | string;
93
100
  globalPrefix?: false;
94
101
  guards?: string[];
102
+ method?: HttpMutationMethod;
95
103
  fileUpload?: boolean;
96
104
  }
97
105
  export interface SerializedSlice extends SerializedSignalOption {}
@@ -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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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" | "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";