akanjs 3.0.0-beta.3 → 3.0.0-beta.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-beta.3",
3
+ "version": "3.0.0-beta.4",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -98,14 +98,18 @@ export class AgentService extends serve("agent" as const, ({ plug }) => ({
98
98
  private static isReadable(attachment: AgentWireAttachment, accepts: LlmAccepts): boolean {
99
99
  if (attachment.text) return true;
100
100
  if (!attachment.data && !attachment.url) return false;
101
+
102
+ if (typeof attachment.mimeType !== "string") return false;
101
103
  return attachment.mimeType.startsWith("image/") ? !!accepts.image : !!accepts.document;
102
104
  }
103
105
 
104
106
  private static note(attachment: AgentWireAttachment): string {
105
107
  const why =
106
- attachment.data || attachment.url
107
- ? "this model cannot read that type"
108
- : "its content is no longer available, as a reloaded conversation keeps the name and not the bytes";
108
+ !attachment.data && !attachment.url
109
+ ? "its content is no longer available, as a reloaded conversation keeps the name and not the bytes"
110
+ : typeof attachment.mimeType === "string"
111
+ ? "this model cannot read that type"
112
+ : "it names no type it could be read as";
109
113
  return `[Attachment not read: ${attachment.name} (${attachment.mimeType}) — ${why}. Tell the user it was not read instead of guessing what it holds, and ask for the text if the answer needs it.]`;
110
114
  }
111
115
  }
@@ -1,4 +1,4 @@
1
- export { AgentProvider, type AgentProviderProps, type AgentRunner, AgentSession, type AgentSessionOptions, type ChatMessage, type CompactOptions, type ContextBlock, httpRunner, type PublishedTool, type RunnerEvent, type RunnerRequest, SessionContext, type SessionHistory, type SurfaceView, useAgent, } from "../vendor/use-agentic.d.ts";
1
+ export { AgentProvider, type AgentProviderProps, type AgentRunner, AgentSession, type AgentSessionOptions, type ChatMessage, type CompactOptions, type ContextBlock, httpRunner, type MessageAttachment, type PublishedTool, type RunnerEvent, type RunnerRequest, SessionContext, type SessionHistory, type SurfaceView, useAgent, } from "../vendor/use-agentic.d.ts";
2
2
  export { Agent } from "./Agent.d.ts";
3
3
  export { type ApprovalProps, DefaultApproval } from "./Agent/Approval.d.ts";
4
4
  export { type AgentSessionSetup, agentSessionOf } from "./Agent/agentSessionOf.d.ts";
@@ -10,9 +10,10 @@ import type { ChatMessage } from "./types.d.ts";
10
10
  * transcript is assembled rather than where each hole is made.
11
11
  */
12
12
  export declare class Transcript {
13
- #private;
14
13
  static readonly unanswered = "The turn was stopped before this call ran.";
15
14
  /** What one turn posts: no host-only message, no unanswered call, no result answering a call nobody sees. */
16
15
  static wire(messages: readonly ChatMessage[]): ChatMessage[];
17
16
  static sanitize(messages: readonly ChatMessage[]): ChatMessage[];
17
+ /** An empty assistant message is a draft a reload or an abort caught before it said anything. */
18
+ static carries(message: ChatMessage): boolean;
18
19
  }
@@ -40,6 +40,7 @@ export const Attach = ({ className, label, onPick }: AttachProps) => {
40
40
 
41
41
  interface ChipsProps {
42
42
  className?: string;
43
+
43
44
  attachments: readonly MessageAttachment[];
44
45
  /** Omitted for a sent message: what is already on the wire cannot be taken back. */
45
46
  onRemove?: (index: number) => void;
@@ -56,7 +57,7 @@ export const Chips = ({ className, attachments, onRemove, removeLabel, pending =
56
57
  className="flex items-center gap-1 rounded-field bg-muted px-2 py-0.5 text-xs"
57
58
  key={`${attachment.name}-${idx}`}
58
59
  >
59
- {attachment.data && attachment.mimeType.startsWith("image/") ? (
60
+ {attachment.data && attachment.mimeType?.startsWith("image/") ? (
60
61
  <img
61
62
  alt={attachment.name}
62
63
  className="size-6 rounded-field object-cover"
package/ui/index.ts CHANGED
@@ -10,6 +10,7 @@ export {
10
10
  type CompactOptions,
11
11
  type ContextBlock,
12
12
  httpRunner,
13
+ type MessageAttachment,
13
14
  type PublishedTool,
14
15
  type RunnerEvent,
15
16
  type RunnerRequest,
@@ -293,6 +293,9 @@ export class AgentSession {
293
293
  this.#pending = null;
294
294
  this.#question = null;
295
295
  this.#progress = null;
296
+
297
+ const draft = this.#messages[this.#messages.length - 1];
298
+ if (draft?.role === "assistant" && !Transcript.carries(draft)) this.#messages = this.#messages.slice(0, -1);
296
299
  this.#notify();
297
300
  }
298
301
  }
@@ -28,7 +28,7 @@ export class Transcript {
28
28
  if (results.length) kept.push({ ...message, toolResults: results });
29
29
  continue;
30
30
  }
31
- if (message.role === "assistant" && !Transcript.#carries(message)) continue;
31
+ if (message.role === "assistant" && !Transcript.carries(message)) continue;
32
32
  kept.push(message);
33
33
  const calls = message.toolCalls ?? [];
34
34
  for (const call of calls) called.add(call.id);
@@ -44,7 +44,7 @@ export class Transcript {
44
44
  }
45
45
 
46
46
  /** An empty assistant message is a draft a reload or an abort caught before it said anything. */
47
- static #carries(message: ChatMessage) {
47
+ static carries(message: ChatMessage) {
48
48
  return !!message.text || !!message.error || !!message.toolCalls?.length || !!message.attachments?.length;
49
49
  }
50
50
  }