Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@alphaxiv/agents",
"version": "0.6.13",
"version": "0.6.14",
"license": "MIT",
"fmt": {
"lineWidth": 120
Expand Down
7 changes: 3 additions & 4 deletions src/adapters/anthropic/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import type Anthropic from "@anthropic-ai/sdk";
import { isStructuredOutputRetryFeedback } from "../../constants.ts";
import { normalizeToolName } from "../../tool.ts";
import type { ChatItem } from "../../types.ts";
import { IMAGE_MIME_TYPES, isTextLikeMimeType } from "../shared/media.ts";
import { ensureToolInputObject } from "../shared/tools.ts";
import type { AnthropicToolMap } from "./utils.ts";

const supportedImageMimeTypes = ["image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp"];

// TODO: drop signature after 10 minutes or whatever
// Mapping between thinking response and signature since signature is meaningless cross-provider and we technically only need to include thinking for the one step
const signatureMap = new Map<string, string>();
Expand Down Expand Up @@ -135,7 +134,7 @@ export async function getAnthropicHistory(options: {
case "input_file":
case "tool_result_file": {
const pushBuffer = historyItem.type === "input_file" ? anthropicHistory : anthropicToolFileBuffer;
if (supportedImageMimeTypes.includes(historyItem.kind)) {
if ((IMAGE_MIME_TYPES as readonly string[]).includes(historyItem.kind)) {
pushBuffer.push({
role: "user",
content: [{
Expand All @@ -159,7 +158,7 @@ export async function getAnthropicHistory(options: {
},
],
});
} else if (historyItem.kind.startsWith("text/")) {
} else if (isTextLikeMimeType(historyItem.kind)) {
const req = await fetch(historyItem.content, { signal: options.signal });
const text = await req.text();

Expand Down
35 changes: 34 additions & 1 deletion tests/adapters/anthropic.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type Anthropic from "@anthropic-ai/sdk";
import { assert, assertEquals } from "@std/assert";
import { assert, assertEquals, assertRejects } from "@std/assert";
import z from "zod";
import { addStreamItem, Agent, type AnyTool, type ChatItem, type StreamItem, Tool } from "../../mod.ts";
import { anthropicModel } from "../../src/adapters/anthropic/adapter.ts";
Expand Down Expand Up @@ -1178,3 +1178,36 @@ Deno.test("Agent cache option reaches the Anthropic adapter", async () => {
assertEquals(tailCacheControl(await runAgent(undefined)), { type: "ephemeral", ttl: undefined });
assertEquals(tailCacheControl(await runAgent(false)), undefined);
});

Deno.test("textlike application/* files are inlined instead of rejected", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = (() => Promise.resolve(new Response('{"cats": 2}'))) as typeof fetch;

try {
const history = await getAnthropicHistory({
history: [{ type: "input_file", kind: "application/json", content: "https://example.com/cats.json" }],
normalizedTools: [],
signal: AbortSignal.abort(),
});

assertEquals(history, [{
role: "user",
content: [{ type: "text", text: '<ant-file>{"cats": 2}</ant-file>' }],
}]);
} finally {
globalThis.fetch = originalFetch;
}
});

Deno.test("non-textlike media types are still rejected", async () => {
await assertRejects(
() =>
getAnthropicHistory({
history: [{ type: "input_file", kind: "video/mp2t", content: "https://example.com/cats.ts" }],
normalizedTools: [],
signal: AbortSignal.abort(),
}),
Error,
"video/mp2t",
);
});
Loading