Skip to content
40 changes: 40 additions & 0 deletions js/plugins/anthropic/src/parts/input_json_part.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { InputJSONDelta } from '@anthropic-ai/sdk/resources';
import {
createAbility,
SupportedPart,
SupportedPartWhat,
SupportedPartWhen,
} from './part';

const ID_DELTA = 'input_json_delta';

export const InputJsonPart: SupportedPart = {
abilities: [
createAbility<InputJSONDelta>({
id: [ID_DELTA],
when: [SupportedPartWhen.StreamDelta],
what: [SupportedPartWhat.ContentBlock],
func: (_when, _what, _delta) => {
throw new Error(
`Anthropic streaming tool input (${ID_DELTA}) is not yet supported. Please disable streaming or upgrade this plugin.`
);
},
}),
],
};
105 changes: 105 additions & 0 deletions js/plugins/anthropic/src/parts/part.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import {
ContentBlock,
RawContentBlockDelta,
} from '@anthropic-ai/sdk/resources';
import {
BetaContentBlock,
BetaRawContentBlockDelta,
BetaRawMessageStreamEvent,
} from '@anthropic-ai/sdk/resources/beta.js';
import { MessageStreamEvent } from '@anthropic-ai/sdk/resources/messages.js';
import type { Part } from 'genkit';

export interface SupportedPart {
abilities: Ability[];
}

export interface Ability {
id: string[];
when: (typeof SupportedPartWhen)[keyof typeof SupportedPartWhen][];
what: (typeof SupportedPartWhat)[keyof typeof SupportedPartWhat][];
func: (
when: (typeof SupportedPartWhen)[keyof typeof SupportedPartWhen],
what: (typeof SupportedPartWhat)[keyof typeof SupportedPartWhat],
chunk:
| MessageStreamEvent
| BetaRawMessageStreamEvent
| ContentBlock
| BetaContentBlock
| RawContentBlockDelta
| BetaRawContentBlockDelta
) => Part;
}

export const SupportedPartWhen = {
StreamStart: 'stream_start' as const,
StreamDelta: 'stream_delta' as const,
StreamEnd: 'stream_end' as const,
NonStream: 'non_stream' as const,
};

export const SupportedPartWhat = {
ContentBlock: 'content_block' as const,
};

export function throwErrorWrongTypeForAbility(
partId: string,
when: (typeof SupportedPartWhen)[keyof typeof SupportedPartWhen],
what: (typeof SupportedPartWhat)[keyof typeof SupportedPartWhat]
): never {
throw new Error(
`Part '${partId}' is not supported for ${String(when)
.replace(/([A-Z])/g, ' $1')
.toLowerCase()} in ${what}`
);
}

function validatePartType<T extends { type: string }>(
expectedTypes: string | string[],
chunk: unknown,
when: (typeof SupportedPartWhen)[keyof typeof SupportedPartWhen],
what: (typeof SupportedPartWhat)[keyof typeof SupportedPartWhat]
): asserts chunk is T {
const types = Array.isArray(expectedTypes) ? expectedTypes : [expectedTypes];
const chunkWithType = chunk as { type: string };
if (!types.includes(chunkWithType.type)) {
throwErrorWrongTypeForAbility(types[0], when, what);
}
}

export function createAbility<T extends { type: string }>(config: {
id: string[];
when: (typeof SupportedPartWhen)[keyof typeof SupportedPartWhen][];
what: (typeof SupportedPartWhat)[keyof typeof SupportedPartWhat][];
func: (
when: (typeof SupportedPartWhen)[keyof typeof SupportedPartWhen],
what: (typeof SupportedPartWhat)[keyof typeof SupportedPartWhat],
chunk: T
) => Part;
}): Ability {
return {
id: config.id,
when: config.when,
what: config.what,
func: (when, what, chunk) => {
validatePartType<T>(config.id, chunk, when, what);
return config.func(when, what, chunk);
},
};
}
38 changes: 38 additions & 0 deletions js/plugins/anthropic/src/parts/redacted_thinking_part.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { RedactedThinkingBlock } from '@anthropic-ai/sdk/resources';
import {
createAbility,
SupportedPart,
SupportedPartWhat,
SupportedPartWhen,
} from './part';

const ID = 'redacted_thinking';

