akanjs 3.0.0-alpha.16 → 3.0.0-alpha.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/constant/index.ts +1 -0
- package/constant/labelOf.ts +17 -0
- package/dictionary/agent.dictionary.ts +31 -0
- package/dictionary/agentTurn.dictionary.ts +29 -0
- package/dictionary/base.dictionary.ts +8 -0
- package/dictionary/dictionary.ts +9 -3
- package/fetch/agentTurn.ts +19 -0
- package/fetch/client/fetchClient.ts +34 -12
- package/fetch/index.ts +1 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local.db-wal +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
- package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-wal +0 -0
- package/package.json +10 -8
- package/server/akanApp.ts +13 -34
- package/server/akanOption.ts +56 -2
- package/server/akanServer.ts +11 -6
- package/server/animatedImage.ts +83 -0
- package/server/di/diLifecycle.ts +30 -2
- package/server/di/predefinedAdaptor.ts +6 -0
- package/server/imageOptimizer.ts +33 -46
- package/server/rscWorker.tsx +6 -2
- package/server/vendor/akanjs-fetch.ts +1 -0
- package/service/agent.service.ts +14 -0
- package/service/base.service.ts +2 -1
- package/service/index.ts +1 -0
- package/service/predefinedAdaptor/deepseekLlm.ts +230 -0
- package/service/predefinedAdaptor/index.ts +2 -0
- package/service/predefinedAdaptor/llm.adaptor.ts +76 -0
- package/service/predefinedAdaptor/role.adaptor.ts +2 -0
- package/service/predefinedAdaptor/schedule.adaptor.ts +12 -5
- package/signal/agent.signal.ts +42 -0
- package/signal/agentTurn.ts +8 -0
- package/signal/agentTurnStream.ts +48 -0
- package/signal/base.signal.ts +2 -1
- package/signal/guards.ts +49 -0
- package/signal/index.ts +3 -0
- package/store/agent/AgentBridge.ts +46 -4
- package/store/agent/AgentContext.ts +74 -0
- package/store/agent/AgentPrompts.ts +82 -0
- package/store/agent/AgentVisibility.ts +68 -0
- package/store/agent/ScreenReader.ts +208 -0
- package/store/agent/StoreCatalogue.ts +58 -5
- package/store/agent/StoreSurfaceSource.ts +122 -0
- package/store/agent/index.ts +6 -0
- package/store/agent/storeSurface.ts +28 -0
- package/store/agentic/StToolBuilder.ts +199 -0
- package/store/agentic/attachAgentic.ts +22 -0
- package/store/agentic/index.ts +5 -0
- package/store/agentic/readableValue.ts +17 -0
- package/store/agentic/useStExpose.ts +18 -0
- package/store/agentic/useStState.ts +35 -0
- package/store/baseSt.ts +7 -0
- package/store/index.ts +1 -0
- package/store/store.ts +10 -1
- package/store/storeInstance.ts +107 -2
- package/store/storeRegistry.ts +6 -1
- package/store/types.ts +11 -0
- package/store/withSelector.ts +5 -4
- package/test/registerDom.ts +4 -0
- package/types/constant/index.d.ts +1 -0
- package/types/constant/labelOf.d.ts +6 -0
- package/types/dictionary/agent.dictionary.d.ts +1 -0
- package/types/dictionary/agentTurn.dictionary.d.ts +1 -0
- package/types/dictionary/base.dictionary.d.ts +1 -1
- package/types/dictionary/dictionary.d.ts +11 -9
- package/types/fetch/agentTurn.d.ts +593 -0
- package/types/fetch/index.d.ts +1 -0
- package/types/server/akanApp.d.ts +0 -6
- package/types/server/akanOption.d.ts +30 -2
- package/types/server/animatedImage.d.ts +7 -0
- package/types/server/di/predefinedAdaptor.d.ts +4 -1
- package/types/server/vendor/akanjs-fetch.d.ts +1 -0
- package/types/service/agent.service.d.ts +12 -0
- package/types/service/base.service.d.ts +4 -0
- package/types/service/index.d.ts +1 -0
- package/types/service/predefinedAdaptor/deepseekLlm.d.ts +64 -0
- package/types/service/predefinedAdaptor/index.d.ts +2 -0
- package/types/service/predefinedAdaptor/llm.adaptor.d.ts +68 -0
- package/types/service/predefinedAdaptor/role.adaptor.d.ts +2 -0
- package/types/service/predefinedAdaptor/schedule.adaptor.d.ts +10 -6
- package/types/signal/agent.signal.d.ts +29 -0
- package/types/signal/agentTurn.d.ts +9 -0
- package/types/signal/agentTurnStream.d.ts +16 -0
- package/types/signal/base.signal.d.ts +1 -1
- package/types/signal/guards.d.ts +19 -0
- package/types/signal/index.d.ts +3 -0
- package/types/store/agent/AgentBridge.d.ts +12 -3
- package/types/store/agent/AgentContext.d.ts +16 -0
- package/types/store/agent/AgentPrompts.d.ts +31 -0
- package/types/store/agent/AgentVisibility.d.ts +21 -0
- package/types/store/agent/ScreenReader.d.ts +11 -0
- package/types/store/agent/StoreCatalogue.d.ts +5 -0
- package/types/store/agent/StoreSurfaceSource.d.ts +18 -0
- package/types/store/agent/index.d.ts +6 -0
- package/types/store/agent/storeSurface.d.ts +14 -0
- package/types/store/agentic/StToolBuilder.d.ts +41 -0
- package/types/store/agentic/attachAgentic.d.ts +14 -0
- package/types/store/agentic/index.d.ts +5 -0
- package/types/store/agentic/readableValue.d.ts +3 -0
- package/types/store/agentic/useStExpose.d.ts +8 -0
- package/types/store/agentic/useStState.d.ts +12 -0
- package/types/store/agentic.d.ts +1 -0
- package/types/store/baseSt.d.ts +28 -21
- package/types/store/index.d.ts +1 -0
- package/types/store/store.d.ts +2 -1
- package/types/store/storeInstance.d.ts +16 -1
- package/types/store/storeRegistry.d.ts +2 -0
- package/types/store/types.d.ts +11 -0
- package/types/store/withSelector.d.ts +5 -4
- package/types/test/registerDom.d.ts +1 -0
- package/types/ui/Agent/Approval.d.ts +7 -0
- package/types/ui/Agent/Bubble.d.ts +13 -0
- package/types/ui/Agent/Chat.d.ts +24 -0
- package/types/ui/Agent/Context.d.ts +9 -0
- package/types/ui/Agent/Dock.d.ts +1 -1
- package/types/ui/Agent/Guide.d.ts +9 -0
- package/types/ui/Agent/StateKey.d.ts +2 -1
- package/types/ui/Agent/Zone.d.ts +23 -0
- package/types/ui/Agent/fetchRunner.d.ts +12 -0
- package/types/ui/Agent/index.d.ts +6 -0
- package/types/ui/Agent/index_.d.ts +1 -0
- package/types/ui/Agent/sessionHistory.d.ts +12 -0
- package/types/ui/UiOverride/context.d.ts +2 -0
- package/types/ui/recipe/badgeRecipe.d.ts +2 -2
- package/types/ui/recipe/buttonRecipe.d.ts +2 -2
- package/types/vendor/use-agentic/AgentProvider.d.ts +18 -0
- package/types/vendor/use-agentic/AgentScope.d.ts +9 -0
- package/types/vendor/use-agentic/AgentSession.d.ts +50 -0
- package/types/vendor/use-agentic/Agentic.d.ts +21 -0
- package/types/vendor/use-agentic/AgenticSurface.d.ts +44 -0
- package/types/vendor/use-agentic/httpRunner.d.ts +15 -0
- package/types/vendor/use-agentic/index.d.ts +13 -0
- package/types/vendor/use-agentic/surfaceContext.d.ts +5 -0
- package/types/vendor/use-agentic/types.d.ts +133 -0
- package/types/vendor/use-agentic/useAgent.d.ts +4 -0
- package/types/vendor/use-agentic/useAgentGuide.d.ts +6 -0
- package/types/vendor/use-agentic/useAgentResource.d.ts +7 -0
- package/types/vendor/use-agentic/useAgentState.d.ts +13 -0
- package/types/vendor/use-agentic/useAgentTool.d.ts +16 -0
- package/types/vendor/use-agentic.d.ts +1 -0
- package/types/webkit/index.d.ts +2 -0
- package/types/webkit/useScreenScope.d.ts +18 -0
- package/ui/Agent/Approval.tsx +29 -0
- package/ui/Agent/Bubble.tsx +74 -0
- package/ui/Agent/Chat.tsx +225 -0
- package/ui/Agent/Context.tsx +38 -0
- package/ui/Agent/Dock.tsx +15 -4
- package/ui/Agent/Guide.tsx +16 -0
- package/ui/Agent/StateKey.tsx +3 -1
- package/ui/Agent/Zone.tsx +48 -0
- package/ui/Agent/fetchRunner.ts +30 -0
- package/ui/Agent/index.ts +6 -1
- package/ui/Agent/index_.tsx +4 -0
- package/ui/Agent/sessionHistory.ts +33 -0
- package/ui/Load/Units.tsx +11 -2
- package/ui/Load/View.tsx +7 -2
- package/ui/System/ThemeToggle.tsx +55 -33
- package/ui/UiOverride/context.ts +2 -0
- package/vendor/use-agentic/AgentProvider.tsx +39 -0
- package/vendor/use-agentic/AgentScope.tsx +20 -0
- package/vendor/use-agentic/AgentSession.ts +292 -0
- package/vendor/use-agentic/Agentic.tsx +44 -0
- package/vendor/use-agentic/AgenticSurface.ts +294 -0
- package/vendor/use-agentic/WIRE.md +57 -0
- package/vendor/use-agentic/httpRunner.ts +115 -0
- package/vendor/use-agentic/index.ts +14 -0
- package/vendor/use-agentic/surfaceContext.ts +9 -0
- package/vendor/use-agentic/types.ts +144 -0
- package/vendor/use-agentic/useAgent.ts +17 -0
- package/vendor/use-agentic/useAgentGuide.ts +16 -0
- package/vendor/use-agentic/useAgentResource.ts +30 -0
- package/vendor/use-agentic/useAgentState.ts +61 -0
- package/vendor/use-agentic/useAgentTool.ts +55 -0
- package/webkit/index.ts +2 -0
- package/webkit/lazy.tsx +5 -1
- package/webkit/useScreenScope.tsx +49 -0
package/README.ko.md
CHANGED
package/README.md
CHANGED
package/constant/index.ts
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one line a human scans for, derived from what the model already declared: the `text: "title"` search role
|
|
3
|
+
* names exactly that field, so an agent-facing label costs no new declaration. Falls back to the conventional
|
|
4
|
+
* `title`/`name` keys; the id is the caller's floor.
|
|
5
|
+
*/
|
|
6
|
+
export const labelOf = (model: unknown, value: unknown): string | undefined => {
|
|
7
|
+
if (!value || typeof value !== "object") return undefined;
|
|
8
|
+
const source = value as Record<string, unknown>;
|
|
9
|
+
const paths = (model as { text?: { title?: Iterable<string> } } | null)?.text?.title;
|
|
10
|
+
const titlePath = [...(paths ?? [])].find((path) => !path.includes(".") && !path.includes("["));
|
|
11
|
+
for (const key of [titlePath, "title", "name"]) {
|
|
12
|
+
if (!key) continue;
|
|
13
|
+
const candidate = source[key];
|
|
14
|
+
if (typeof candidate === "string" && candidate) return candidate;
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AgentEndpoint } from "akanjs/signal";
|
|
2
|
+
|
|
3
|
+
import { serviceDictionary } from "./dictInfo";
|
|
4
|
+
|
|
5
|
+
export const agentDictionary = serviceDictionary(["en", "ko"])
|
|
6
|
+
.endpoint<AgentEndpoint>((fn) => ({
|
|
7
|
+
runAgentTurn: fn(["Run Agent Turn", "에이전트 턴 실행"])
|
|
8
|
+
.desc([
|
|
9
|
+
"Relays one in-page agent turn to the model and returns its answer; tools execute in the caller's browser",
|
|
10
|
+
"인페이지 에이전트 턴 하나를 모델에 릴레이하고 응답을 돌려준다. 툴 실행은 호출자의 브라우저에서 한다",
|
|
11
|
+
])
|
|
12
|
+
.arg((t) => ({
|
|
13
|
+
messages: t(["Messages", "메시지"]).desc(["The whole transcript in wire shape", "와이어 형태의 전체 대화"]),
|
|
14
|
+
tools: t(["Tools", "툴"]).desc(["The published tool catalogue, schemas only", "게시된 툴 카탈로그 (스키마만)"]),
|
|
15
|
+
context: t(["Context", "컨텍스트"]).desc([
|
|
16
|
+
"Screen context blocks, forwarded as data",
|
|
17
|
+
"화면 컨텍스트 블록 (데이터로 전달)",
|
|
18
|
+
]),
|
|
19
|
+
instructions: t(["Instructions", "지시문"]).desc(["App-level system instructions", "앱 수준 시스템 지시문"]),
|
|
20
|
+
})),
|
|
21
|
+
}))
|
|
22
|
+
.error({
|
|
23
|
+
llmUnavailable: [
|
|
24
|
+
"The agent model is unavailable — no LLM adaptor answered this turn",
|
|
25
|
+
"에이전트 모델을 사용할 수 없습니다. 이번 턴에 응답한 LLM 어댑터가 없습니다",
|
|
26
|
+
],
|
|
27
|
+
deepseekRequestFailed: [
|
|
28
|
+
"DeepSeek request failed with status {status}",
|
|
29
|
+
"DeepSeek 요청이 실패했습니다 (status {status})",
|
|
30
|
+
],
|
|
31
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AgentStop, AgentTurn } from "akanjs/signal";
|
|
2
|
+
|
|
3
|
+
import { scalarDictionary } from "./dictInfo";
|
|
4
|
+
|
|
5
|
+
export const agentTurnDictionary = scalarDictionary(["en", "ko"])
|
|
6
|
+
.of((t) =>
|
|
7
|
+
t(["Agent Turn", "에이전트 턴"]).desc([
|
|
8
|
+
"One assistant answer of an in-page agent turn",
|
|
9
|
+
"인페이지 에이전트 턴의 어시스턴트 응답 하나",
|
|
10
|
+
]),
|
|
11
|
+
)
|
|
12
|
+
.model<AgentTurn>((t) => ({
|
|
13
|
+
text: t(["Text", "텍스트"]).desc([
|
|
14
|
+
"What the assistant said; empty when the turn is only tool calls",
|
|
15
|
+
"어시스턴트가 말한 내용. 툴 호출뿐인 턴에서는 비어 있다",
|
|
16
|
+
]),
|
|
17
|
+
toolCalls: t(["Tool Calls", "툴 호출"]).desc([
|
|
18
|
+
"Tool calls the client should execute, as { id, name, args }",
|
|
19
|
+
"클라이언트가 실행할 툴 호출 목록 ({ id, name, args })",
|
|
20
|
+
]),
|
|
21
|
+
stop: t(["Stop", "종료 사유"]).desc([
|
|
22
|
+
"Why the turn ended — end, or toolUse when tool results are awaited",
|
|
23
|
+
"턴이 끝난 이유 — end 또는 툴 결과를 기다리는 toolUse",
|
|
24
|
+
]),
|
|
25
|
+
}))
|
|
26
|
+
.enum<AgentStop>("agentStop", (t) => ({
|
|
27
|
+
end: t(["End", "종료"]).desc(["The final answer", "최종 응답"]),
|
|
28
|
+
toolUse: t(["Tool Use", "툴 사용"]).desc(["The model awaits tool results", "모델이 툴 결과를 기다린다"]),
|
|
29
|
+
}));
|
|
@@ -48,6 +48,14 @@ export const baseDictionary = serviceDictionary(["en", "ko"])
|
|
|
48
48
|
ok: ["OK", "확인"],
|
|
49
49
|
cancel: ["Cancel", "취소"],
|
|
50
50
|
unauthorized: ["Unauthorized", "권한 없음"],
|
|
51
|
+
agent: ["Agent", "에이전트"],
|
|
52
|
+
agentIntro: ["Ask about this page or tell the agent what to do.", "이 화면에 대해 묻거나 할 일을 지시해 보세요."],
|
|
53
|
+
agentPlaceholder: ["Message the agent...", "에이전트에게 메시지..."],
|
|
54
|
+
agentClear: ["Clear conversation", "대화 비우기"],
|
|
55
|
+
send: ["Send", "보내기"],
|
|
56
|
+
stop: ["Stop", "중지"],
|
|
57
|
+
approve: ["Approve", "승인"],
|
|
58
|
+
decline: ["Decline", "거절"],
|
|
51
59
|
confirmClose: ["Are you sure you want to close the modal?", "모달을 닫으시겠습니까?"],
|
|
52
60
|
textTooShortError: ["More than {minlength} characters needed.", "{minlength}자 이상 필요합니다."],
|
|
53
61
|
textTooLongError: ["Less than {maxlength} characters needed.", "{maxlength}자 이하로 필요합니다."],
|
package/dictionary/dictionary.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import type { BaseEndpoint } from "akanjs/signal";
|
|
1
|
+
import type { AgentEndpoint, AgentTurn, BaseEndpoint } from "akanjs/signal";
|
|
2
2
|
|
|
3
|
+
import { agentDictionary } from "./agent.dictionary";
|
|
4
|
+
import { agentTurnDictionary } from "./agentTurn.dictionary";
|
|
3
5
|
import { baseDictionary } from "./base.dictionary";
|
|
4
|
-
import { registerServiceTrans } from "./locale";
|
|
6
|
+
import { registerScalarTrans, registerServiceTrans } from "./locale";
|
|
5
7
|
import { makeTrans } from "./trans";
|
|
6
8
|
|
|
7
|
-
export const dictionary = {
|
|
9
|
+
export const dictionary = {
|
|
10
|
+
base: registerServiceTrans<"base", BaseEndpoint, typeof baseDictionary>(baseDictionary),
|
|
11
|
+
agentTurn: registerScalarTrans<"agentTurn", AgentTurn, typeof agentTurnDictionary>(agentTurnDictionary),
|
|
12
|
+
agent: registerServiceTrans<"agent", AgentEndpoint, typeof agentDictionary>(agentDictionary),
|
|
13
|
+
};
|
|
8
14
|
|
|
9
15
|
export const { Err, translate, msg, getDictionary, getAllDictionary, __Dict_Key__, __Error_Key__ } =
|
|
10
16
|
makeTrans(dictionary);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Any, enumOf } from "akanjs/base";
|
|
2
|
+
import { ConstantRegistry, via } from "akanjs/constant";
|
|
3
|
+
|
|
4
|
+
export class AgentStop extends enumOf("agentStop", ["end", "toolUse"] as const) {}
|
|
5
|
+
|
|
6
|
+
export class AgentTurn extends via((field) => ({
|
|
7
|
+
text: field(String, { default: "" }), // the assistant's words; empty when the turn is only tool calls
|
|
8
|
+
toolCalls: field([Any]), // { id, name, args } per call — args carries the tool's own schema, not a model's
|
|
9
|
+
stop: field(AgentStop, { default: "end" }),
|
|
10
|
+
})) {
|
|
11
|
+
isToolUse() {
|
|
12
|
+
return this.stop === "toolUse";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const agentTurnConstant = ConstantRegistry.buildScalar("agentTurn" as const, AgentTurn, {
|
|
17
|
+
AgentTurn,
|
|
18
|
+
AgentStop,
|
|
19
|
+
});
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
SerializedSlice,
|
|
11
11
|
ServiceSignal,
|
|
12
12
|
} from "akanjs/signal";
|
|
13
|
+
import { agentTurnConstant } from "../agentTurn";
|
|
13
14
|
import type { ClientSignal, FetchClientType, FetchSignalInput, MergeAllFetchTypes, SliceMeta } from "../fetchType";
|
|
14
15
|
import { memoizeRequestQuery, cookies as requestCookies, headers as requestHeaders } from "../requestStorage";
|
|
15
16
|
import type { GetSliceMetaObjFromDatabaseSignals } from "../types";
|
|
@@ -47,6 +48,19 @@ const globalWithSharedClient = globalThis as typeof globalThis & { [SHARED_CLIEN
|
|
|
47
48
|
const sharedClientState: SharedClientState = globalWithSharedClient[SHARED_CLIENT_KEY] ?? { proxy: null, origin: null };
|
|
48
49
|
globalWithSharedClient[SHARED_CLIENT_KEY] = sharedClientState;
|
|
49
50
|
|
|
51
|
+
interface SharedSignalRegistry {
|
|
52
|
+
signal: { [key: string]: SerializedSignal };
|
|
53
|
+
version: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const SHARED_SIGNAL_KEY = Symbol.for("akanjs.fetch.sharedSignalRegistry");
|
|
57
|
+
const globalWithSharedSignal = globalThis as typeof globalThis & { [SHARED_SIGNAL_KEY]?: SharedSignalRegistry };
|
|
58
|
+
const sharedSignalRegistry: SharedSignalRegistry = globalWithSharedSignal[SHARED_SIGNAL_KEY] ?? {
|
|
59
|
+
signal: {},
|
|
60
|
+
version: 0,
|
|
61
|
+
};
|
|
62
|
+
globalWithSharedSignal[SHARED_SIGNAL_KEY] = sharedSignalRegistry;
|
|
63
|
+
|
|
50
64
|
type ClientSignalMap<SigType extends { fetch: any }> = {
|
|
51
65
|
[K in keyof SigType as SigType[K] extends DatabaseSignal<any, any, any, any>
|
|
52
66
|
? K
|
|
@@ -57,8 +71,9 @@ type ClientSignalMap<SigType extends { fetch: any }> = {
|
|
|
57
71
|
|
|
58
72
|
/** Runtime fetch client that registers serialized Akan signals as HTTP/WebSocket methods. */
|
|
59
73
|
export class FetchClient {
|
|
60
|
-
static
|
|
61
|
-
|
|
74
|
+
static {
|
|
75
|
+
ConstantRegistry.setScalar(agentTurnConstant.refName, agentTurnConstant);
|
|
76
|
+
}
|
|
62
77
|
readonly logger = new Logger("FetchClient");
|
|
63
78
|
readonly origin: string;
|
|
64
79
|
readonly http: HttpClient;
|
|
@@ -93,11 +108,11 @@ export class FetchClient {
|
|
|
93
108
|
* would change what the next client applies. Read by the agent catalogue, which needs the argument schemas.
|
|
94
109
|
*/
|
|
95
110
|
static get sharedSerializedSignal(): { [key: string]: SerializedSignal } {
|
|
96
|
-
return { ...
|
|
111
|
+
return { ...sharedSignalRegistry.signal };
|
|
97
112
|
}
|
|
98
113
|
static resetSharedRegistry() {
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
sharedSignalRegistry.signal = {};
|
|
115
|
+
sharedSignalRegistry.version++;
|
|
101
116
|
}
|
|
102
117
|
static resetSharedClient() {
|
|
103
118
|
sharedClientState.proxy = null;
|
|
@@ -147,9 +162,9 @@ export class FetchClient {
|
|
|
147
162
|
applySignal(serializedSignal: { [key: string]: SerializedSignal }, { share = true }: { share?: boolean } = {}) {
|
|
148
163
|
if (share && Object.keys(serializedSignal).length > 0) {
|
|
149
164
|
for (const [refName, signal] of Object.entries(serializedSignal))
|
|
150
|
-
FetchClient.#mergeSerializedSignalInto(
|
|
151
|
-
|
|
152
|
-
this.#sharedRegistryAppliedVersion =
|
|
165
|
+
FetchClient.#mergeSerializedSignalInto(sharedSignalRegistry.signal, refName, signal);
|
|
166
|
+
sharedSignalRegistry.version++;
|
|
167
|
+
this.#sharedRegistryAppliedVersion = sharedSignalRegistry.version;
|
|
153
168
|
}
|
|
154
169
|
for (const [refName, signal] of Object.entries(serializedSignal))
|
|
155
170
|
FetchClient.#mergeSerializedSignalInto(this.serializedSignal, refName, signal);
|
|
@@ -183,9 +198,9 @@ export class FetchClient {
|
|
|
183
198
|
});
|
|
184
199
|
}
|
|
185
200
|
#syncSharedRegistry() {
|
|
186
|
-
if (this.#sharedRegistryAppliedVersion ===
|
|
187
|
-
this.applySignal(
|
|
188
|
-
this.#sharedRegistryAppliedVersion =
|
|
201
|
+
if (this.#sharedRegistryAppliedVersion === sharedSignalRegistry.version) return;
|
|
202
|
+
this.applySignal(sharedSignalRegistry.signal, { share: false });
|
|
203
|
+
this.#sharedRegistryAppliedVersion = sharedSignalRegistry.version;
|
|
189
204
|
}
|
|
190
205
|
#getOrCreateHandler(key: string): FetchHandler | undefined {
|
|
191
206
|
const current = this.#handlerStore[key];
|
|
@@ -719,9 +734,16 @@ export class FetchClient {
|
|
|
719
734
|
});
|
|
720
735
|
}
|
|
721
736
|
});
|
|
722
|
-
const instance = new FetchClient(
|
|
737
|
+
const instance = new FetchClient(FetchClient.#originFromEnv(), handler, serializedSignal);
|
|
723
738
|
return FetchClient.#makeProxy<MergeAllFetchTypes<Signals>, GetSliceMetaObjFromDatabaseSignals<Signals>>(instance);
|
|
724
739
|
}
|
|
740
|
+
static #originFromEnv() {
|
|
741
|
+
try {
|
|
742
|
+
return getEnv().serverHttpUri;
|
|
743
|
+
} catch {
|
|
744
|
+
return "";
|
|
745
|
+
}
|
|
746
|
+
}
|
|
725
747
|
static build<SigType extends { fetch: any }>(
|
|
726
748
|
constant: object,
|
|
727
749
|
serializedSignal: { [key: string]: SerializedSignal },
|
package/fetch/index.ts
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akanjs",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.18",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -180,7 +180,6 @@
|
|
|
180
180
|
"dependencies": {
|
|
181
181
|
"dayjs": "^1.11.20",
|
|
182
182
|
"immer": "^11.1.8",
|
|
183
|
-
"sharp": "^0.34.5",
|
|
184
183
|
"tailwind-merge": "^3.3.1",
|
|
185
184
|
"tailwind-variants": "^3.3.0"
|
|
186
185
|
},
|
|
@@ -202,6 +201,7 @@
|
|
|
202
201
|
"@capacitor/preferences": "^8.0.1",
|
|
203
202
|
"@capacitor/push-notifications": "^8.1.1",
|
|
204
203
|
"@capgo/capacitor-updater": "^8.46.1",
|
|
204
|
+
"@happy-dom/global-registrator": "^20.11.2",
|
|
205
205
|
"@libsql/client": "^0.17.3",
|
|
206
206
|
"@playwright/test": "^1.60.0",
|
|
207
207
|
"@react-spring/web": "^10.1.0",
|
|
@@ -210,8 +210,8 @@
|
|
|
210
210
|
"capacitor-plugin-safe-area": "^5.0.0",
|
|
211
211
|
"chance": "^1.1.13",
|
|
212
212
|
"cordova-plugin-purchase": "^13.16.0",
|
|
213
|
-
"croner": "^10.0.1",
|
|
214
213
|
"firebase": "^12.13.0",
|
|
214
|
+
"happy-dom": "^20.11.2",
|
|
215
215
|
"ioredis": "^5.10.1",
|
|
216
216
|
"mermaid": "^11.15.0",
|
|
217
217
|
"postgres": "^3.4.9",
|
|
@@ -225,7 +225,6 @@
|
|
|
225
225
|
"react-simple-pull-to-refresh": "^1.3.4",
|
|
226
226
|
"react-spring": "^10.0.4",
|
|
227
227
|
"scheduler": "^0.27.0",
|
|
228
|
-
"sharp": "^0.34.5",
|
|
229
228
|
"tailwind-scrollbar": "^4.0.2",
|
|
230
229
|
"typescript": "^6.0.3"
|
|
231
230
|
},
|
|
@@ -281,6 +280,9 @@
|
|
|
281
280
|
"@capgo/capacitor-updater": {
|
|
282
281
|
"optional": true
|
|
283
282
|
},
|
|
283
|
+
"@happy-dom/global-registrator": {
|
|
284
|
+
"optional": true
|
|
285
|
+
},
|
|
284
286
|
"@libsql/client": {
|
|
285
287
|
"optional": true
|
|
286
288
|
},
|
|
@@ -305,10 +307,10 @@
|
|
|
305
307
|
"cordova-plugin-purchase": {
|
|
306
308
|
"optional": true
|
|
307
309
|
},
|
|
308
|
-
"
|
|
310
|
+
"firebase": {
|
|
309
311
|
"optional": true
|
|
310
312
|
},
|
|
311
|
-
"
|
|
313
|
+
"happy-dom": {
|
|
312
314
|
"optional": true
|
|
313
315
|
},
|
|
314
316
|
"ioredis": {
|
|
@@ -359,13 +361,13 @@
|
|
|
359
361
|
},
|
|
360
362
|
"devDependencies": {
|
|
361
363
|
"@biomejs/biome": "2.5.8",
|
|
362
|
-
"@types/bun": "^1.
|
|
364
|
+
"@types/bun": "^1.4.0"
|
|
363
365
|
},
|
|
364
366
|
"bun": {
|
|
365
367
|
"platform": "bun"
|
|
366
368
|
},
|
|
367
369
|
"engines": {
|
|
368
|
-
"bun": ">=1.
|
|
370
|
+
"bun": ">=1.4.0"
|
|
369
371
|
},
|
|
370
372
|
"main": "./index.ts"
|
|
371
373
|
}
|
package/server/akanApp.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { Logger } from "akanjs/common";
|
|
|
6
6
|
import type { AkanChildRole, AkanChildStatus, AkanIpcMessage, AkanMetricsReport, AkanUpstream } from "akanjs/service";
|
|
7
7
|
import { isTraceEnabled } from "akanjs/signal";
|
|
8
8
|
import { makeAkanChildProxyHeaders } from "./akanAppHeaders";
|
|
9
|
-
import type { McpServerOption } from "./akanServer";
|
|
10
9
|
import type { BuilderCsrReq, BuilderCsrRes, BuilderMessage, BuilderReq, BuilderRes } from "./artifact";
|
|
11
10
|
import { resolveEncodedSidecar } from "./assetEncoding";
|
|
12
11
|
import { isPortInUseError } from "./lifecycle/portInUse";
|
|
@@ -55,11 +54,6 @@ export interface AkanAppOptions {
|
|
|
55
54
|
port?: number;
|
|
56
55
|
wsBasePort?: number;
|
|
57
56
|
openapi?: boolean;
|
|
58
|
-
/**
|
|
59
|
-
* MCP server config for the children, which are the processes that actually mount `/mcp`. Declared here
|
|
60
|
-
* because `server.ts` is generated and takes no options, so `main.ts` is the only app-authored place left.
|
|
61
|
-
*/
|
|
62
|
-
mcp?: boolean | McpServerOption;
|
|
63
57
|
}
|
|
64
58
|
|
|
65
59
|
interface AkanReplicaConfig {
|
|
@@ -92,7 +86,6 @@ export class AkanApp {
|
|
|
92
86
|
readonly #port: number;
|
|
93
87
|
readonly #wsBasePort: number;
|
|
94
88
|
readonly #openapi?: boolean;
|
|
95
|
-
readonly #mcpEnv: Record<string, string>;
|
|
96
89
|
readonly #children = new Map<number, ChildState>();
|
|
97
90
|
readonly #roomChildren = new Map<string, Set<number>>();
|
|
98
91
|
readonly #childRooms = new Map<number, Set<string>>();
|
|
@@ -134,30 +127,6 @@ export class AkanApp {
|
|
|
134
127
|
this.#port = Number(resolvedOptions.port ?? process.env.PORT ?? 8282);
|
|
135
128
|
this.#wsBasePort = Number(resolvedOptions.wsBasePort ?? process.env.AKAN_WS_BASE_PORT ?? this.#port + 10_000);
|
|
136
129
|
this.#openapi = resolvedOptions.openapi;
|
|
137
|
-
this.#mcpEnv = AkanApp.#toMcpEnv(resolvedOptions.mcp);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* `McpServerOption`, spelled as the environment a child is spawned with — the only channel the gateway has to
|
|
142
|
-
* a process it starts, and the same one a deployment configures MCP through when it sets nothing in code.
|
|
143
|
-
*/
|
|
144
|
-
static #toMcpEnv(mcp: boolean | McpServerOption | undefined): Record<string, string> {
|
|
145
|
-
if (mcp === undefined) return {};
|
|
146
|
-
if (typeof mcp === "boolean") return { AKAN_MCP: String(mcp) };
|
|
147
|
-
const { enabled, readOnly, path: mcpPath, version, instructions, allowedOrigins, pageSize, language, auth } = mcp;
|
|
148
|
-
return {
|
|
149
|
-
AKAN_MCP: String(enabled ?? true),
|
|
150
|
-
...(readOnly === undefined ? {} : { AKAN_MCP_READONLY: String(readOnly) }),
|
|
151
|
-
...(mcpPath ? { AKAN_MCP_PATH: mcpPath } : {}),
|
|
152
|
-
...(version ? { AKAN_MCP_VERSION: version } : {}),
|
|
153
|
-
...(instructions ? { AKAN_MCP_INSTRUCTIONS: instructions } : {}),
|
|
154
|
-
...(allowedOrigins?.length ? { AKAN_MCP_ALLOWED_ORIGINS: allowedOrigins.join(",") } : {}),
|
|
155
|
-
...(pageSize === undefined ? {} : { AKAN_MCP_PAGE_SIZE: String(pageSize) }),
|
|
156
|
-
...(language ? { AKAN_MCP_LANGUAGE: language } : {}),
|
|
157
|
-
...(auth?.authorizationServers?.length ? { AKAN_MCP_AUTH_SERVERS: auth.authorizationServers.join(",") } : {}),
|
|
158
|
-
...(auth?.scopes?.length ? { AKAN_MCP_SCOPES: auth.scopes.join(",") } : {}),
|
|
159
|
-
...(auth?.resource ? { AKAN_MCP_RESOURCE: auth.resource } : {}),
|
|
160
|
-
};
|
|
161
130
|
}
|
|
162
131
|
|
|
163
132
|
static #resolveServerPath(serverPath: string) {
|
|
@@ -328,7 +297,6 @@ export class AkanApp {
|
|
|
328
297
|
AKAN_CHILD_SOCKET: upstream.http.socketPath,
|
|
329
298
|
AKAN_CHILD_WS_PORT: upstream.ws ? String(upstream.ws.port) : "",
|
|
330
299
|
...(this.#openapi === undefined ? {} : { AKAN_OPENAPI: this.#openapi ? "true" : "false" }),
|
|
331
|
-
...this.#mcpEnv,
|
|
332
300
|
},
|
|
333
301
|
ipc: (message) => this.#handleMessage(idx, message as AkanIpcMessage, proc),
|
|
334
302
|
stdout: "pipe",
|
|
@@ -626,7 +594,11 @@ export class AkanApp {
|
|
|
626
594
|
const result = ws.send(event.data as string | ArrayBuffer);
|
|
627
595
|
if (result === 0) upstream.close();
|
|
628
596
|
});
|
|
629
|
-
upstream.addEventListener("close", (event) =>
|
|
597
|
+
upstream.addEventListener("close", (event) => {
|
|
598
|
+
const code = AkanApp.#sendableCloseCode(event.code);
|
|
599
|
+
if (code === undefined) ws.close();
|
|
600
|
+
else ws.close(code, event.reason);
|
|
601
|
+
});
|
|
630
602
|
upstream.addEventListener("error", () => ws.close(1011, "upstream websocket error"));
|
|
631
603
|
Object.assign(ws.data, { pending });
|
|
632
604
|
}
|
|
@@ -646,8 +618,15 @@ export class AkanApp {
|
|
|
646
618
|
else ws.data.pending?.push(payload as string | ArrayBuffer);
|
|
647
619
|
}
|
|
648
620
|
|
|
621
|
+
static #sendableCloseCode(code: number): number | undefined {
|
|
622
|
+
const sendable = (code >= 1000 && code <= 1014 && (code < 1004 || code > 1006)) || (code >= 3000 && code <= 4999);
|
|
623
|
+
return sendable ? code : undefined;
|
|
624
|
+
}
|
|
625
|
+
|
|
649
626
|
#handleWsClose(ws: Bun.ServerWebSocket<GatewayWsData>, code: number, reason: string) {
|
|
650
|
-
|
|
627
|
+
const upstreamCode = AkanApp.#sendableCloseCode(code);
|
|
628
|
+
if (upstreamCode === undefined) ws.data.upstream.close();
|
|
629
|
+
else ws.data.upstream.close(upstreamCode, reason);
|
|
651
630
|
const child = this.#children.get(ws.data.childIdx);
|
|
652
631
|
if (child) child.metrics.activeWebSockets = Math.max(0, (child.metrics.activeWebSockets ?? 1) - 1);
|
|
653
632
|
}
|
package/server/akanOption.ts
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
import type { BaseEnv, PromiseOrObject } from "akanjs/base";
|
|
2
|
-
import type {
|
|
2
|
+
import type { Adaptor, AdaptorCls, LlmOption } from "akanjs/service";
|
|
3
|
+
import type { AgentRelayPolicy, MiddlewareCls } from "akanjs/signal";
|
|
4
|
+
import type { McpServerOption } from "./akanServer";
|
|
3
5
|
import type { WebProxyRegistration } from "./proxy";
|
|
4
6
|
import { HostBasePathWebProxy, LocaleWebProxy } from "./proxy";
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
export interface AdaptorOverride {
|
|
9
|
+
role: AdaptorCls;
|
|
10
|
+
adaptor: AdaptorCls;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
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.
|
|
16
|
+
*/
|
|
7
17
|
export class AkanOption<Env extends BaseEnv = BaseEnv> {
|
|
8
18
|
readonly #getUses: ((env: Env) => Record<string, PromiseOrObject<unknown>>)[];
|
|
9
19
|
readonly #middlewares: MiddlewareCls[] = [];
|
|
20
|
+
readonly #adaptorOverrides: AdaptorOverride[] = [];
|
|
10
21
|
readonly #webProxies: WebProxyRegistration[] = [];
|
|
22
|
+
readonly #getLlms: ((env: Env) => LlmOption)[] = [];
|
|
23
|
+
#mcp: boolean | McpServerOption | undefined;
|
|
24
|
+
#agentAccess: AgentRelayPolicy | null | undefined;
|
|
11
25
|
constructor() {
|
|
12
26
|
this.#getUses = [];
|
|
13
27
|
}
|
|
@@ -21,10 +35,38 @@ export class AkanOption<Env extends BaseEnv = BaseEnv> {
|
|
|
21
35
|
this.#middlewares.push(...middlewares);
|
|
22
36
|
return this;
|
|
23
37
|
}
|
|
38
|
+
/** Rebinds a predefined adaptor role (e.g. `LlmAdaptorRole`) to the app's own implementation. Last writer wins. */
|
|
39
|
+
applyAdaptor<T extends Adaptor>(role: AdaptorCls<T>, adaptor: AdaptorCls<T>) {
|
|
40
|
+
this.#adaptorOverrides.push({ role: role as AdaptorCls, adaptor: adaptor as AdaptorCls });
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
24
43
|
applyWebProxy(...proxies: WebProxyRegistration[]) {
|
|
25
44
|
this.#webProxies.push(...proxies);
|
|
26
45
|
return this;
|
|
27
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* MCP server settings for the app mounting this option, merged over the `AKAN_MCP_*` environment and under an
|
|
49
|
+
* option the server is constructed with. The app's own `option.ts` is the last lib the server reads, so it wins
|
|
50
|
+
* over every library it depends on.
|
|
51
|
+
*/
|
|
52
|
+
setMcp(mcp: boolean | McpServerOption = true) {
|
|
53
|
+
this.#mcp = mcp;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Who may spend the LLM key through the `runAgentTurn` relay — the `AgentRelayAccess` policy, which defaults to
|
|
58
|
+
* allowing everyone because the framework has no account model to gate on. `null` reopens it.
|
|
59
|
+
*/
|
|
60
|
+
setAgentAccess(policy: AgentRelayPolicy | null) {
|
|
61
|
+
this.#agentAccess = policy;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
/** Settings for whichever adaptor fills `LlmAdaptorRole`, injected into it as the `llmOption` use. */
|
|
65
|
+
setLlm(llmOrFn: LlmOption | ((env: Env) => LlmOption)) {
|
|
66
|
+
if (typeof llmOrFn === "function") this.#getLlms.push(llmOrFn);
|
|
67
|
+
else this.#getLlms.push(() => llmOrFn);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
28
70
|
getUses(env: Env): Record<string, PromiseOrObject<unknown>> {
|
|
29
71
|
const uses = this.#getUses.map((fn) => fn(env));
|
|
30
72
|
return Object.assign({}, ...uses);
|
|
@@ -32,9 +74,21 @@ export class AkanOption<Env extends BaseEnv = BaseEnv> {
|
|
|
32
74
|
getMiddlewares(): MiddlewareCls[] {
|
|
33
75
|
return this.#middlewares;
|
|
34
76
|
}
|
|
77
|
+
getAdaptorOverrides(): AdaptorOverride[] {
|
|
78
|
+
return this.#adaptorOverrides;
|
|
79
|
+
}
|
|
35
80
|
getWebProxies(): WebProxyRegistration[] {
|
|
36
81
|
return this.#webProxies;
|
|
37
82
|
}
|
|
83
|
+
getMcp(): boolean | McpServerOption | undefined {
|
|
84
|
+
return this.#mcp;
|
|
85
|
+
}
|
|
86
|
+
getAgentAccess(): AgentRelayPolicy | null | undefined {
|
|
87
|
+
return this.#agentAccess;
|
|
88
|
+
}
|
|
89
|
+
getLlm(env: Env): LlmOption {
|
|
90
|
+
return Object.assign({}, ...this.#getLlms.map((fn) => fn(env)));
|
|
91
|
+
}
|
|
38
92
|
}
|
|
39
93
|
|
|
40
94
|
export function createDefaultAkanOption() {
|
package/server/akanServer.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
SolidConfig,
|
|
12
12
|
} from "akanjs/service";
|
|
13
13
|
import type { ServerSignal, ServerSignalCls, WebsocketPublishData } from "akanjs/signal";
|
|
14
|
+
import { AgentRelayAccess } from "../signal/guards";
|
|
14
15
|
import { createOpenApiDocument } from "../signal/openapi";
|
|
15
16
|
import { FetchSerializer } from "../signal/serializer";
|
|
16
17
|
import type { AkanLib, AkanLibProps } from "./akanLib";
|
|
@@ -142,6 +143,13 @@ export class AkanServer {
|
|
|
142
143
|
this.libs = libs;
|
|
143
144
|
this.env = { ...env };
|
|
144
145
|
this.openapi = options?.openapi ?? this.openapi;
|
|
146
|
+
|
|
147
|
+
libs.forEach((lib) => {
|
|
148
|
+
const mcp = lib.option.getMcp();
|
|
149
|
+
if (mcp !== undefined) this.setMcp(mcp);
|
|
150
|
+
const agentAccess = lib.option.getAgentAccess();
|
|
151
|
+
if (agentAccess !== undefined) AgentRelayAccess.use(agentAccess);
|
|
152
|
+
});
|
|
145
153
|
this.setMcp(options?.mcp ?? this.mcp);
|
|
146
154
|
this.serverMode = serverMode;
|
|
147
155
|
this.#di = new DiLifecycle(this.env, serverMode, ...libs);
|
|
@@ -616,12 +624,9 @@ export class AkanServer {
|
|
|
616
624
|
}
|
|
617
625
|
|
|
618
626
|
/**
|
|
619
|
-
* The rest of `McpServerOption`,
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
* nothing but its environment — so without an env spelling per field, the only settable parts were the two
|
|
623
|
-
* booleans and `instructions` / `allowedOrigins` / `path` / `version` / `pageSize` / `language` had no path
|
|
624
|
-
* from an app at all. An option passed in code still wins: `setMcp` merges over this.
|
|
627
|
+
* The rest of `McpServerOption`, spelled as environment — the channel a deployment configures what the source
|
|
628
|
+
* does not through, and the only one a child of the gateway is handed. An app writes these in its `option.ts`,
|
|
629
|
+
* which merges over this: a value written in code wins over the env of the same name.
|
|
625
630
|
*/
|
|
626
631
|
/**
|
|
627
632
|
* Keys whose value is `undefined` are dropped before the merge. "An option written in code wins over the env of
|