{"version":3,"file":"openai-assistant-adapter.cjs","names":["convertMessageToOpenAIMessage","convertSystemMessageToAssistantAPI","convertActionInputToOpenAITool"],"sources":["../../../src/service-adapters/openai/openai-assistant-adapter.ts"],"sourcesContent":["/**\n * Copilot Runtime adapter for the OpenAI Assistant API.\n *\n * ## Example\n *\n * ```ts\n * import { CopilotRuntime, OpenAIAssistantAdapter } from \"@copilotkit/runtime\";\n * import OpenAI from \"openai\";\n *\n * const copilotKit = new CopilotRuntime();\n *\n * const openai = new OpenAI({\n *   organization: \"<your-organization-id>\",\n *   apiKey: \"<your-api-key>\",\n * });\n *\n * return new OpenAIAssistantAdapter({\n *   openai,\n *   assistantId: \"<your-assistant-id>\",\n *   codeInterpreterEnabled: true,\n *   fileSearchEnabled: true,\n * });\n * ```\n */\nimport type OpenAI from \"openai\";\nimport type { RunSubmitToolOutputsStreamParams } from \"openai/resources/beta/threads/runs/runs\";\nimport type { AssistantStream } from \"openai/lib/AssistantStream\";\nimport type {\n  AssistantStreamEvent,\n  AssistantTool,\n} from \"openai/resources/beta/assistants\";\nimport {\n  CopilotServiceAdapter,\n  CopilotRuntimeChatCompletionRequest,\n  CopilotRuntimeChatCompletionResponse,\n} from \"../service-adapter\";\nimport {\n  Message,\n  ResultMessage,\n  TextMessage,\n} from \"../../graphql/types/converted\";\nimport {\n  convertActionInputToOpenAITool,\n  convertMessageToOpenAIMessage,\n  convertSystemMessageToAssistantAPI,\n} from \"./utils\";\nimport { RuntimeEventSource } from \"../events\";\nimport { ActionInput } from \"../../graphql/inputs/action.input\";\nimport { ForwardedParametersInput } from \"../../graphql/inputs/forwarded-parameters.input\";\n\nexport interface OpenAIAssistantAdapterParams {\n  /**\n   * The ID of the assistant to use.\n   */\n  assistantId: string;\n\n  /**\n   * An optional OpenAI instance to use. If not provided, a new instance will be created.\n   */\n  openai?: OpenAI;\n\n  /**\n   * Whether to enable code interpretation.\n   * @default true\n   */\n  codeInterpreterEnabled?: boolean;\n\n  /**\n   * Whether to enable file search.\n   * @default true\n   */\n  fileSearchEnabled?: boolean;\n\n  /**\n   * Whether to disable parallel tool calls.\n   * You can disable parallel tool calls to force the model to execute tool calls sequentially.\n   * This is useful if you want to execute tool calls in a specific order so that the state changes\n   * introduced by one tool call are visible to the next tool call. (i.e. new actions or readables)\n   *\n   * @default false\n   */\n  disableParallelToolCalls?: boolean;\n\n  /**\n   * Whether to keep the role in system messages as \"System\".\n   * By default, it is converted to \"developer\", which is used by newer OpenAI models\n   *\n   * @default false\n   */\n  keepSystemRole?: boolean;\n}\n\nexport class OpenAIAssistantAdapter implements CopilotServiceAdapter {\n  private _openai: OpenAI;\n  private codeInterpreterEnabled: boolean;\n  private assistantId: string;\n  private fileSearchEnabled: boolean;\n  private disableParallelToolCalls: boolean;\n  private keepSystemRole: boolean = false;\n\n  public get name() {\n    return \"OpenAIAssistantAdapter\";\n  }\n\n  constructor(params: OpenAIAssistantAdapterParams) {\n    if (params.openai) {\n      this._openai = params.openai;\n    }\n    // If no instance provided, we'll lazy-load in ensureOpenAI()\n    this.codeInterpreterEnabled =\n      params.codeInterpreterEnabled === false || true;\n    this.fileSearchEnabled = params.fileSearchEnabled === false || true;\n    this.assistantId = params.assistantId;\n    this.disableParallelToolCalls = params?.disableParallelToolCalls || false;\n    this.keepSystemRole = params?.keepSystemRole ?? false;\n  }\n\n  private ensureOpenAI(): OpenAI {\n    if (!this._openai) {\n      // eslint-disable-next-line @typescript-eslint/no-var-requires\n      const OpenAI = require(\"openai\").default;\n      this._openai = new OpenAI({});\n    }\n    return this._openai;\n  }\n\n  async process(\n    request: CopilotRuntimeChatCompletionRequest,\n  ): Promise<CopilotRuntimeChatCompletionResponse> {\n    const { messages, actions, eventSource, runId, forwardedParameters } =\n      request;\n\n    // if we don't have a threadId, create a new thread\n    let threadId = request.extensions?.openaiAssistantAPI?.threadId;\n    const openai = this.ensureOpenAI();\n\n    if (!threadId) {\n      threadId = (await openai.beta.threads.create()).id;\n    }\n\n    const lastMessage = messages.at(-1);\n\n    let nextRunId: string | undefined = undefined;\n\n    // submit function outputs\n    if (lastMessage.isResultMessage() && runId) {\n      nextRunId = await this.submitToolOutputs(\n        threadId,\n        runId,\n        messages,\n        eventSource,\n      );\n    }\n    // submit user message\n    else if (lastMessage.isTextMessage()) {\n      nextRunId = await this.submitUserMessage(\n        threadId,\n        messages,\n        actions,\n        eventSource,\n        forwardedParameters,\n      );\n    }\n    // unsupported message\n    else {\n      throw new Error(\"No actionable message found in the messages\");\n    }\n\n    return {\n      runId: nextRunId,\n      threadId,\n      extensions: {\n        ...request.extensions,\n        openaiAssistantAPI: {\n          threadId: threadId,\n          runId: nextRunId,\n        },\n      },\n    };\n  }\n\n  private async submitToolOutputs(\n    threadId: string,\n    runId: string,\n    messages: Message[],\n    eventSource: RuntimeEventSource,\n  ) {\n    const openai = this.ensureOpenAI();\n    let run = await openai.beta.threads.runs.retrieve(threadId, runId);\n\n    if (!run.required_action) {\n      throw new Error(\"No tool outputs required\");\n    }\n\n    // get the required tool call ids\n    const toolCallsIds = run.required_action.submit_tool_outputs.tool_calls.map(\n      (toolCall) => toolCall.id,\n    );\n\n    // search for these tool calls\n    const resultMessages = messages.filter(\n      (message) =>\n        message.isResultMessage() &&\n        toolCallsIds.includes(message.actionExecutionId),\n    ) as ResultMessage[];\n\n    if (toolCallsIds.length != resultMessages.length) {\n      throw new Error(\n        \"Number of function results does not match the number of tool calls\",\n      );\n    }\n\n    // submit the tool outputs\n    const toolOutputs: RunSubmitToolOutputsStreamParams.ToolOutput[] =\n      resultMessages.map((message) => {\n        return {\n          tool_call_id: message.actionExecutionId,\n          output: message.result,\n        };\n      });\n\n    const stream = openai.beta.threads.runs.submitToolOutputsStream(\n      threadId,\n      runId,\n      {\n        tool_outputs: toolOutputs,\n        ...(this.disableParallelToolCalls && { parallel_tool_calls: false }),\n      },\n    );\n\n    await this.streamResponse(stream, eventSource);\n    return runId;\n  }\n\n  private async submitUserMessage(\n    threadId: string,\n    messages: Message[],\n    actions: ActionInput[],\n    eventSource: RuntimeEventSource,\n    forwardedParameters: ForwardedParametersInput,\n  ) {\n    const openai = this.ensureOpenAI();\n    messages = [...messages];\n\n    // get the instruction message\n    const instructionsMessage = messages.shift();\n    const instructions = instructionsMessage.isTextMessage()\n      ? instructionsMessage.content\n      : \"\";\n\n    // get the latest user message\n    const userMessage = messages\n      .map((m) =>\n        convertMessageToOpenAIMessage(m, {\n          keepSystemRole: this.keepSystemRole,\n        }),\n      )\n      .map(convertSystemMessageToAssistantAPI)\n      .at(-1);\n\n    if (userMessage.role !== \"user\") {\n      throw new Error(\"No user message found\");\n    }\n\n    await openai.beta.threads.messages.create(threadId, {\n      role: \"user\",\n      content: userMessage.content,\n    });\n\n    const openaiTools = actions.map(convertActionInputToOpenAITool);\n\n    const tools = [\n      ...openaiTools,\n      ...(this.codeInterpreterEnabled\n        ? [{ type: \"code_interpreter\" } as AssistantTool]\n        : []),\n      ...(this.fileSearchEnabled\n        ? [{ type: \"file_search\" } as AssistantTool]\n        : []),\n    ];\n\n    let stream = openai.beta.threads.runs.stream(threadId, {\n      assistant_id: this.assistantId,\n      instructions,\n      tools: tools,\n      ...(forwardedParameters?.maxTokens && {\n        max_completion_tokens: forwardedParameters.maxTokens,\n      }),\n      ...(this.disableParallelToolCalls && { parallel_tool_calls: false }),\n    });\n\n    await this.streamResponse(stream, eventSource);\n\n    return getRunIdFromStream(stream);\n  }\n\n  private async streamResponse(\n    stream: AssistantStream,\n    eventSource: RuntimeEventSource,\n  ) {\n    eventSource.stream(async (eventStream$) => {\n      let inFunctionCall = false;\n      let currentMessageId: string;\n      let currentToolCallId: string;\n\n      for await (const chunk of stream) {\n        switch (chunk.event) {\n          case \"thread.message.created\":\n            if (inFunctionCall) {\n              eventStream$.sendActionExecutionEnd({\n                actionExecutionId: currentToolCallId,\n              });\n            }\n            currentMessageId = chunk.data.id;\n            eventStream$.sendTextMessageStart({ messageId: currentMessageId });\n            break;\n          case \"thread.message.delta\":\n            if (chunk.data.delta.content?.[0].type === \"text\") {\n              eventStream$.sendTextMessageContent({\n                messageId: currentMessageId,\n                content: chunk.data.delta.content?.[0].text.value,\n              });\n            }\n            break;\n          case \"thread.message.completed\":\n            eventStream$.sendTextMessageEnd({ messageId: currentMessageId });\n            break;\n          case \"thread.run.step.delta\":\n            let toolCallId: string | undefined;\n            let toolCallName: string | undefined;\n            let toolCallArgs: string | undefined;\n            if (\n              chunk.data.delta.step_details.type === \"tool_calls\" &&\n              chunk.data.delta.step_details.tool_calls?.[0].type === \"function\"\n            ) {\n              toolCallId = chunk.data.delta.step_details.tool_calls?.[0].id;\n              toolCallName =\n                chunk.data.delta.step_details.tool_calls?.[0].function.name;\n              toolCallArgs =\n                chunk.data.delta.step_details.tool_calls?.[0].function\n                  .arguments;\n            }\n\n            if (toolCallName && toolCallId) {\n              if (inFunctionCall) {\n                eventStream$.sendActionExecutionEnd({\n                  actionExecutionId: currentToolCallId,\n                });\n              }\n              inFunctionCall = true;\n              currentToolCallId = toolCallId;\n              eventStream$.sendActionExecutionStart({\n                actionExecutionId: currentToolCallId,\n                parentMessageId: chunk.data.id,\n                actionName: toolCallName,\n              });\n            } else if (toolCallArgs) {\n              eventStream$.sendActionExecutionArgs({\n                actionExecutionId: currentToolCallId,\n                args: toolCallArgs,\n              });\n            }\n            break;\n        }\n      }\n      if (inFunctionCall) {\n        eventStream$.sendActionExecutionEnd({\n          actionExecutionId: currentToolCallId,\n        });\n      }\n      eventStream$.complete();\n    });\n  }\n}\n\nfunction getRunIdFromStream(stream: AssistantStream): Promise<string> {\n  return new Promise<string>((resolve, reject) => {\n    let runIdGetter = (event: AssistantStreamEvent) => {\n      if (event.event === \"thread.run.created\") {\n        const runId = event.data.id;\n        stream.off(\"event\", runIdGetter);\n        resolve(runId);\n      }\n    };\n    stream.on(\"event\", runIdGetter);\n  });\n}\n"],"mappings":"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"}