export const RedactedThinkingPart: SupportedPart = {
abilities: [
createAbility<RedactedThinkingBlock>({
id: [ID],
when: [SupportedPartWhen.NonStream, SupportedPartWhen.StreamStart],
what: [SupportedPartWhat.ContentBlock],
func: (_when, _what, contentBlock) => {
return { custom: { redactedThinking: contentBlock.data } };
},
}),
],
};
53 changes: 53 additions & 0 deletions js/plugins/anthropic/src/parts/server_tool_use_part.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ServerToolUseBlock } from '@anthropic-ai/sdk/resources';
import {
createAbility,
SupportedPart,
SupportedPartWhat,
SupportedPartWhen,
} from './part';

const ID = 'server_tool_use';

export const ServerToolUsePart: SupportedPart = {
abilities: [
createAbility<ServerToolUseBlock>({
id: [ID],
when: [SupportedPartWhen.NonStream, SupportedPartWhen.StreamStart],
what: [SupportedPartWhat.ContentBlock],
func: (_when, _what, contentBlock) => {
const baseName = contentBlock.name ?? 'unknown_tool';
const serverToolName =
'server_name' in contentBlock && contentBlock.server_name
? `${contentBlock.server_name}/${baseName}`
: baseName;

return {
text: `[Anthropic server tool ${serverToolName}] input: ${JSON.stringify(contentBlock.input)}`,
custom: {
anthropicServerToolUse: {
id: contentBlock.id,
name: serverToolName,
input: contentBlock.input,
},
},
};
},
}),
],
};
43 changes: 43 additions & 0 deletions js/plugins/anthropic/src/parts/text_part.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { TextBlock, TextDelta } from '@anthropic-ai/sdk/resources';
import {
SupportedPart,
SupportedPartWhat,
SupportedPartWhen,
createAbility,
} from './part';

const ID = 'text';
const ID_DELTA = 'text_delta';

export const TextPart: SupportedPart = {
abilities: [
createAbility<TextBlock | TextDelta>({
id: [ID, ID_DELTA],
when: [
SupportedPartWhen.NonStream,
SupportedPartWhen.StreamDelta,
SupportedPartWhen.StreamStart,
],
what: [SupportedPartWhat.ContentBlock],
func: (_when, _what, content) => {
return { text: content.text };
},
}),
],
};
70 changes: 70 additions & 0 deletions js/plugins/anthropic/src/parts/thinking_part.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ThinkingBlock, ThinkingDelta } from '@anthropic-ai/sdk/resources';
import { Part } from 'genkit';
import { ANTHROPIC_THINKING_CUSTOM_KEY } from '../runner/base';
import {
SupportedPart,
SupportedPartWhat,
SupportedPartWhen,
createAbility,
} from './part';

const ID = 'thinking';
const ID_DELTA = 'thinking_delta';

export const ThinkingPart: SupportedPart = {
abilities: [
createAbility<ThinkingBlock>({
id: [ID],
when: [SupportedPartWhen.NonStream, SupportedPartWhen.StreamStart],
what: [SupportedPartWhat.ContentBlock],
func: (_when, _what, contentBlock) => {
return createThinkingPart(
contentBlock.thinking,
contentBlock.signature
);
},
}),

createAbility<ThinkingDelta>({
id: [ID_DELTA],
when: [SupportedPartWhen.StreamDelta],
what: [SupportedPartWhat.ContentBlock],
func: (_when, _what, delta) => {
return { reasoning: delta.thinking };
},
}),
],
};

function createThinkingPart(thinking: string, signature?: string): Part {
const custom =
signature !== undefined
? {
[ANTHROPIC_THINKING_CUSTOM_KEY]: { signature },
}
: undefined;
return custom
? {
reasoning: thinking,
custom,
}
: {
reasoning: thinking,
};
}
44 changes: 44 additions & 0 deletions js/plugins/anthropic/src/parts/tool_use_part.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { ToolUseBlock } from '@anthropic-ai/sdk/resources';
import {
createAbility,
SupportedPart,
SupportedPartWhat,
SupportedPartWhen,
} from './part';

const ID = 'tool_use';

export const ToolUsePart: SupportedPart = {
abilities: [
createAbility<ToolUseBlock>({
id: [ID],
when: [SupportedPartWhen.NonStream, SupportedPartWhen.StreamStart],
what: [SupportedPartWhat.ContentBlock],
func: (_when, _what, contentBlock) => {
return {
toolRequest: {
ref: contentBlock.id,
name: contentBlock.name ?? 'unknown_tool',
input: contentBlock.input,
},
};
},
}),
],
};
Loading