akanjs 3.0.0-alpha.54 → 3.0.0-alpha.56
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/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/server/rscWorker.tsx +5 -13
- package/server/webRouter.ts +3 -5
- package/service/agent.service.ts +5 -2
- package/service/toolNames.ts +119 -0
- package/store/agent/StoreSurfaceSource.ts +9 -1
- package/types/service/toolNames.d.ts +35 -0
- package/types/store/agent/StoreSurfaceSource.d.ts +9 -1
- package/types/ui/Agent/Chat.d.ts +25 -4
- package/types/ui/Agent/Context.d.ts +6 -2
- package/types/ui/Agent/Zone.d.ts +27 -4
- package/types/ui/Agent/agentSessionOf.d.ts +12 -3
- package/types/ui/Agent/index.d.ts +1 -1
- package/types/ui/Agent/sessionHistory.d.ts +1 -1
- package/types/ui/Agent/sessionView.d.ts +15 -0
- package/types/ui/Field.d.ts +12 -12
- package/types/ui/RecentTime.d.ts +18 -1
- package/types/ui/ToggleSelect.d.ts +1 -0
- package/types/ui/index.d.ts +5 -3
- package/types/vendor/use-agentic/AgentProvider.d.ts +1 -2
- package/types/vendor/use-agentic/AgentSession.d.ts +33 -6
- package/types/vendor/use-agentic/AgenticSurface.d.ts +5 -0
- package/types/vendor/use-agentic/index.d.ts +1 -0
- package/types/vendor/use-agentic/sharedContext.d.ts +15 -0
- package/ui/Agent/Chat.tsx +76 -36
- package/ui/Agent/Context.tsx +13 -4
- package/ui/Agent/Zone.tsx +48 -12
- package/ui/Agent/agentSessionOf.ts +23 -4
- package/ui/Agent/sessionHistory.ts +10 -3
- package/ui/Agent/sessionView.ts +38 -0
- package/ui/Field.tsx +41 -34
- package/ui/RecentTime.tsx +83 -35
- package/ui/System/Client.tsx +19 -7
- package/ui/System/ThemeToggle.tsx +14 -5
- package/ui/ToggleSelect.tsx +4 -1
- package/ui/index.ts +29 -3
- package/vendor/use-agentic/AgentProvider.tsx +1 -1
- package/vendor/use-agentic/AgentSession.ts +84 -19
- package/vendor/use-agentic/AgenticSurface.ts +9 -0
- package/vendor/use-agentic/index.ts +1 -0
- package/vendor/use-agentic/sharedContext.ts +25 -0
- package/vendor/use-agentic/surfaceContext.ts +4 -3
- package/vendor/use-agentic/useAgent.ts +3 -2
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/server/rscWorker.tsx
CHANGED
|
@@ -547,7 +547,6 @@ export class RscRenderer {
|
|
|
547
547
|
});
|
|
548
548
|
return;
|
|
549
549
|
}
|
|
550
|
-
const theme = untrackedCookies().get("theme")?.value;
|
|
551
550
|
let element: ReactNode;
|
|
552
551
|
let effectivePatchDecision = safePatchDecision;
|
|
553
552
|
if (match && safePatchDecision.status === "patch" && safePatchDecision.patch) {
|
|
@@ -563,9 +562,9 @@ export class RscRenderer {
|
|
|
563
562
|
reason: "suffix-compose-fallback",
|
|
564
563
|
commonPrefixLength: safePatchDecision.commonPrefixLength,
|
|
565
564
|
};
|
|
566
|
-
element = await this.#renderMatched(urlObj, match,
|
|
565
|
+
element = await this.#renderMatched(urlObj, match, searchParams);
|
|
567
566
|
} else element = suffixElement;
|
|
568
|
-
} else if (match) element = await this.#renderMatched(urlObj, match,
|
|
567
|
+
} else if (match) element = await this.#renderMatched(urlObj, match, searchParams);
|
|
569
568
|
else element = await this.#renderNotFound(urlObj);
|
|
570
569
|
const traceCacheKey =
|
|
571
570
|
effectivePatchDecision.status === "patch" ? (patchCacheEntry?.key ?? cacheEntry?.key) : cacheEntry?.key;
|
|
@@ -1236,12 +1235,8 @@ export class RscRenderer {
|
|
|
1236
1235
|
const routeHeadSnapshot = this.#createRouteHeadSnapshot(url, routeHead, {
|
|
1237
1236
|
hasExplicitLanguageAlternates: routeHead.hasExplicitLanguageAlternates,
|
|
1238
1237
|
});
|
|
1239
|
-
const theme = untrackedCookies().get("theme")?.value;
|
|
1240
1238
|
return (
|
|
1241
|
-
<html
|
|
1242
|
-
lang={params.lang ?? RscRenderer.#getLocale(pathname, this.#i18n)}
|
|
1243
|
-
{...(theme ? { "data-theme": theme } : { suppressHydrationWarning: true })}
|
|
1244
|
-
>
|
|
1239
|
+
<html lang={params.lang ?? RscRenderer.#getLocale(pathname, this.#i18n)} suppressHydrationWarning>
|
|
1245
1240
|
<head key="head">
|
|
1246
1241
|
<meta key="charset" charSet="utf-8" />
|
|
1247
1242
|
<meta key="viewport" name="viewport" content="width=device-width, initial-scale=1" />
|
|
@@ -1263,7 +1258,6 @@ export class RscRenderer {
|
|
|
1263
1258
|
async #renderMatched(
|
|
1264
1259
|
url: URL,
|
|
1265
1260
|
match: { pathRoute: PathRoute; params: Record<string, string> },
|
|
1266
|
-
theme?: string,
|
|
1267
1261
|
searchParams = RouteTreeBuilder.parseSearchParams(url.search),
|
|
1268
1262
|
): Promise<ReactNode> {
|
|
1269
1263
|
this.#logger.verbose(
|
|
@@ -1289,11 +1283,9 @@ export class RscRenderer {
|
|
|
1289
1283
|
searchParams,
|
|
1290
1284
|
navKey: url.pathname + url.search,
|
|
1291
1285
|
});
|
|
1286
|
+
|
|
1292
1287
|
return (
|
|
1293
|
-
<html
|
|
1294
|
-
lang={match.params.lang ?? this.#i18n.defaultLocale}
|
|
1295
|
-
{...(theme ? { "data-theme": theme } : { suppressHydrationWarning: true })}
|
|
1296
|
-
>
|
|
1288
|
+
<html lang={match.params.lang ?? this.#i18n.defaultLocale} suppressHydrationWarning>
|
|
1297
1289
|
<head key="head">
|
|
1298
1290
|
<meta key="charset" charSet="utf-8" />
|
|
1299
1291
|
<meta key="viewport" name="viewport" content="width=device-width, initial-scale=1" />
|
package/server/webRouter.ts
CHANGED
|
@@ -610,7 +610,8 @@ export class WebRouter {
|
|
|
610
610
|
if (rscResult.type === "redirect")
|
|
611
611
|
return Response.redirect(new URL(rscResult.location, url.origin), rscResult.status);
|
|
612
612
|
if (rscResult.type === "not-found") return this.#renderSystemNotFoundFallbackResponse(req, url);
|
|
613
|
-
|
|
613
|
+
|
|
614
|
+
const cookieTheme = WebRouter.#cookieValue(req, "theme");
|
|
614
615
|
const hostRequestStore = createRequestStore(req);
|
|
615
616
|
const extraBootstrapInline = [
|
|
616
617
|
rscResult.trace?.routeState
|
|
@@ -628,7 +629,7 @@ export class WebRouter {
|
|
|
628
629
|
bootstrapModules: [this.#artifact.rscClientUrl],
|
|
629
630
|
extraBootstrapInline: extraBootstrapInline || undefined,
|
|
630
631
|
importmap: this.#artifact.vendorMap,
|
|
631
|
-
theme:
|
|
632
|
+
theme: cookieTheme ?? rscResult.theme ?? "system",
|
|
632
633
|
lateControl: rscResult.lateControl,
|
|
633
634
|
waitForAllReady: rscResult.trace?.ssrBlocking ?? false,
|
|
634
635
|
onCancel: (reason: unknown) => {
|
|
@@ -797,9 +798,6 @@ export class WebRouter {
|
|
|
797
798
|
}
|
|
798
799
|
}
|
|
799
800
|
|
|
800
|
-
static #hasCookie(req: Request, name: string): boolean {
|
|
801
|
-
return parseCookieHeader(req.headers.get("cookie") ?? "").has(name);
|
|
802
|
-
}
|
|
803
801
|
#getHtmlCacheEntry(req: Request, url: URL): { entry: RouteCacheEntry | null; reason?: string } {
|
|
804
802
|
const decision = resolvePublicRouteCacheEntryDecision({
|
|
805
803
|
request: req,
|
package/service/agent.service.ts
CHANGED
|
@@ -7,15 +7,18 @@ import type {
|
|
|
7
7
|
} from "./predefinedAdaptor/llm.adaptor";
|
|
8
8
|
import { LlmAdaptorRole } from "./predefinedAdaptor/role.adaptor";
|
|
9
9
|
import { serve } from "./serve";
|
|
10
|
+
import { ToolNames } from "./toolNames";
|
|
10
11
|
|
|
11
12
|
export class AgentService extends serve("agent" as const, ({ plug }) => ({
|
|
12
13
|
llm: plug(LlmAdaptorRole),
|
|
13
14
|
})) {
|
|
14
15
|
async runTurn(request: LlmTurnRequest, onDelta?: (delta: string) => void) {
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
const names = ToolNames.of(request);
|
|
18
|
+
const prepared = names.encode(AgentService.readable(AgentService.explained(request), this.llm.accepts));
|
|
16
19
|
const answer = await this.llm.chat(prepared, onDelta);
|
|
17
20
|
if (!answer) throw new Err("agent.error.llmUnavailable");
|
|
18
|
-
return { text: answer.text ?? "", toolCalls: answer.toolCalls ?? [], stop: answer.stop };
|
|
21
|
+
return { text: answer.text ?? "", toolCalls: names.decode(answer.toolCalls ?? []), stop: answer.stop };
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/**
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { AgentWireMessage, AgentWireToolCall, LlmTurnRequest } from "./predefinedAdaptor/llm.adaptor";
|
|
2
|
+
|
|
3
|
+
const wireSafe = /^[A-Za-z0-9_-]+$/;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Renames a turn's tools onto what a provider's function-calling wire accepts, and reads the answer back.
|
|
7
|
+
*
|
|
8
|
+
* A zone publishes its tools scope-prefixed — `videoProjectDraft.createVideoProject` — which is legal for MCP,
|
|
9
|
+
* where `.` is an allowed character and the scope join. Every OpenAI-compatible and Anthropic function schema is
|
|
10
|
+
* narrower: `[A-Za-z0-9_-]`, at most 64 characters. A provider that validates answers 400; DeepSeek does not, and
|
|
11
|
+
* what happened instead was worse to debug — the model normalized the illegal name itself, called the bare
|
|
12
|
+
* `createVideoProject`, and the browser answered `Unknown tool`, spending a turn on a tool that was published all
|
|
13
|
+
* along.
|
|
14
|
+
*
|
|
15
|
+
* Renamed here rather than in each adaptor for the reason `AgentService.explained` gives: every adaptor would
|
|
16
|
+
* otherwise have to remember, and forgetting is silent. A name the wire already accepts is left alone, so the
|
|
17
|
+
* root agent's request is byte-for-byte what it was.
|
|
18
|
+
*/
|
|
19
|
+
export class ToolNames {
|
|
20
|
+
/** Both dialects reject a longer name, and neither says so in terms of the tool you wrote. */
|
|
21
|
+
static readonly limit = 64;
|
|
22
|
+
|
|
23
|
+
readonly #toWire = new Map<string, string>();
|
|
24
|
+
readonly #toSurface = new Map<string, string>();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Every name the request carries, not just the published ones: the transcript holds calls to tools that have
|
|
28
|
+
* since left the screen, and one of those reaching the wire unrenamed is the same failure a turn later.
|
|
29
|
+
*/
|
|
30
|
+
static of(request: LlmTurnRequest): ToolNames {
|
|
31
|
+
return new ToolNames([
|
|
32
|
+
...request.tools.map((tool) => tool.name),
|
|
33
|
+
...request.messages.flatMap((message) => ToolNames.#namesIn(message)),
|
|
34
|
+
]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
constructor(names: Iterable<string>) {
|
|
38
|
+
const all = [...new Set(names)];
|
|
39
|
+
|
|
40
|
+
const taken = new Set(all.filter((name) => ToolNames.#fits(name)));
|
|
41
|
+
|
|
42
|
+
for (const name of all.filter((candidate) => !ToolNames.#fits(candidate)).sort((a, b) => (a < b ? -1 : 1))) {
|
|
43
|
+
const wire = ToolNames.#unique(ToolNames.#fold(name), taken);
|
|
44
|
+
taken.add(wire);
|
|
45
|
+
this.#toWire.set(name, wire);
|
|
46
|
+
this.#toSurface.set(wire, name);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get renamed() {
|
|
51
|
+
return this.#toWire.size > 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
wire(name: string) {
|
|
55
|
+
return this.#toWire.get(name) ?? name;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Unknown stays as it came. A model that invented a name is answered by the surface's own `Unknown tool`, which
|
|
60
|
+
* lands in the transcript as a tool result it can correct from — guessing which tool it meant would run one.
|
|
61
|
+
*/
|
|
62
|
+
surface(name: string) {
|
|
63
|
+
return this.#toSurface.get(name) ?? name;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
encode(request: LlmTurnRequest): LlmTurnRequest {
|
|
67
|
+
if (!this.renamed) return request;
|
|
68
|
+
return {
|
|
69
|
+
...request,
|
|
70
|
+
tools: request.tools.map((tool) => ({ ...tool, name: this.wire(tool.name) })),
|
|
71
|
+
messages: request.messages.map((message) => this.#encoded(message)),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
decode(calls: AgentWireToolCall[]): AgentWireToolCall[] {
|
|
76
|
+
if (!this.renamed) return calls;
|
|
77
|
+
return calls.map((call) => ({ ...call, name: this.surface(call.name) }));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#encoded(message: AgentWireMessage): AgentWireMessage {
|
|
81
|
+
if (!message.toolCalls?.length && !message.toolResults?.length) return message;
|
|
82
|
+
return {
|
|
83
|
+
...message,
|
|
84
|
+
...(message.toolCalls?.length
|
|
85
|
+
? { toolCalls: message.toolCalls.map((call) => ({ ...call, name: this.wire(call.name) })) }
|
|
86
|
+
: {}),
|
|
87
|
+
...(message.toolResults?.length
|
|
88
|
+
? { toolResults: message.toolResults.map((result) => ({ ...result, name: this.wire(result.name) })) }
|
|
89
|
+
: {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static #namesIn(message: AgentWireMessage): string[] {
|
|
94
|
+
return [
|
|
95
|
+
...(message.toolCalls ?? []).map((call) => call.name),
|
|
96
|
+
...(message.toolResults ?? []).map((result) => result.name),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static #fits(name: string) {
|
|
101
|
+
return name.length <= ToolNames.limit && wireSafe.test(name);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** `.` is the one character the surface itself adds, so it folds to the `__` every MCP client already reads. */
|
|
105
|
+
static #fold(name: string) {
|
|
106
|
+
const folded = name.replaceAll(".", "__").replace(/[^A-Za-z0-9_-]/g, "-");
|
|
107
|
+
|
|
108
|
+
return folded.length <= ToolNames.limit ? folded : folded.slice(folded.length - ToolNames.limit);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
static #unique(candidate: string, taken: Set<string>) {
|
|
112
|
+
if (!taken.has(candidate)) return candidate;
|
|
113
|
+
for (let idx = 2; ; idx += 1) {
|
|
114
|
+
const suffix = `_${idx}`;
|
|
115
|
+
const next = `${candidate.slice(0, ToolNames.limit - suffix.length)}${suffix}`;
|
|
116
|
+
if (!taken.has(next)) return next;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -13,13 +13,21 @@ import { ScreenTarget } from "./ScreenTarget";
|
|
|
13
13
|
*
|
|
14
14
|
* Per zone view: a zone's `readState` reaches the keys its own subtree subscribes, and its `readScreen` and
|
|
15
15
|
* `highlight` reach into its own `data-agent-zone` container rather than the whole document. A page can shadow any
|
|
16
|
-
* of them by registering a hook tool of the same name — hook entries win over a source's.
|
|
16
|
+
* of them by registering a hook tool of the same name — hook entries win over a source's. **That is a root-scope
|
|
17
|
+
* move only**: inside a zone the hook entry is registered under its scope-prefixed name, which can never collide
|
|
18
|
+
* with the bare name a source publishes, so a zone drops a built-in with the session's `builtins` option instead.
|
|
17
19
|
*/
|
|
18
20
|
export class StoreSurfaceSource implements SurfaceSource {
|
|
19
21
|
/** Defined in `akanjs/ui/styles.css`, so the flash follows the app's own theme tokens. */
|
|
20
22
|
static readonly highlightClass = "akan-agent-highlight";
|
|
21
23
|
/** Mirrors the animation in that stylesheet: the class outlives the ring by nothing. */
|
|
22
24
|
static readonly highlightMs = 2400;
|
|
25
|
+
/**
|
|
26
|
+
* What `tools()` contributes, in the order it builds them — the list a session's `builtins` option selects from.
|
|
27
|
+
* A screen that declares a hook tool of one of these names is not in it: that entry is the screen's, not this
|
|
28
|
+
* source's, so withholding the built-ins never withholds a tool a component published on purpose.
|
|
29
|
+
*/
|
|
30
|
+
static readonly builtins = ["navigate", "goBack", "readScreen", "readState", "highlight"] as const;
|
|
23
31
|
|
|
24
32
|
#bridge: AgentBridge | null;
|
|
25
33
|
readonly #builtins = new Map<string, ToolEntry[]>();
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AgentWireToolCall, LlmTurnRequest } from "./predefinedAdaptor/llm.adaptor";
|
|
2
|
+
/**
|
|
3
|
+
* Renames a turn's tools onto what a provider's function-calling wire accepts, and reads the answer back.
|
|
4
|
+
*
|
|
5
|
+
* A zone publishes its tools scope-prefixed — `videoProjectDraft.createVideoProject` — which is legal for MCP,
|
|
6
|
+
* where `.` is an allowed character and the scope join. Every OpenAI-compatible and Anthropic function schema is
|
|
7
|
+
* narrower: `[A-Za-z0-9_-]`, at most 64 characters. A provider that validates answers 400; DeepSeek does not, and
|
|
8
|
+
* what happened instead was worse to debug — the model normalized the illegal name itself, called the bare
|
|
9
|
+
* `createVideoProject`, and the browser answered `Unknown tool`, spending a turn on a tool that was published all
|
|
10
|
+
* along.
|
|
11
|
+
*
|
|
12
|
+
* Renamed here rather than in each adaptor for the reason `AgentService.explained` gives: every adaptor would
|
|
13
|
+
* otherwise have to remember, and forgetting is silent. A name the wire already accepts is left alone, so the
|
|
14
|
+
* root agent's request is byte-for-byte what it was.
|
|
15
|
+
*/
|
|
16
|
+
export declare class ToolNames {
|
|
17
|
+
#private;
|
|
18
|
+
/** Both dialects reject a longer name, and neither says so in terms of the tool you wrote. */
|
|
19
|
+
static readonly limit = 64;
|
|
20
|
+
/**
|
|
21
|
+
* Every name the request carries, not just the published ones: the transcript holds calls to tools that have
|
|
22
|
+
* since left the screen, and one of those reaching the wire unrenamed is the same failure a turn later.
|
|
23
|
+
*/
|
|
24
|
+
static of(request: LlmTurnRequest): ToolNames;
|
|
25
|
+
constructor(names: Iterable<string>);
|
|
26
|
+
get renamed(): boolean;
|
|
27
|
+
wire(name: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Unknown stays as it came. A model that invented a name is answered by the surface's own `Unknown tool`, which
|
|
30
|
+
* lands in the transcript as a tool result it can correct from — guessing which tool it meant would run one.
|
|
31
|
+
*/
|
|
32
|
+
surface(name: string): string;
|
|
33
|
+
encode(request: LlmTurnRequest): LlmTurnRequest;
|
|
34
|
+
decode(calls: AgentWireToolCall[]): AgentWireToolCall[];
|
|
35
|
+
}
|
|
@@ -8,7 +8,9 @@ import { AgentBridge } from "./AgentBridge.d.ts";
|
|
|
8
8
|
*
|
|
9
9
|
* Per zone view: a zone's `readState` reaches the keys its own subtree subscribes, and its `readScreen` and
|
|
10
10
|
* `highlight` reach into its own `data-agent-zone` container rather than the whole document. A page can shadow any
|
|
11
|
-
* of them by registering a hook tool of the same name — hook entries win over a source's.
|
|
11
|
+
* of them by registering a hook tool of the same name — hook entries win over a source's. **That is a root-scope
|
|
12
|
+
* move only**: inside a zone the hook entry is registered under its scope-prefixed name, which can never collide
|
|
13
|
+
* with the bare name a source publishes, so a zone drops a built-in with the session's `builtins` option instead.
|
|
12
14
|
*/
|
|
13
15
|
export declare class StoreSurfaceSource implements SurfaceSource {
|
|
14
16
|
#private;
|
|
@@ -16,6 +18,12 @@ export declare class StoreSurfaceSource implements SurfaceSource {
|
|
|
16
18
|
static readonly highlightClass = "akan-agent-highlight";
|
|
17
19
|
/** Mirrors the animation in that stylesheet: the class outlives the ring by nothing. */
|
|
18
20
|
static readonly highlightMs = 2400;
|
|
21
|
+
/**
|
|
22
|
+
* What `tools()` contributes, in the order it builds them — the list a session's `builtins` option selects from.
|
|
23
|
+
* A screen that declares a hook tool of one of these names is not in it: that entry is the screen's, not this
|
|
24
|
+
* source's, so withholding the built-ins never withholds a tool a component published on purpose.
|
|
25
|
+
*/
|
|
26
|
+
static readonly builtins: readonly ["navigate", "goBack", "readScreen", "readState", "highlight"];
|
|
19
27
|
/** Lazy by default: `AgentBridge.of()` walks the whole store, so it waits for the first enumeration. */
|
|
20
28
|
constructor(bridge?: AgentBridge);
|
|
21
29
|
tools: (view?: string[]) => ToolEntry[];
|
package/types/ui/Agent/Chat.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import { type AgentRunner, type CompactOptions } from "../../vendor/use-agentic.d.ts";
|
|
2
|
+
import { type AgentRunner, type AgentSessionOptions, type CompactOptions, type SessionHistory } from "../../vendor/use-agentic.d.ts";
|
|
3
3
|
import type { AttachReader } from "./attachment.d.ts";
|
|
4
4
|
import type { PersistOption } from "./sessionHistory.d.ts";
|
|
5
|
+
import type { BuiltinOption } from "./sessionView.d.ts";
|
|
5
6
|
import type { VoiceEngine } from "./voice.d.ts";
|
|
6
7
|
export interface ChatProps {
|
|
7
8
|
/** Reaches whichever surface is showing — the launcher while closed, the panel while open. */
|
|
@@ -17,6 +18,13 @@ export interface ChatProps {
|
|
|
17
18
|
* messages left verbatim below the summary. Tune it per provider; `{ at: 0 }` turns it off.
|
|
18
19
|
*/
|
|
19
20
|
compact?: CompactOptions;
|
|
21
|
+
/**
|
|
22
|
+
* Which of the runtime's own tools this chat's agent gets — all of them by default, `false` none, an array
|
|
23
|
+
* exactly the ones it names. A chat that must not leave the screen it is on drops `navigate` and `goBack`.
|
|
24
|
+
*/
|
|
25
|
+
builtins?: BuiltinOption;
|
|
26
|
+
/** Called after a compaction replaced messages with one summary — where a host syncs its own watermark. */
|
|
27
|
+
onCompact?: AgentSessionOptions["onCompact"];
|
|
20
28
|
defaultOpen?: boolean;
|
|
21
29
|
/**
|
|
22
30
|
* Controlled open state. Pass it with `onOpenChange` to drive the panel from the app's own control — a header
|
|
@@ -26,8 +34,14 @@ export interface ChatProps {
|
|
|
26
34
|
onOpenChange?: (open: boolean) => void;
|
|
27
35
|
/** `false` draws no launcher, for an app that opens the panel from a control of its own. */
|
|
28
36
|
launcher?: boolean;
|
|
29
|
-
/**
|
|
30
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Keeps the transcript across reloads — sessionStorage by default, `{ storage: "local" }` to outlive the tab, or
|
|
39
|
+
* a `SessionHistory` of the app's own to keep it anywhere else, a server included.
|
|
40
|
+
*
|
|
41
|
+
* Ignored, like every session option above it, when an enclosing `Agent.Zone` or `AgentProvider` already holds a
|
|
42
|
+
* session: this chat then binds to that one, and the options belong to whoever built it.
|
|
43
|
+
*/
|
|
44
|
+
persist?: PersistOption | SessionHistory;
|
|
31
45
|
/** Renders in the page flow instead of floating above it — a zone chat that lives inside its own section. */
|
|
32
46
|
inline?: boolean;
|
|
33
47
|
/** `false` gives the browser its own Cmd/Ctrl+L back, for an app whose shell already spends that chord. */
|
|
@@ -39,6 +53,13 @@ export interface ChatProps {
|
|
|
39
53
|
intro?: ReactNode;
|
|
40
54
|
/** Extra header controls, left of the built-in clear and close buttons. */
|
|
41
55
|
header?: ReactNode;
|
|
56
|
+
/**
|
|
57
|
+
* `false` draws no header bar at all — for an `inline` chat inside a panel the app already titles. The extra
|
|
58
|
+
* `header` controls go with it, and the clear action stays reachable as the `/new` command.
|
|
59
|
+
*/
|
|
60
|
+
chrome?: boolean;
|
|
61
|
+
/** The composer's opening text, read once at mount — where a `?prompt=` lands without sending it. */
|
|
62
|
+
defaultDraft?: string;
|
|
42
63
|
/**
|
|
43
64
|
* Reads a file the user attached into an attachment, or answers `null` to leave it to the built-in reader
|
|
44
65
|
* (images as bytes, text as text). This is where an app puts what needs a parser — a PDF's text, a spreadsheet's
|
|
@@ -60,6 +81,6 @@ export interface ChatProps {
|
|
|
60
81
|
* `persist` keeps it. An enclosing AgentProvider's session wins, which is how an app isolates a surface or swaps
|
|
61
82
|
* the loop while keeping this UI.
|
|
62
83
|
*/
|
|
63
|
-
export declare const DefaultChat: ({ className, title, instructions, runner, maxTurns, compact, defaultOpen, open: openProp, onOpenChange, launcher, persist, inline, shortcut, launcherClassName, panelClassName, intro, header, attach, voice, }: ChatProps) => ReactNode;
|
|
84
|
+
export declare const DefaultChat: ({ className, title, instructions, runner, maxTurns, compact, builtins, onCompact, defaultOpen, open: openProp, onOpenChange, launcher, persist, inline, shortcut, launcherClassName, panelClassName, intro, header, chrome, defaultDraft, attach, voice, }: ChatProps) => ReactNode;
|
|
64
85
|
declare const _default: import("react").ComponentType<ChatProps>;
|
|
65
86
|
export default _default;
|
|
@@ -2,8 +2,12 @@ interface ContextProps {
|
|
|
2
2
|
className?: string;
|
|
3
3
|
}
|
|
4
4
|
/**
|
|
5
|
-
* Assembles and shows
|
|
6
|
-
*
|
|
5
|
+
* Assembles and shows exactly what a turn would carry, on demand — the one preview of "what does the agent see on
|
|
6
|
+
* this screen" that no amount of reading the source answers.
|
|
7
|
+
*
|
|
8
|
+
* The tool list leads, by name only: a zone publishes its tools scope-prefixed, and instructions that name a tool
|
|
9
|
+
* without its prefix name a tool that does not exist. That is invisible in the source of either file and obvious
|
|
10
|
+
* here.
|
|
7
11
|
*/
|
|
8
12
|
export default function Context({ className }: ContextProps): import("react/jsx-runtime").JSX.Element;
|
|
9
13
|
export {};
|
package/types/ui/Agent/Zone.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import { type AgentRunner, type CompactOptions } from "../../vendor/use-agentic.d.ts";
|
|
2
|
+
import { type AgentRunner, type AgentSession, type AgentSessionOptions, type CompactOptions, type SessionHistory } from "../../vendor/use-agentic.d.ts";
|
|
3
3
|
import type { PersistOption } from "./sessionHistory.d.ts";
|
|
4
|
+
import type { BuiltinOption } from "./sessionView.d.ts";
|
|
4
5
|
export interface ZoneProps {
|
|
5
6
|
className?: string;
|
|
6
7
|
/** Names the zone; the scope id and the `data-agent-zone` container both derive from it. */
|
|
@@ -12,8 +13,26 @@ export interface ZoneProps {
|
|
|
12
13
|
maxTurns?: number;
|
|
13
14
|
/** When this zone's conversation summarizes itself — same contract as the chat's own `compact`. */
|
|
14
15
|
compact?: CompactOptions;
|
|
15
|
-
/**
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Which of the runtime's own tools this zone's agent gets — all of them by default, `false` none, an array
|
|
18
|
+
* exactly the ones it names. `builtins={["readScreen", "readState"]}` is how a zone that must not leave the
|
|
19
|
+
* screen stops being able to: the tools are withheld, not discouraged, so a prompt cannot talk the model past it.
|
|
20
|
+
*/
|
|
21
|
+
builtins?: BuiltinOption;
|
|
22
|
+
/**
|
|
23
|
+
* Keeps this zone's transcript across reloads, keyed by the zone's scope path — web storage by default, or a
|
|
24
|
+
* `SessionHistory` of the app's own to keep it anywhere else, a server included.
|
|
25
|
+
*/
|
|
26
|
+
persist?: PersistOption | SessionHistory;
|
|
27
|
+
/** Called after a compaction replaced messages with one summary — where a host syncs its own watermark. */
|
|
28
|
+
onCompact?: AgentSessionOptions["onCompact"];
|
|
29
|
+
/**
|
|
30
|
+
* Runs this zone on a session the app built instead of one of its own, and the app then owns it: unmounting the
|
|
31
|
+
* zone leaves it running. Read once at mount, like every other session option here.
|
|
32
|
+
*/
|
|
33
|
+
session?: AgentSession;
|
|
34
|
+
/** Hands the session out once it exists, for a page or store that wants to send into it or watch it. */
|
|
35
|
+
onSession?: (session: AgentSession) => void;
|
|
17
36
|
children: ReactNode;
|
|
18
37
|
}
|
|
19
38
|
/**
|
|
@@ -21,5 +40,9 @@ export interface ZoneProps {
|
|
|
21
40
|
* inside — hook tools, `st.use` subscriptions, guides — belongs to this zone's session *and* to the root agent:
|
|
22
41
|
* zones are views, never walls. An `Agent.Chat` mounted inside binds to this session automatically, so two zones
|
|
23
42
|
* on one screen run two conversations in parallel, each seeing only its own subtree.
|
|
43
|
+
*
|
|
44
|
+
* **Everything a zone publishes is named `<id>.<name>`.** Instructions that name a tool must carry the prefix —
|
|
45
|
+
* a bare name is a tool that does not exist, and the model calling it spends a turn on `Unknown tool`. Build the
|
|
46
|
+
* name from the id rather than writing it twice, and read `Agent.Context`'s Assemble to see the published list.
|
|
24
47
|
*/
|
|
25
|
-
export declare const Zone: ({ className, id, label, instructions, runner, maxTurns, compact, persist, children, }: ZoneProps) => import("react/jsx-runtime").JSX.Element;
|
|
48
|
+
export declare const Zone: ({ className, id, label, instructions, runner, maxTurns, compact, builtins, persist, onCompact, session: provided, onSession, children, }: ZoneProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type AgentRunner, AgentSession, type CompactOptions } from "../../vendor/use-agentic.d.ts";
|
|
1
|
+
import { type AgentRunner, AgentSession, type AgentSessionOptions, type CompactOptions, type SessionHistory } from "../../vendor/use-agentic.d.ts";
|
|
2
2
|
import { type PersistOption } from "./sessionHistory.d.ts";
|
|
3
|
+
import { type BuiltinOption } from "./sessionView.d.ts";
|
|
3
4
|
export interface AgentSessionSetup {
|
|
4
5
|
/** Read per call rather than captured, so text the session builds follows a language switched mid-conversation. */
|
|
5
6
|
l: (key: string) => string;
|
|
@@ -9,10 +10,18 @@ export interface AgentSessionSetup {
|
|
|
9
10
|
instructions?: string;
|
|
10
11
|
maxTurns?: number;
|
|
11
12
|
compact?: CompactOptions;
|
|
12
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Which of the runtime's own tools this session gets: all of them by default, none with `false`, exactly the
|
|
15
|
+
* ones an array names. A zone whose conversation must stay on one screen takes `navigate` and `goBack` off it.
|
|
16
|
+
*/
|
|
17
|
+
builtins?: BuiltinOption;
|
|
18
|
+
/** Web storage by default; a `SessionHistory` puts the transcript wherever the app keeps it, including a server. */
|
|
19
|
+
persist?: PersistOption | SessionHistory;
|
|
20
|
+
/** Called after a compaction replaced messages with one summary — where a host syncs its own watermark. */
|
|
21
|
+
onCompact?: AgentSessionOptions["onCompact"];
|
|
13
22
|
}
|
|
14
23
|
/**
|
|
15
24
|
* The one place a chat session is wired to the akan runtime. Chat and Zone both build one, and building it twice
|
|
16
25
|
* is how a zone came to be the only surface with no `compact` option — an option added on one side of a copy.
|
|
17
26
|
*/
|
|
18
|
-
export declare const agentSessionOf: ({ l, view, runner, instructions, maxTurns, compact, persist, }: AgentSessionSetup) => AgentSession;
|
|
27
|
+
export declare const agentSessionOf: ({ l, view, runner, instructions, maxTurns, compact, builtins, persist, onCompact, }: AgentSessionSetup) => AgentSession;
|
|
@@ -14,5 +14,5 @@ export declare const Agent: {
|
|
|
14
14
|
StateKey: typeof StateKey;
|
|
15
15
|
Tool: typeof Tool;
|
|
16
16
|
Transcript: typeof Transcript;
|
|
17
|
-
Zone: ({ className, id, label, instructions, runner, maxTurns, compact, persist, children, }: import("./Zone.d.ts").ZoneProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
Zone: ({ className, id, label, instructions, runner, maxTurns, compact, builtins, persist, onCompact, session: provided, onSession, children, }: import("./Zone.d.ts").ZoneProps) => import("react/jsx-runtime").JSX.Element;
|
|
18
18
|
};
|
|
@@ -11,4 +11,4 @@ export type PersistOption = boolean | {
|
|
|
11
11
|
* why the cap is applied *before* the pairing repair: the window it keeps can start between a tool call and the
|
|
12
12
|
* result answering it, and a transcript restored in that state is refused by the provider on its first turn.
|
|
13
13
|
*/
|
|
14
|
-
export declare const sessionHistoryOf: (persist: PersistOption | undefined, pathKey?: string) => SessionHistory | undefined;
|
|
14
|
+
export declare const sessionHistoryOf: (persist: PersistOption | SessionHistory | undefined, pathKey?: string) => SessionHistory | undefined;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { StoreSurfaceSource } from "akanjs/store";
|
|
2
|
+
import type { AgenticSurface, SurfaceView } from "../../vendor/use-agentic.d.ts";
|
|
3
|
+
/** One of the tools the akan runtime contributes to every screen, whatever that screen declares. */
|
|
4
|
+
export type AgentBuiltin = (typeof StoreSurfaceSource.builtins)[number];
|
|
5
|
+
/** `true` (the default) takes all of them, `false` none, an array exactly the ones it names. */
|
|
6
|
+
export type BuiltinOption = boolean | AgentBuiltin[];
|
|
7
|
+
/**
|
|
8
|
+
* The half of the surface one session reads: scoped to its zone, and narrowed to the built-ins it was given.
|
|
9
|
+
*
|
|
10
|
+
* Narrowing happens here rather than on the source because the source is shared — two sessions read one registry,
|
|
11
|
+
* and a zone that must not navigate away cannot take `navigate` off the screen for the root agent too. A withheld
|
|
12
|
+
* tool is withheld from `call` as well as from the listing, answering the same "unknown tool" a name that was
|
|
13
|
+
* never registered gets: a tool the model can still reach by guessing its name is not withheld.
|
|
14
|
+
*/
|
|
15
|
+
export declare const sessionView: (surface: AgenticSurface, path: string[], builtins?: BuiltinOption) => SurfaceView;
|
package/types/ui/Field.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export declare const Field: {
|
|
|
33
33
|
Price: import("react").MemoExoticComponent<({ label, desc, labelClassName, className, value, onChange, placeholder, nullable, disabled, minlength, maxlength, transform, validate, onPressEnter, inputClassName, inputStyleType, }: PriceProps) => import("react/jsx-runtime").JSX.Element>;
|
|
34
34
|
TextArea: import("react").MemoExoticComponent<({ label, desc, labelClassName, className, value, onChange, placeholder, nullable, disabled, rows, minlength, maxlength, transform, validate, onPressEnter, cache, inputClassName, }: TextAreaProps) => import("react/jsx-runtime").JSX.Element>;
|
|
35
35
|
Switch: ({ label, desc, labelClassName, className, value, onChange, disabled, inputClassName, onDesc, offDesc, }: SwitchProps) => import("react/jsx-runtime").JSX.Element;
|
|
36
|
-
ToggleSelect: <I extends string | number | boolean | null>({ className, labelClassName, label, desc, items, value, validate, onChange, nullable, disabled, btnClassName, }: ToggleSelectProps<I>) => import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
ToggleSelect: <I extends string | number | boolean | null, Nullable extends boolean = false>({ className, labelClassName, label, desc, items, value, validate, onChange, nullable, disabled, btnClassName, }: ToggleSelectProps<I, Nullable>) => import("react/jsx-runtime").JSX.Element;
|
|
37
37
|
MultiToggleSelect: <I extends string | number | boolean>({ className, labelClassName, label, desc, items, value, minlength, maxlength, validate, onChange, disabled, }: MultiToggleSelectProps<I>) => import("react/jsx-runtime").JSX.Element;
|
|
38
38
|
TextList: ({ label, desc, labelClassName, className, value, onChange, placeholder, disabled, transform, minlength, maxlength, minTextlength, maxTextlength, cache, validate, inputClassName, }: TextListProps) => import("react/jsx-runtime").JSX.Element;
|
|
39
39
|
Tags: ({ label, desc, labelClassName, className, value, onChange, placeholder, disabled, transform, minlength, maxlength, minTextlength, maxTextlength, validate, inputClassName, }: TagsProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -71,7 +71,7 @@ interface ListProps<Item> {
|
|
|
71
71
|
label?: string;
|
|
72
72
|
desc?: string;
|
|
73
73
|
nullable?: boolean;
|
|
74
|
-
value: Item[];
|
|
74
|
+
value: Item[] | null;
|
|
75
75
|
onChange: (value: Item[]) => void;
|
|
76
76
|
onAdd: () => void;
|
|
77
77
|
renderItem: (item: Item, idx: number) => ReactNode;
|
|
@@ -137,14 +137,14 @@ interface SwitchProps {
|
|
|
137
137
|
desc?: string;
|
|
138
138
|
labelClassName?: string;
|
|
139
139
|
className?: string;
|
|
140
|
-
value: boolean;
|
|
140
|
+
value: boolean | null;
|
|
141
141
|
onChange: (value: boolean) => void;
|
|
142
142
|
inputClassName?: string;
|
|
143
143
|
onDesc?: string;
|
|
144
144
|
offDesc?: string;
|
|
145
145
|
disabled?: boolean;
|
|
146
146
|
}
|
|
147
|
-
interface ToggleSelectProps<I> {
|
|
147
|
+
interface ToggleSelectProps<I, Nullable extends boolean> {
|
|
148
148
|
className?: string;
|
|
149
149
|
labelClassName?: string;
|
|
150
150
|
label?: string;
|
|
@@ -156,11 +156,11 @@ interface ToggleSelectProps<I> {
|
|
|
156
156
|
value: I;
|
|
157
157
|
disabled?: boolean;
|
|
158
158
|
}[] | readonly I[] | I[] | EnumInstance<string, I>;
|
|
159
|
-
value: I;
|
|
160
|
-
nullable?:
|
|
159
|
+
value: I | null;
|
|
160
|
+
nullable?: Nullable;
|
|
161
161
|
disabled?: boolean;
|
|
162
162
|
validate?: (value: I) => boolean | string;
|
|
163
|
-
onChange: (value: I) => void;
|
|
163
|
+
onChange: (value: Nullable extends true ? I | null : I) => void;
|
|
164
164
|
btnClassName?: string;
|
|
165
165
|
}
|
|
166
166
|
interface MultiToggleSelectProps<I extends string | number | boolean> {
|
|
@@ -173,7 +173,7 @@ interface MultiToggleSelectProps<I extends string | number | boolean> {
|
|
|
173
173
|
value: I;
|
|
174
174
|
disabled?: boolean;
|
|
175
175
|
}[] | readonly I[] | I[];
|
|
176
|
-
value: I[];
|
|
176
|
+
value: I[] | null;
|
|
177
177
|
disabled?: boolean;
|
|
178
178
|
minlength?: number;
|
|
179
179
|
maxlength?: number;
|
|
@@ -185,7 +185,7 @@ interface TextListProps {
|
|
|
185
185
|
desc?: string;
|
|
186
186
|
labelClassName?: string;
|
|
187
187
|
className?: string;
|
|
188
|
-
value: string[];
|
|
188
|
+
value: string[] | null;
|
|
189
189
|
onChange: (value: string[]) => void;
|
|
190
190
|
inputClassName?: string;
|
|
191
191
|
placeholder?: string;
|
|
@@ -203,7 +203,7 @@ interface TagsProps {
|
|
|
203
203
|
desc?: string;
|
|
204
204
|
labelClassName?: string;
|
|
205
205
|
className?: string;
|
|
206
|
-
value: string[];
|
|
206
|
+
value: string[] | null;
|
|
207
207
|
onChange: (value: string[]) => void;
|
|
208
208
|
inputClassName?: string;
|
|
209
209
|
placeholder?: string;
|
|
@@ -386,7 +386,7 @@ interface ChildrenProps<T extends string, State, Input, Full, Light> {
|
|
|
386
386
|
disabled?: boolean;
|
|
387
387
|
nullable?: boolean;
|
|
388
388
|
initArgs?: any[];
|
|
389
|
-
value: Light[];
|
|
389
|
+
value: Light[] | null;
|
|
390
390
|
onChange: (value: Light[]) => void;
|
|
391
391
|
onSearch?: (text: string) => void;
|
|
392
392
|
slice: SliceMeta;
|
|
@@ -402,7 +402,7 @@ interface ChildrenIdProps<T extends string, State, Input, Full, Light> {
|
|
|
402
402
|
disabled?: boolean;
|
|
403
403
|
nullable?: boolean;
|
|
404
404
|
initArgs?: any[];
|
|
405
|
-
value: string[];
|
|
405
|
+
value: string[] | null;
|
|
406
406
|
slice: SliceMeta;
|
|
407
407
|
onChange: (value: string[]) => void;
|
|
408
408
|
onSearch?: (text: string) => void;
|
package/types/ui/RecentTime.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { type Dayjs } from "akanjs/base";
|
|
2
|
+
export type RecentTimeRelativeUnit = "second" | "minute" | "hour" | "day" | "week" | "month" | "year";
|
|
3
|
+
export type RecentTimeRelativeStyle = "fromNow" | "always" | "auto";
|
|
4
|
+
export interface RecentTimeRelative {
|
|
5
|
+
unit: RecentTimeRelativeUnit;
|
|
6
|
+
/** Signed count in `unit`. Negative is past, positive is future, `0` is now. */
|
|
7
|
+
count: number;
|
|
8
|
+
date: Dayjs;
|
|
9
|
+
now: Dayjs;
|
|
10
|
+
defaultLabel: string;
|
|
11
|
+
}
|
|
12
|
+
export type RecentTimeRelativeFormat = RecentTimeRelativeStyle | ((relative: RecentTimeRelative) => string);
|
|
2
13
|
export interface RecentTimeProps {
|
|
3
14
|
/** Date value to render. Null renders nothing, and epoch placeholder values render --:--. */
|
|
4
15
|
date: Date | Dayjs | null;
|
|
@@ -6,7 +17,13 @@ export interface RecentTimeProps {
|
|
|
6
17
|
breakUnit?: Intl.RelativeTimeFormatUnit;
|
|
7
18
|
/** Use compact automatic formatting or always include date and time. */
|
|
8
19
|
format?: "full" | "auto";
|
|
20
|
+
/**
|
|
21
|
+
* Relative phrasing. `"fromNow"` (default) keeps dayjs locale strings (`하루 전`).
|
|
22
|
+
* `"always"` / `"auto"` use `Intl.RelativeTimeFormat` — `1일 전` vs `어제`.
|
|
23
|
+
* A function replaces the relative label; return `defaultLabel` to keep the default.
|
|
24
|
+
*/
|
|
25
|
+
relative?: RecentTimeRelativeFormat;
|
|
9
26
|
/** Additional classes for the trigger span. */
|
|
10
27
|
className?: string;
|
|
11
28
|
}
|
|
12
|
-
export declare const RecentTime: ({ date, breakUnit, format, className }: RecentTimeProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
29
|
+
export declare const RecentTime: ({ date, breakUnit, format, relative, className }: RecentTimeProps) => import("react/jsx-runtime").JSX.Element | null;
|