claude-code-rust 0.9.0 → 0.10.0

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.
@@ -1,13 +1,14 @@
1
1
  import { asRecordOrNull } from "./shared.js";
2
2
  import { toPermissionMode, buildModeState } from "./commands.js";
3
- import { writeEvent, emitSessionUpdate, emitConnectEvent, refreshSessionsList } from "./events.js";
3
+ import { writeEvent, emitSessionUpdate, emitConnectEvent, emitSessionReplacedEvent, } from "./events.js";
4
4
  import { TOOL_RESULT_TYPES, unwrapToolUseResult } from "./tooling.js";
5
- import { emitToolCall, emitPlanIfTodoWrite, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, taskProgressText, } from "./tool_calls.js";
5
+ import { emitToolCall, emitToolCallUpdate, emitPlanIfTodoWrite, emitToolResultUpdate, finalizeOpenToolCalls, emitToolProgressUpdate, emitToolSummaryUpdate, ensureToolCallVisible, resolveTaskToolUseId, taskProgressText, } from "./tool_calls.js";
6
6
  import { emitAuthRequired, classifyTurnErrorKind, emitFastModeUpdateIfChanged } from "./error_classification.js";
7
7
  import { mapAvailableAgentsFromNames, emitAvailableAgentsIfChanged, refreshAvailableAgents } from "./agents.js";
8
8
  import { buildRateLimitUpdate, numberField } from "./state_parsing.js";
9
9
  import { looksLikeAuthRequired } from "./auth.js";
10
10
  import { updateSessionId } from "./session_lifecycle.js";
