{"version":3,"sources":["../src/utils/openai-stream-interceptor.ts"],"sourcesContent":["import { AnnotatedFunction, parseChatCompletion } from \"@copilotkit/shared\";\n\n/**\n * This function decides what to handle server side and what to forward to the client.\n * It also handles the execution of server side functions.\n *\n * TODO: add proper error handling and logging\n */\nexport function openaiStreamInterceptor(\n  stream: ReadableStream<Uint8Array>,\n  functions: AnnotatedFunction<any[]>[],\n  debug: boolean = false,\n): ReadableStream {\n  const functionsByName = functions.reduce((acc, fn) => {\n    acc[fn.name] = fn;\n    return acc;\n  }, {} as Record<string, AnnotatedFunction<any[]>>);\n\n  const decodedStream = parseChatCompletion(stream);\n  const reader = decodedStream.getReader();\n\n  async function cleanup(controller?: ReadableStreamDefaultController<any>) {\n    if (controller) {\n      try {\n        controller.close();\n      } catch (_) {}\n    }\n    if (reader) {\n      try {\n        await reader.cancel();\n      } catch (_) {}\n    }\n  }\n\n  // Keep track of current state as we process the stream\n\n  // Loop Invariant:\n  // Either we are in the middle of a function call that should be executed on the backend = TRUE\n  // or we are in the middle of processing a chunk that should be forwarded to the client = FALSE\n  let executeThisFunctionCall = false;\n\n  let functionCallName = \"\";\n  let functionCallArguments = \"\";\n\n  const executeFunctionCall = async (): Promise<boolean> => {\n    const fn = functionsByName[functionCallName];\n    let args: Record<string, any>[] = [];\n    if (functionCallArguments) {\n      args = JSON.parse(functionCallArguments);\n    }\n    const paramsInCorrectOrder: any[] = [];\n    for (let arg of fn.argumentAnnotations) {\n      paramsInCorrectOrder.push(args[arg.name as keyof typeof args]);\n    }\n    await fn.implementation(...paramsInCorrectOrder);\n\n    executeThisFunctionCall = false;\n\n    functionCallName = \"\";\n    functionCallArguments = \"\";\n    return true;\n  };\n\n  return new ReadableStream({\n    async pull(controller) {\n      while (true) {\n        try {\n          const { done, value } = await reader.read();\n          if (done) {\n            if (debug) {\n              console.log(\"data: [DONE]\\n\\n\");\n            }\n            if (executeThisFunctionCall) {\n              // We are at the end of the stream and still have a function call to execute\n              await executeFunctionCall();\n            }\n            const payload = new TextEncoder().encode(\"data: [DONE]\\n\\n\");\n            controller.enqueue(payload);\n            await cleanup(controller);\n            return;\n          } else if (debug) {\n            console.log(\"data: \" + JSON.stringify(value) + \"\\n\\n\");\n          }\n\n          // We are in the middle of a function call and got a non function call chunk\n          // so we need to execute the function call first\n          if (executeThisFunctionCall && !value.choices[0].delta.function_call) {\n            if (!(await executeFunctionCall())) {\n              return;\n            }\n          }\n\n          let mode: \"function\" | \"message\" = value.choices[0].delta.function_call\n            ? \"function\"\n            : \"message\";\n\n          // if we get a message, emit the content and continue;\n          if (mode === \"message\") {\n            if (value.choices[0].delta.content) {\n              const payload = new TextEncoder().encode(\"data: \" + JSON.stringify(value) + \"\\n\\n\");\n              controller.enqueue(payload);\n            }\n            continue;\n          }\n          // if we get a function call, emit it only if we don't execute it server side\n          else if (mode === \"function\") {\n            // Set the function name if present\n            if (value.choices[0].delta.function_call!.name) {\n              functionCallName = value.choices[0].delta.function_call!.name!;\n            }\n            // If we have argument streamed back, add them to the function call arguments\n            if (value.choices[0].delta.function_call!.arguments) {\n              functionCallArguments += value.choices[0].delta.function_call!.arguments!;\n            }\n            if (!executeThisFunctionCall) {\n              // Decide if we should execute the function call server side\n\n              if (!(functionCallName in functionsByName)) {\n                // Just forward the function call to the client\n                const payload = new TextEncoder().encode(\"data: \" + JSON.stringify(value) + \"\\n\\n\");\n                controller.enqueue(payload);\n              } else {\n                // Execute the function call server side\n                executeThisFunctionCall = true;\n              }\n            }\n            continue;\n          }\n        } catch (error) {\n          controller.error(error);\n          return;\n        }\n      }\n    },\n    cancel() {\n      reader.cancel();\n    },\n  });\n}\n"],"mappings":"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","names":[]}