copillm 0.4.1 → 0.4.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.
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  const FALLBACK_PACKAGE_INFO = {
3
3
  name: "copillm",
4
- version: "0.4.1"
4
+ version: "0.4.2"
5
5
  };
6
6
  export function getPackageInfo() {
7
7
  const envName = cleanPackageValue(process.env.COPILLM_PACKAGE_NAME);
@@ -1,6 +1,6 @@
1
1
  import { resolveModelId } from "../../models/discovery.js";
2
2
  import { CopilotTokenManagerError } from "../../auth/copilotToken.js";
3
- import { anthropicToOpenAI } from "../../translation/openaiAnthropic.js";
3
+ import { anthropicToOpenAI, ProtocolTranslationError } from "../../translation/openaiAnthropic.js";
4
4
  import { writeAnthropicPrelude } from "../../translation/streamingOpenAIToAnthropic.js";
5
5
  import { isBenignSocketError, safeEnd, safeSendJson } from "../requestLifecycle.js";
6
6
  import { InvalidRequestShapeError, writeAnthropicSseError } from "../errors.js";
@@ -15,6 +15,9 @@ function translateRequestBody(routeKind, body) {
15
15
  return anthropicToOpenAI(body);
16
16
  }
17
17
  catch (error) {
18
+ if (error instanceof ProtocolTranslationError) {
19
+ throw error;
20
+ }
18
21
  if (error instanceof Error) {
19
22
  throw new InvalidRequestShapeError(error.message);
20
23
  }
@@ -297,14 +297,68 @@ function translateImageSource(source) {
297
297
  }
298
298
  throw new ProtocolTranslationError("unsupported_image_source", `Unsupported Anthropic image source type: ${String(source.type)}.`);
299
299
  }
300
+ /**
301
+ * Translate Anthropic `tool_result.content` into the string that goes into the
302
+ * OpenAI `tool` message's `content` field.
303
+ *
304
+ * Anthropic permits a `tool_result.content` array to contain `text` blocks
305
+ * *and* `image` blocks (and tolerates unknown future block types), but the
306
+ * OpenAI chat-completions `tool` role only accepts string content. Rather
307
+ * than reject the whole request — which historically surfaced to Claude Code
308
+ * as the cryptic `invalid_request_shape: Anthropic tool_result content only
309
+ * supports text blocks.` 400 whenever a tool (e.g. a screenshot MCP, an image
310
+ * read) returned an image — we degrade gracefully:
311
+ *
312
+ * - text blocks → extracted verbatim
313
+ * - image blocks → a `[image: <media_type_or_url>]` placeholder so the
314
+ * model at least sees that the tool returned an image
315
+ * - other types → a `[unsupported tool_result content block: type=X]`
316
+ * placeholder
317
+ *
318
+ * Full image pass-through (forwarding the image bytes to the model as a
319
+ * follow-up multi-part user message) is a deliberate non-goal for this
320
+ * function — it would change the request structure and the upstream contract
321
+ * for `tool` messages. We can revisit if/when there is a confirmed need.
322
+ *
323
+ * Malformed `text` blocks (missing `.text`) still throw — that is a real
324
+ * client bug, not a content shape we should silently invent text for.
325
+ */
300
326
  function translateToolResultContent(content) {
301
327
  if (typeof content === "string") {
302
328
  return content;
303
329
  }
304
330
  if (!Array.isArray(content)) {
305
- throw new ProtocolTranslationError("invalid_tool_result_content", "Anthropic tool_result content must be string or text blocks.");
331
+ throw new ProtocolTranslationError("invalid_tool_result_content", "Anthropic tool_result content must be a string or an array of content blocks.");
332
+ }
333
+ return content.map(stringifyToolResultContentBlock).join("\n");
334
+ }
335
+ function stringifyToolResultContentBlock(block) {
336
+ if (!isObject(block) || typeof block.type !== "string") {
337
+ throw new ProtocolTranslationError("invalid_tool_result_content", "Anthropic tool_result content blocks must be objects with a string type.");
338
+ }
339
+ if (block.type === "text") {
340
+ if (typeof block.text !== "string") {
341
+ throw new ProtocolTranslationError("invalid_text_block", "Anthropic text block must include string text.");
342
+ }
343
+ return block.text;
344
+ }
345
+ if (block.type === "image") {
346
+ const descriptor = describeImageSource(block.source);
347
+ return descriptor ? `[image: ${descriptor}]` : "[image]";
348
+ }
349
+ return `[unsupported tool_result content block: type=${block.type}]`;
350
+ }
351
+ function describeImageSource(source) {
352
+ if (!isObject(source) || typeof source.type !== "string") {
353
+ return null;
354
+ }
355
+ if (source.type === "base64" && typeof source.media_type === "string" && source.media_type.length > 0) {
356
+ return source.media_type;
357
+ }
358
+ if (source.type === "url" && typeof source.url === "string" && source.url.length > 0) {
359
+ return source.url;
306
360
  }
307
- return joinTextBlocks(content, "Anthropic tool_result content");
361
+ return null;
308
362
  }
309
363
  function translateToolDefinition(tool) {
310
364
  if (!isObject(tool) || typeof tool.name !== "string" || tool.name.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copillm",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Local Copilot proxy CLI (OpenAI/Anthropic-compatible)",
5
5
  "license": "MIT",
6
6
  "type": "module",