11
+ import { bridgeLogger, LOG_TARGETS } from "./logger.js";
11
12
  export function textFromPrompt(command) {
12
13
  const chunks = command.chunks ?? [];
13
14
  return chunks
@@ -20,6 +21,77 @@ export function textFromPrompt(command) {
20
21
  .filter((part) => part.length > 0)
21
22
  .join("");
22
23
  }
24
+ /** MIME types supported by the Anthropic Vision API.
25
+ * NOTE: Keep in sync with `SUPPORTED_IMAGE_MIME_TYPES` in
26
+ * `src/app/clipboard_image.rs`. */
27
+ const SUPPORTED_IMAGE_MIME_TYPES = new Set([
28
+ "image/png",
29
+ "image/jpeg",
30
+ "image/gif",
31
+ "image/webp",
32
+ ]);
33
+ /** Fast check that a string looks like valid base64 (non-empty, correct charset & padding). */
34
+ function isValidBase64(data) {
35
+ if (!data)
36
+ return false;
37
+ const clean = data.replace(/\s/g, "");
38
+ if (clean.length % 4 !== 0)
39
+ return false;
40
+ // Padding ('=') must only appear at the end and be at most 2 characters.
41
+ return /^[A-Za-z0-9+/]+={0,2}$/.test(clean);
42
+ }
43
+ /**
44
+ * Build a content array from prompt chunks, supporting both text and image blocks.
45
+ * Returns the Anthropic API content block format expected by MessageParam.
46
+ */
47
+ export function contentFromPrompt(command) {
48
+ const chunks = command.chunks ?? [];
49
+ const content = [];
50
+ for (const chunk of chunks) {
51
+ if (chunk.kind === "text") {
52
+ const text = typeof chunk.value === "string" ? chunk.value : "";
53
+ if (text.trim()) {
54
+ content.push({ type: "text", text });
55
+ }
56
+ }
57
+ else if (chunk.kind === "image") {
58
+ const val = chunk.value && typeof chunk.value === "object" ? chunk.value : null;
59
+ if (!val)
60
+ continue;
61
+ const data = typeof val.data === "string" ? val.data : "";
62
+ const mimeType = typeof val.mime_type === "string" ? val.mime_type : "image/png";
63
+ if (!SUPPORTED_IMAGE_MIME_TYPES.has(mimeType)) {
64
+ bridgeLogger.warn({
65
+ target: LOG_TARGETS.BRIDGE_PROTOCOL,
66
+ eventName: "prompt_image_skipped",
67
+ message: "skipping unsupported prompt image type",
68
+ outcome: "skipped",
69
+ fields: { mime_type: mimeType },
70
+ });
71
+ continue;
72
+ }
73
+ if (!isValidBase64(data)) {
74
+ bridgeLogger.warn({
75
+ target: LOG_TARGETS.BRIDGE_PROTOCOL,
76
+ eventName: "prompt_image_skipped",
77
+ message: "skipping prompt image with invalid base64 data",
78
+ outcome: "skipped",
79
+ fields: { mime_type: mimeType, reason: "invalid_base64" },
80
+ });
81
+ continue;
82
+ }
83
+ content.push({
84
+ type: "image",
85
+ source: {
86
+ type: "base64",
87
+ media_type: mimeType,
88
+ data,
89
+ },
90
+ });
91
+ }
92
+ }
93
+ return content;
94
+ }
23
95
  export function handleTaskSystemMessage(session, subtype, msg) {
24
96
  if (subtype !== "task_started" && subtype !== "task_progress" && subtype !== "task_notification") {
25
97
  return;
@@ -35,28 +107,18 @@ export function handleTaskSystemMessage(session, subtype, msg) {
35
107
  }
36
108
  const toolCall = ensureToolCallVisible(session, toolUseId, "Agent", {});
37
109
  if (toolCall.status === "pending") {
38
- toolCall.status = "in_progress";
39
- emitSessionUpdate(session.sessionId, {
40
- type: "tool_call_update",
41
- tool_call_update: { tool_call_id: toolUseId, fields: { status: "in_progress" } },
42
- });
110
+ emitToolCallUpdate(session, toolUseId, { status: "in_progress" }, "progress");
43
111
  }
44
112
  if (subtype === "task_started") {
45
113
  const description = typeof msg.description === "string" ? msg.description : "";
46
114
  if (!description) {
47
115
  return;
48
116
  }
49
- emitSessionUpdate(session.sessionId, {
50
- type: "tool_call_update",
51
- tool_call_update: {
52
- tool_call_id: toolUseId,
53
- fields: {
54
- status: "in_progress",
55
- raw_output: description,
56
- content: [{ type: "content", content: { type: "text", text: description } }],
57
- },
58
- },
59
- });
117
+ emitToolCallUpdate(session, toolUseId, {
118
+ status: "in_progress",
119
+ raw_output: description,
120
+ content: [{ type: "content", content: { type: "text", text: description } }],
121
+ }, "task_started");
60
122
  return;
61
123
  }
62
124
  if (subtype === "task_progress") {
@@ -64,17 +126,11 @@ export function handleTaskSystemMessage(session, subtype, msg) {
64
126
  if (!progress) {
65
127
  return;
66
128
  }
67
- emitSessionUpdate(session.sessionId, {
68
- type: "tool_call_update",
69
- tool_call_update: {
70
- tool_call_id: toolUseId,
71
- fields: {
72
- status: "in_progress",
73
- raw_output: progress,
74
- content: [{ type: "content", content: { type: "text", text: progress } }],
75
- },
76
- },
77
- });
129
+ emitToolCallUpdate(session, toolUseId, {
130
+ status: "in_progress",
131
+ raw_output: progress,
132
+ content: [{ type: "content", content: { type: "text", text: progress } }],
133
+ }, "task_progress");
78
134
  return;
79
135
  }
80
136
  const status = typeof msg.status === "string" ? msg.status : "";
@@ -85,11 +141,7 @@ export function handleTaskSystemMessage(session, subtype, msg) {
85
141
  fields.raw_output = summary;
86
142
  fields.content = [{ type: "content", content: { type: "text", text: summary } }];
87
143
  }
88
- emitSessionUpdate(session.sessionId, {
89
- type: "tool_call_update",
90
- tool_call_update: { tool_call_id: toolUseId, fields },
91
- });
92
- toolCall.status = finalStatus;
144
+ emitToolCallUpdate(session, toolUseId, fields, "task_notification");
93
145
  if (taskId) {
94
146
  session.taskToolUseIds.delete(taskId);
95
147
  }
@@ -257,20 +309,7 @@ export function handleSdkMessage(session, message) {
257
309
  emitConnectEvent(session);
258
310
  }
259
311
  else if (previousSessionId !== session.sessionId) {
260
- const historyUpdates = session.resumeUpdates;
261
- writeEvent({
262
- event: "session_replaced",
263
- session_id: session.sessionId,
264
- cwd: session.cwd,
265
- model_name: session.model,
266
- available_models: session.availableModels,
267
- mode: session.mode ? buildModeState(session.mode) : null,
268
- ...(historyUpdates && historyUpdates.length > 0
269
- ? { history_updates: historyUpdates }
270
- : {}),
271
- });
272
- session.resumeUpdates = undefined;
273
- refreshSessionsList();
312
+ emitSessionReplacedEvent(session);
274
313
  }
275
314
  else {
276
315
  if (session.model !== previousModelName) {