( toolCallDelta: ToolCallDelta, currentState: ToolCallState | undefined, )
| 6 | // See example of data coming in here: |
| 7 | // https://platform.openai.com/docs/guides/function-calling?api-mode=chat#streaming |
| 8 | export function addToolCallDeltaToState( |
| 9 | toolCallDelta: ToolCallDelta, |
| 10 | currentState: ToolCallState | undefined, |
| 11 | ): ToolCallState { |
| 12 | const currentCall = currentState?.toolCall; |
| 13 | |
| 14 | // If we have a current state and the delta has a different ID, ignore the delta |
| 15 | if ( |
| 16 | currentState && |
| 17 | toolCallDelta.id && |
| 18 | currentCall?.id !== toolCallDelta.id |
| 19 | ) { |
| 20 | return currentState; |
| 21 | } |
| 22 | |
| 23 | // These will/should not be partially streamed |
| 24 | const callType = toolCallDelta.type ?? "function"; |
| 25 | const callId = currentCall?.id || toolCallDelta.id || ""; |
| 26 | |
| 27 | // These may be streamed in chunks |
| 28 | const currentName = currentCall?.function.name ?? ""; |
| 29 | const currentArgs = currentCall?.function.arguments ?? ""; |
| 30 | |
| 31 | const nameDelta = toolCallDelta.function?.name ?? ""; |
| 32 | const argsDelta = toolCallDelta.function?.arguments ?? ""; |
| 33 | |
| 34 | let mergedName = currentName; |
| 35 | if (nameDelta.startsWith(currentName)) { |
| 36 | // Case where model progresssively streams name but full name each time e.g. "readFi" -> "readFil" -> "readFile" |
| 37 | mergedName = nameDelta; |
| 38 | } else if (!currentName.startsWith(nameDelta)) { |
| 39 | mergedName = currentName + nameDelta; |
| 40 | } |
| 41 | |
| 42 | // Similar logic for args, with an extra JSON check |
| 43 | let mergedArgs = currentArgs; |
| 44 | try { |
| 45 | // If args is JSON parseable, it is complete, don't add to it |
| 46 | JSON.parse(currentArgs); |
| 47 | } catch (e) { |
| 48 | // Model streams in args in parts e.g. "{"file": "file1"" -> ", "line": 1}" |
| 49 | mergedArgs = currentArgs + argsDelta; |
| 50 | |
| 51 | // Note, removed case where model progresssively streams args but full args each time e.g. "{"file": "file1"}" -> "{"file": "file1", "line": 1}" |
| 52 | // Because no apis do this and difficult to detect reliably |
| 53 | } |
| 54 | |
| 55 | const [_, parsedArgs] = incrementalParseJson(mergedArgs || "{}"); |
| 56 | |
| 57 | return { |
| 58 | status: "generating", |
| 59 | toolCall: { |
| 60 | id: callId, |
| 61 | type: callType, |
| 62 | function: { |
| 63 | name: mergedName, |
| 64 | arguments: mergedArgs, |
| 65 | }, |
no test coverage detected