| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- var _BetaToolRunner_instances, _BetaToolRunner_consumed, _BetaToolRunner_mutated, _BetaToolRunner_state, _BetaToolRunner_options, _BetaToolRunner_message, _BetaToolRunner_toolResponse, _BetaToolRunner_completion, _BetaToolRunner_iterationCount, _BetaToolRunner_checkAndCompact, _BetaToolRunner_generateToolResponse;
- import { __classPrivateFieldGet, __classPrivateFieldSet } from "../../internal/tslib.mjs";
- import { ToolError } from "./ToolError.mjs";
- import { AnthropicError } from "../../core/error.mjs";
- import { buildHeaders } from "../../internal/headers.mjs";
- import { DEFAULT_SUMMARY_PROMPT, DEFAULT_TOKEN_THRESHOLD } from "./CompactionControl.mjs";
- import { collectStainlessHelpers } from "../stainless-helper-header.mjs";
- /**
- * Just Promise.withResolvers(), which is not available in all environments.
- */
- function promiseWithResolvers() {
- let resolve;
- let reject;
- const promise = new Promise((res, rej) => {
- resolve = res;
- reject = rej;
- });
- return { promise, resolve: resolve, reject: reject };
- }
- /**
- * A ToolRunner handles the automatic conversation loop between the assistant and tools.
- *
- * A ToolRunner is an async iterable that yields either BetaMessage or BetaMessageStream objects
- * depending on the streaming configuration.
- */
- export class BetaToolRunner {
- constructor(client, params, options) {
- _BetaToolRunner_instances.add(this);
- this.client = client;
- /** Whether the async iterator has been consumed */
- _BetaToolRunner_consumed.set(this, false);
- /** Whether parameters have been mutated since the last API call */
- _BetaToolRunner_mutated.set(this, false);
- /** Current state containing the request parameters */
- _BetaToolRunner_state.set(this, void 0);
- _BetaToolRunner_options.set(this, void 0);
- /** Promise for the last message received from the assistant */
- _BetaToolRunner_message.set(this, void 0);
- /** Cached tool response to avoid redundant executions */
- _BetaToolRunner_toolResponse.set(this, void 0);
- /** Promise resolvers for waiting on completion */
- _BetaToolRunner_completion.set(this, void 0);
- /** Number of iterations (API requests) made so far */
- _BetaToolRunner_iterationCount.set(this, 0);
- __classPrivateFieldSet(this, _BetaToolRunner_state, {
- params: {
- // You can't clone the entire params since there are functions as handlers.
- // You also don't really need to clone params.messages, but it probably will prevent a foot gun
- // somewhere.
- ...params,
- messages: structuredClone(params.messages),
- },
- }, "f");
- const helpers = collectStainlessHelpers(params.tools, params.messages);
- const helperValue = ['BetaToolRunner', ...helpers].join(', ');
- __classPrivateFieldSet(this, _BetaToolRunner_options, {
- ...options,
- headers: buildHeaders([{ 'x-stainless-helper': helperValue }, options?.headers]),
- }, "f");
- __classPrivateFieldSet(this, _BetaToolRunner_completion, promiseWithResolvers(), "f");
- }
- async *[(_BetaToolRunner_consumed = new WeakMap(), _BetaToolRunner_mutated = new WeakMap(), _BetaToolRunner_state = new WeakMap(), _BetaToolRunner_options = new WeakMap(), _BetaToolRunner_message = new WeakMap(), _BetaToolRunner_toolResponse = new WeakMap(), _BetaToolRunner_completion = new WeakMap(), _BetaToolRunner_iterationCount = new WeakMap(), _BetaToolRunner_instances = new WeakSet(), _BetaToolRunner_checkAndCompact = async function _BetaToolRunner_checkAndCompact() {
- const compactionControl = __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.compactionControl;
- if (!compactionControl || !compactionControl.enabled) {
- return false;
- }
- let tokensUsed = 0;
- if (__classPrivateFieldGet(this, _BetaToolRunner_message, "f") !== undefined) {
- try {
- const message = await __classPrivateFieldGet(this, _BetaToolRunner_message, "f");
- const totalInputTokens = message.usage.input_tokens +
- (message.usage.cache_creation_input_tokens ?? 0) +
- (message.usage.cache_read_input_tokens ?? 0);
- tokensUsed = totalInputTokens + message.usage.output_tokens;
- }
- catch {
- // If we can't get the message, skip compaction
- return false;
- }
- }
- const threshold = compactionControl.contextTokenThreshold ?? DEFAULT_TOKEN_THRESHOLD;
- if (tokensUsed < threshold) {
- return false;
- }
- const model = compactionControl.model ?? __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.model;
- const summaryPrompt = compactionControl.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT;
- const messages = __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages;
- if (messages[messages.length - 1].role === 'assistant') {
- // Remove tool_use blocks from the last message to avoid 400 error
- // (tool_use requires tool_result, which we don't have yet)
- const lastMessage = messages[messages.length - 1];
- if (Array.isArray(lastMessage.content)) {
- const nonToolBlocks = lastMessage.content.filter((block) => block.type !== 'tool_use');
- if (nonToolBlocks.length === 0) {
- // If all blocks were tool_use, just remove the message entirely
- messages.pop();
- }
- else {
- lastMessage.content = nonToolBlocks;
- }
- }
- }
- const response = await this.client.beta.messages.create({
- model,
- messages: [
- ...messages,
- {
- role: 'user',
- content: [
- {
- type: 'text',
- text: summaryPrompt,
- },
- ],
- },
- ],
- max_tokens: __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.max_tokens,
- }, {
- headers: { 'x-stainless-helper': 'compaction' },
- });
- if (response.content[0]?.type !== 'text') {
- throw new AnthropicError('Expected text response for compaction');
- }
- __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages = [
- {
- role: 'user',
- content: response.content,
- },
- ];
- return true;
- }, Symbol.asyncIterator)]() {
- var _a;
- if (__classPrivateFieldGet(this, _BetaToolRunner_consumed, "f")) {
- throw new AnthropicError('Cannot iterate over a consumed stream');
- }
- __classPrivateFieldSet(this, _BetaToolRunner_consumed, true, "f");
- __classPrivateFieldSet(this, _BetaToolRunner_mutated, true, "f");
- __classPrivateFieldSet(this, _BetaToolRunner_toolResponse, undefined, "f");
- try {
- while (true) {
- let stream;
- try {
- if (__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.max_iterations &&
- __classPrivateFieldGet(this, _BetaToolRunner_iterationCount, "f") >= __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.max_iterations) {
- break;
- }
- __classPrivateFieldSet(this, _BetaToolRunner_mutated, false, "f");
- __classPrivateFieldSet(this, _BetaToolRunner_toolResponse, undefined, "f");
- __classPrivateFieldSet(this, _BetaToolRunner_iterationCount, (_a = __classPrivateFieldGet(this, _BetaToolRunner_iterationCount, "f"), _a++, _a), "f");
- __classPrivateFieldSet(this, _BetaToolRunner_message, undefined, "f");
- const { max_iterations, compactionControl, ...params } = __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params;
- if (params.stream) {
- stream = this.client.beta.messages.stream({ ...params }, __classPrivateFieldGet(this, _BetaToolRunner_options, "f"));
- __classPrivateFieldSet(this, _BetaToolRunner_message, stream.finalMessage(), "f");
- // Make sure that this promise doesn't throw before we get the option to do something about it.
- // Error will be caught when we call await this.#message ultimately
- __classPrivateFieldGet(this, _BetaToolRunner_message, "f").catch(() => { });
- yield stream;
- }
- else {
- __classPrivateFieldSet(this, _BetaToolRunner_message, this.client.beta.messages.create({ ...params, stream: false }, __classPrivateFieldGet(this, _BetaToolRunner_options, "f")), "f");
- yield __classPrivateFieldGet(this, _BetaToolRunner_message, "f");
- }
- const isCompacted = await __classPrivateFieldGet(this, _BetaToolRunner_instances, "m", _BetaToolRunner_checkAndCompact).call(this);
- if (!isCompacted) {
- if (!__classPrivateFieldGet(this, _BetaToolRunner_mutated, "f")) {
- const { role, content } = await __classPrivateFieldGet(this, _BetaToolRunner_message, "f");
- __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages.push({ role, content });
- }
- const toolMessage = await __classPrivateFieldGet(this, _BetaToolRunner_instances, "m", _BetaToolRunner_generateToolResponse).call(this, __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages.at(-1));
- if (toolMessage) {
- __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages.push(toolMessage);
- }
- else if (!__classPrivateFieldGet(this, _BetaToolRunner_mutated, "f")) {
- break;
- }
- }
- }
- finally {
- if (stream) {
- stream.abort();
- }
- }
- }
- if (!__classPrivateFieldGet(this, _BetaToolRunner_message, "f")) {
- throw new AnthropicError('ToolRunner concluded without a message from the server');
- }
- __classPrivateFieldGet(this, _BetaToolRunner_completion, "f").resolve(await __classPrivateFieldGet(this, _BetaToolRunner_message, "f"));
- }
- catch (error) {
- __classPrivateFieldSet(this, _BetaToolRunner_consumed, false, "f");
- // Silence unhandled promise errors
- __classPrivateFieldGet(this, _BetaToolRunner_completion, "f").promise.catch(() => { });
- __classPrivateFieldGet(this, _BetaToolRunner_completion, "f").reject(error);
- __classPrivateFieldSet(this, _BetaToolRunner_completion, promiseWithResolvers(), "f");
- throw error;
- }
- }
- setMessagesParams(paramsOrMutator) {
- if (typeof paramsOrMutator === 'function') {
- __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params = paramsOrMutator(__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params);
- }
- else {
- __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params = paramsOrMutator;
- }
- __classPrivateFieldSet(this, _BetaToolRunner_mutated, true, "f");
- // Invalidate cached tool response since parameters changed
- __classPrivateFieldSet(this, _BetaToolRunner_toolResponse, undefined, "f");
- }
- /**
- * Get the tool response for the last message from the assistant.
- * Avoids redundant tool executions by caching results.
- *
- * @returns A promise that resolves to a BetaMessageParam containing tool results, or null if no tools need to be executed
- *
- * @example
- * const toolResponse = await runner.generateToolResponse();
- * if (toolResponse) {
- * console.log('Tool results:', toolResponse.content);
- * }
- */
- async generateToolResponse() {
- const message = (await __classPrivateFieldGet(this, _BetaToolRunner_message, "f")) ?? this.params.messages.at(-1);
- if (!message) {
- return null;
- }
- return __classPrivateFieldGet(this, _BetaToolRunner_instances, "m", _BetaToolRunner_generateToolResponse).call(this, message);
- }
- /**
- * Wait for the async iterator to complete. This works even if the async iterator hasn't yet started, and
- * will wait for an instance to start and go to completion.
- *
- * @returns A promise that resolves to the final BetaMessage when the iterator completes
- *
- * @example
- * // Start consuming the iterator
- * for await (const message of runner) {
- * console.log('Message:', message.content);
- * }
- *
- * // Meanwhile, wait for completion from another part of the code
- * const finalMessage = await runner.done();
- * console.log('Final response:', finalMessage.content);
- */
- done() {
- return __classPrivateFieldGet(this, _BetaToolRunner_completion, "f").promise;
- }
- /**
- * Returns a promise indicating that the stream is done. Unlike .done(), this will eagerly read the stream:
- * * If the iterator has not been consumed, consume the entire iterator and return the final message from the
- * assistant.
- * * If the iterator has been consumed, waits for it to complete and returns the final message.
- *
- * @returns A promise that resolves to the final BetaMessage from the conversation
- * @throws {AnthropicError} If no messages were processed during the conversation
- *
- * @example
- * const finalMessage = await runner.runUntilDone();
- * console.log('Final response:', finalMessage.content);
- */
- async runUntilDone() {
- // If not yet consumed, start consuming and wait for completion
- if (!__classPrivateFieldGet(this, _BetaToolRunner_consumed, "f")) {
- for await (const _ of this) {
- // Iterator naturally populates this.#message
- }
- }
- // If consumed but not completed, wait for completion
- return this.done();
- }
- /**
- * Get the current parameters being used by the ToolRunner.
- *
- * @returns A readonly view of the current ToolRunnerParams
- *
- * @example
- * const currentParams = runner.params;
- * console.log('Current model:', currentParams.model);
- * console.log('Message count:', currentParams.messages.length);
- */
- get params() {
- return __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params;
- }
- /**
- * Add one or more messages to the conversation history.
- *
- * @param messages - One or more BetaMessageParam objects to add to the conversation
- *
- * @example
- * runner.pushMessages(
- * { role: 'user', content: 'Also, what about the weather in NYC?' }
- * );
- *
- * @example
- * // Adding multiple messages
- * runner.pushMessages(
- * { role: 'user', content: 'What about NYC?' },
- * { role: 'user', content: 'And Boston?' }
- * );
- */
- pushMessages(...messages) {
- this.setMessagesParams((params) => ({
- ...params,
- messages: [...params.messages, ...messages],
- }));
- }
- /**
- * Makes the ToolRunner directly awaitable, equivalent to calling .runUntilDone()
- * This allows using `await runner` instead of `await runner.runUntilDone()`
- */
- then(onfulfilled, onrejected) {
- return this.runUntilDone().then(onfulfilled, onrejected);
- }
- }
- _BetaToolRunner_generateToolResponse = async function _BetaToolRunner_generateToolResponse(lastMessage) {
- if (__classPrivateFieldGet(this, _BetaToolRunner_toolResponse, "f") !== undefined) {
- return __classPrivateFieldGet(this, _BetaToolRunner_toolResponse, "f");
- }
- __classPrivateFieldSet(this, _BetaToolRunner_toolResponse, generateToolResponse(__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params, lastMessage), "f");
- return __classPrivateFieldGet(this, _BetaToolRunner_toolResponse, "f");
- };
- async function generateToolResponse(params, lastMessage = params.messages.at(-1)) {
- // Only process if the last message is from the assistant and has tool use blocks
- if (!lastMessage ||
- lastMessage.role !== 'assistant' ||
- !lastMessage.content ||
- typeof lastMessage.content === 'string') {
- return null;
- }
- const toolUseBlocks = lastMessage.content.filter((content) => content.type === 'tool_use');
- if (toolUseBlocks.length === 0) {
- return null;
- }
- const toolResults = await Promise.all(toolUseBlocks.map(async (toolUse) => {
- const tool = params.tools.find((t) => ('name' in t ? t.name : t.mcp_server_name) === toolUse.name);
- if (!tool || !('run' in tool)) {
- return {
- type: 'tool_result',
- tool_use_id: toolUse.id,
- content: `Error: Tool '${toolUse.name}' not found`,
- is_error: true,
- };
- }
- try {
- let input = toolUse.input;
- if ('parse' in tool && tool.parse) {
- input = tool.parse(input);
- }
- const result = await tool.run(input);
- return {
- type: 'tool_result',
- tool_use_id: toolUse.id,
- content: result,
- };
- }
- catch (error) {
- return {
- type: 'tool_result',
- tool_use_id: toolUse.id,
- content: error instanceof ToolError ?
- error.content
- : `Error: ${error instanceof Error ? error.message : String(error)}`,
- is_error: true,
- };
- }
- }));
- return {
- role: 'user',
- content: toolResults,
- };
- }
- //# sourceMappingURL=BetaToolRunner.mjs.map
|