akanjs 3.0.0-beta.0 → 3.0.0-beta.2

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 (40) hide show
  1. package/dictionary/agent.dictionary.ts +8 -0
  2. package/dictionary/agentTurn.dictionary.ts +6 -2
  3. package/dictionary/base.dictionary.ts +1 -0
  4. package/fetch/agentTurn.ts +1 -1
  5. package/local/apps/serverLifecycle/serverLifecycle-local.db-shm +0 -0
  6. package/local/apps/serverLifecycle/serverLifecycle-local_solid.db-shm +0 -0
  7. package/package.json +1 -1
  8. package/service/predefinedAdaptor/anthropicLlm.ts +382 -0
  9. package/service/predefinedAdaptor/deepseekLlm.ts +21 -211
  10. package/service/predefinedAdaptor/index.ts +3 -0
  11. package/service/predefinedAdaptor/llm.adaptor.ts +25 -1
  12. package/service/predefinedAdaptor/openaiDialect.ts +246 -0
  13. package/service/predefinedAdaptor/openaiLlm.ts +91 -0
  14. package/signal/agentTurnStream.ts +5 -2
  15. package/types/dictionary/agent.dictionary.d.ts +1 -1
  16. package/types/dictionary/base.dictionary.d.ts +1 -1
  17. package/types/dictionary/dictionary.d.ts +9 -9
  18. package/types/fetch/agentTurn.d.ts +3 -3
  19. package/types/service/agent.service.d.ts +1 -1
  20. package/types/service/predefinedAdaptor/anthropicLlm.d.ts +112 -0
  21. package/types/service/predefinedAdaptor/deepseekLlm.d.ts +10 -67
  22. package/types/service/predefinedAdaptor/index.d.ts +3 -0
  23. package/types/service/predefinedAdaptor/llm.adaptor.d.ts +25 -1
  24. package/types/service/predefinedAdaptor/openaiDialect.d.ts +96 -0
  25. package/types/service/predefinedAdaptor/openaiLlm.d.ts +24 -0
  26. package/types/signal/agent.signal.d.ts +1 -1
  27. package/types/signal/agentTurn.d.ts +1 -1
  28. package/types/signal/agentTurnStream.d.ts +1 -1
  29. package/types/ui/Agent/Attach.d.ts +4 -1
  30. package/types/ui/Agent/Composer.d.ts +3 -1
  31. package/types/ui/Agent/useChatAttachments.d.ts +1 -0
  32. package/types/vendor/use-agentic/types.d.ts +7 -1
  33. package/ui/Agent/Attach.tsx +13 -1
  34. package/ui/Agent/Chat.tsx +1 -0
  35. package/ui/Agent/Composer.tsx +11 -2
  36. package/ui/Agent/useChatAttachments.ts +6 -0
  37. package/vendor/use-agentic/AgentSession.ts +12 -2
  38. package/vendor/use-agentic/WIRE.md +6 -1
  39. package/vendor/use-agentic/httpRunner.ts +1 -1
  40. package/vendor/use-agentic/types.ts +8 -1
@@ -5,6 +5,8 @@ export interface ComposerProps {
5
5
  session: AgentSession;
6
6
  draft: string;
7
7
  attached: readonly MessageAttachment[];
8
+ /** Files still being read. An `attach` that uploads takes seconds, and a panel that shows nothing looks broken. */
9
+ pending?: number;
8
10
  /** Absent when the screen cannot listen — the same rule as publishing no tool for a control that is not drawn. */
9
11
  mic?: {
10
12
  listening: boolean;
@@ -19,5 +21,5 @@ export interface ComposerProps {
19
21
  inputRef?: RefObject<HTMLTextAreaElement | null>;
20
22
  }
21
23
  /** What the user writes with: the staged files above, and the controls that send or stop below them. */
22
- export declare const DefaultComposer: ({ className, session, draft, attached, mic, onDraft, onKeyDown, onFiles, onRemoveFile, onSend, onStop, inputRef, }: ComposerProps) => import("react/jsx-runtime").JSX.Element;
24
+ export declare const DefaultComposer: ({ className, session, draft, attached, pending, mic, onDraft, onKeyDown, onFiles, onRemoveFile, onSend, onStop, inputRef, }: ComposerProps) => import("react/jsx-runtime").JSX.Element;
23
25
  export declare const Composer: import("react").ComponentType<ComposerProps>;
@@ -14,6 +14,7 @@ interface ChatAttachmentsSetup {
14
14
  */
15
15
  export declare const useChatAttachments: ({ session, attach, limits, l }: ChatAttachmentsSetup) => {
16
16
  attached: MessageAttachment[];
17
+ pending: number;
17
18
  dragging: boolean;
18
19
  dropProps: {
19
20
  [key: string]: DragEventHandler<HTMLElement>;
@@ -114,6 +114,12 @@ export interface ToolCallResult {
114
114
  * model cannot read and says so in the transcript, because a silently dropped file is one the model then
115
115
  * hallucinates about.
116
116
  */
117
+ /**
118
+ * Why an assistant turn ended. `length` is the provider's own ceiling rather than the model's choice, so the turn
119
+ * is incomplete — a truncated answer and a turn cut off before its tool call finished both arrive this way, and
120
+ * neither is distinguishable from `end` without it.
121
+ */
122
+ export type TurnStop = "end" | "toolUse" | "length";
117
123
  export interface MessageAttachment {
118
124
  name: string;
119
125
  mimeType: string;
@@ -165,7 +171,7 @@ export type RunnerEvent = {
165
171
  args: Record<string, unknown>;
166
172
  } | {
167
173
  type: "done";
168
- stop: "end" | "toolUse";
174
+ stop: TurnStop;
169
175
  }
170
176
  /**
171
177
  * `data` accompanies a message that is a code rather than a sentence — the values whoever resolves the code
@@ -44,9 +44,12 @@ interface ChipsProps {
44
44
  /** Omitted for a sent message: what is already on the wire cannot be taken back. */
45
45
  onRemove?: (index: number) => void;
46
46
  removeLabel?: string;
47
+ /** Files still being read, standing beside the ones that are staged so the panel is never blank mid-drop. */
48
+ pending?: number;
49
+ pendingLabel?: string;
47
50
  }
48
51
 
49
- export const Chips = ({ className, attachments, onRemove, removeLabel }: ChipsProps) => (
52
+ export const Chips = ({ className, attachments, onRemove, removeLabel, pending = 0, pendingLabel }: ChipsProps) => (
50
53
  <div className={cn("flex flex-wrap gap-1", className)}>
51
54
  {attachments.map((attachment, idx) => (
52
55
  <span
@@ -73,5 +76,14 @@ export const Chips = ({ className, attachments, onRemove, removeLabel }: ChipsPr
73
76
  ) : null}
74
77
  </span>
75
78
  ))}
79
+ {Array.from({ length: pending }, (_, idx) => (
80
+ <span
81
+ className="flex animate-pulse items-center gap-1 rounded-field bg-muted px-2 py-0.5 text-foreground/50 text-xs"
82
+ key={`pending-${idx}`}
83
+ >
84
+ <AiOutlinePaperClip />
85
+ <span className="max-w-32 truncate">{pendingLabel}</span>
86
+ </span>
87
+ ))}
76
88
  </div>
77
89
  );
package/ui/Agent/Chat.tsx CHANGED
@@ -498,6 +498,7 @@ export const DefaultChat = ({
498
498
  <Menu onPick={(row) => row.pick()} rows={menu.rows} selected={menu.selected} />
499
499
  <Composer
500
500
  attached={files.attached}
501
+ pending={files.pending}
501
502
  draft={draft}
502
503
  inputRef={inputRef}
503
504
  {...(speech.canListen ? { mic: { listening: speech.listening, onToggle: speech.toggle } } : {})}
@@ -13,6 +13,8 @@ export interface ComposerProps {
13
13
  session: AgentSession;
14
14
  draft: string;
15
15
  attached: readonly MessageAttachment[];
16
+ /** Files still being read. An `attach` that uploads takes seconds, and a panel that shows nothing looks broken. */
17
+ pending?: number;
16
18
  /** Absent when the screen cannot listen — the same rule as publishing no tool for a control that is not drawn. */
17
19
  mic?: { listening: boolean; onToggle: () => void };
18
20
  onDraft: (text: string) => void;
@@ -33,6 +35,7 @@ export const DefaultComposer = ({
33
35
  session,
34
36
  draft,
35
37
  attached,
38
+ pending = 0,
36
39
  mic,
37
40
  onDraft,
38
41
  onKeyDown,
@@ -53,8 +56,14 @@ export const DefaultComposer = ({
53
56
  }, [draft, inputRef]);
54
57
  return (
55
58
  <div className={cn("flex flex-col gap-2 border-foreground/5 border-t p-3", className)}>
56
- {attached.length ? (
57
- <Chips attachments={attached} onRemove={onRemoveFile} removeLabel={l("base.agentAttachRemove")} />
59
+ {attached.length || pending ? (
60
+ <Chips
61
+ attachments={attached}
62
+ onRemove={onRemoveFile}
63
+ pending={pending}
64
+ pendingLabel={l("base.agentAttachReading")}
65
+ removeLabel={l("base.agentAttachRemove")}
66
+ />
58
67
  ) : null}
59
68
  <div className="flex items-end gap-2">
60
69
  {mic ? (
@@ -25,6 +25,8 @@ export const useChatAttachments = ({ session, attach, limits = {}, l }: ChatAtta
25
25
  const count = limits.perMessageCount ?? maxMessageAttachments;
26
26
  const messageBytes = limits.perMessageBytes ?? maxMessageAttachmentBytes;
27
27
  const [attached, setAttached] = useState<MessageAttachment[]>([]);
28
+
29
+ const [pending, setPending] = useState(0);
28
30
  const [dragging, setDragging] = useState(false);
29
31
  const staged = useRef<MessageAttachment[]>([]);
30
32
 
@@ -45,6 +47,7 @@ export const useChatAttachments = ({ session, attach, limits = {}, l }: ChatAtta
45
47
  session.note(l("base.agentAttachTooMany", { count }));
46
48
  return;
47
49
  }
50
+ setPending((waiting) => waiting + 1);
48
51
  try {
49
52
  const read = await Attachment.read(file, attach, limits);
50
53
  if (Attachment.failure(read))
@@ -58,6 +61,8 @@ export const useChatAttachments = ({ session, attach, limits = {}, l }: ChatAtta
58
61
  else stage([...staged.current, read]);
59
62
  } catch (error) {
60
63
  session.report(`${file.name}: ${error instanceof Error ? error.message : String(error)}`);
64
+ } finally {
65
+ setPending((waiting) => Math.max(0, waiting - 1));
61
66
  }
62
67
  }
63
68
  };
@@ -94,6 +99,7 @@ export const useChatAttachments = ({ session, attach, limits = {}, l }: ChatAtta
94
99
  };
95
100
  return {
96
101
  attached,
102
+ pending,
97
103
  dragging,
98
104
  dropProps,
99
105
  add,
@@ -12,6 +12,7 @@ import type {
12
12
  SurfaceView,
13
13
  ToolCallRequest,
14
14
  ToolCallResult,
15
+ TurnStop,
15
16
  } from "./types";
16
17
 
17
18
  export interface PendingApproval {
@@ -264,6 +265,15 @@ export class AgentSession {
264
265
  this.#unanswered(toolCalls);
265
266
  return;
266
267
  }
268
+
269
+ if (stop === "length") {
270
+
271
+ this.#fail(
272
+ "The model ran out of room mid-answer, so this turn is incomplete. Ask again, or raise the answer limit.",
273
+ );
274
+ if (toolCalls.length) this.#unanswered(toolCalls);
275
+ return;
276
+ }
267
277
  if (stop !== "toolUse" || !toolCalls.length) return;
268
278
  const toolResults: ToolCallResult[] = [];
269
279
  for (const call of toolCalls)
@@ -478,7 +488,7 @@ export class AgentSession {
478
488
  return text;
479
489
  }
480
490
 
481
- async #assistantTurn(signal: AbortSignal): Promise<{ toolCalls: ToolCallRequest[]; stop: "end" | "toolUse" }> {
491
+ async #assistantTurn(signal: AbortSignal): Promise<{ toolCalls: ToolCallRequest[]; stop: TurnStop }> {
482
492
  const { tools, guides } = this.#surface.snapshot();
483
493
  const instructions = [this.#options.instructions, ...guides].filter(Boolean).join("\n\n");
484
494
  const request: RunnerRequest = {
@@ -493,7 +503,7 @@ export class AgentSession {
493
503
  this.#append({ role: "assistant" });
494
504
  let text = "";
495
505
  const toolCalls: ToolCallRequest[] = [];
496
- let stop: "end" | "toolUse" = "end";
506
+ let stop: TurnStop = "end";
497
507
  for await (const event of this.#runner.run(request)) {
498
508
  if (signal.aborted) break;
499
509
  if (event.type === "text") {
@@ -44,7 +44,7 @@ tools, and the loop stay in the client; the server serves exactly one stateless
44
44
  ## Attachments
45
45
 
46
46
  A message may carry files in `attachments` (`MessageAttachment`, `types.ts`) — one of `data` (base64 bytes), `url`,
47
- or `text` (already-extracted content), plus `name` and `mimeType`:
47
+ or `text` (already-extracted content), plus `name` and `mimeType`, and an optional `ref`:
48
48
 
49
49
  ```jsonc
50
50
  { "role": "user", "text": "What does this chart say?",
@@ -57,6 +57,11 @@ it with a note in the message text saying which file was not read and why, so th
57
57
  another form. `text` is readable by every provider by definition, which is what makes an extracted PDF work against
58
58
  a text-only model.
59
59
 
60
+ `ref` is the host's own handle on the file — a file id, a storage key — opaque to everything in between. A backend
61
+ carries it and never reads it; it is not shown to the model, and a tool the host published is what spends it.
62
+ Without one, a host that stores its uploads has to identify a file by name and size, which collides for two crops
63
+ of one export.
64
+
60
65
  Nothing here is stored: the wire carries the bytes for exactly one turn's request.
61
66
 
62
67
  ## Response
@@ -10,7 +10,7 @@ export interface HttpRunnerOptions {
10
10
  interface TurnAnswer {
11
11
  text?: string;
12
12
  toolCalls?: ToolCallRequest[];
13
- stop?: "end" | "toolUse";
13
+ stop?: "end" | "toolUse" | "length";
14
14
  }
15
15
 
16
16
  const eventTypes = new Set(["text", "toolCall", "done", "error"]);
@@ -131,6 +131,13 @@ export interface ToolCallResult {
131
131
  * model cannot read and says so in the transcript, because a silently dropped file is one the model then
132
132
  * hallucinates about.
133
133
  */
134
+ /**
135
+ * Why an assistant turn ended. `length` is the provider's own ceiling rather than the model's choice, so the turn
136
+ * is incomplete — a truncated answer and a turn cut off before its tool call finished both arrive this way, and
137
+ * neither is distinguishable from `end` without it.
138
+ */
139
+ export type TurnStop = "end" | "toolUse" | "length";
140
+
134
141
  export interface MessageAttachment {
135
142
  name: string;
136
143
  mimeType: string;
@@ -178,7 +185,7 @@ export interface ContextBlock {
178
185
  export type RunnerEvent =
179
186
  | { type: "text"; delta: string }
180
187
  | { type: "toolCall"; id: string; name: string; args: Record<string, unknown> }
181
- | { type: "done"; stop: "end" | "toolUse" }
188
+ | { type: "done"; stop: TurnStop }
182
189
  /**
183
190
  * `data` accompanies a message that is a code rather than a sentence — the values whoever resolves the code
184
191
  * interpolates into its text. A host that does not know the code shows the message as it stands.