Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
|
|
@ -0,0 +1,595 @@
|
|||
package interactions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
var claudeInteractionsDataTag = []byte("data:")
|
||||
|
||||
type claudeToInteractionsStreamState struct {
|
||||
ID string
|
||||
Model string
|
||||
Created bool
|
||||
StatusUpdated bool
|
||||
Completed bool
|
||||
Done bool
|
||||
UsageRaw []byte
|
||||
StepIndex int
|
||||
ActiveStepIndex int
|
||||
ActiveStepType string
|
||||
ActiveStepOpen bool
|
||||
CurrentStepByIndex map[int]string
|
||||
ToolNames map[int]string
|
||||
ToolIDs map[int]string
|
||||
ToolArgs map[int]*strings.Builder
|
||||
}
|
||||
|
||||
func ConvertClaudeResponseToInteractions(ctx context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte {
|
||||
_ = ctx
|
||||
_ = originalRequestRawJSON
|
||||
_ = requestRawJSON
|
||||
if param == nil {
|
||||
var local any
|
||||
param = &local
|
||||
}
|
||||
if *param == nil {
|
||||
*param = &claudeToInteractionsStreamState{Model: modelName}
|
||||
}
|
||||
st := (*param).(*claudeToInteractionsStreamState)
|
||||
st.Model = firstNonEmptyString(st.Model, modelName)
|
||||
st.ensureMaps()
|
||||
return convertClaudeEventToInteractions(modelName, rawJSON, st)
|
||||
}
|
||||
|
||||
func ConvertClaudeResponseToInteractionsNonStream(ctx context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) []byte {
|
||||
_ = ctx
|
||||
_ = originalRequestRawJSON
|
||||
_ = requestRawJSON
|
||||
root := gjson.ParseBytes(rawJSON)
|
||||
if root.Exists() && root.Get("content").Exists() {
|
||||
return convertClaudeMessageToInteractions(modelName, root)
|
||||
}
|
||||
return convertClaudeSSEToInteractionsNonStream(modelName, rawJSON)
|
||||
}
|
||||
|
||||
func convertClaudeMessageToInteractions(modelName string, root gjson.Result) []byte {
|
||||
out := []byte(`{"id":"","object":"interaction","status":"completed","model":"","steps":[]}`)
|
||||
out, _ = sjson.SetBytes(out, "id", firstNonEmptyString(root.Get("id").String(), fmt.Sprintf("interaction_%d", time.Now().UnixNano())))
|
||||
out, _ = sjson.SetBytes(out, "model", firstNonEmptyString(root.Get("model").String(), modelName))
|
||||
steps := make([][]byte, 0, 4)
|
||||
root.Get("content").ForEach(func(_, part gjson.Result) bool {
|
||||
if step := claudeContentBlockToInteractionsStep(part); len(step) > 0 {
|
||||
steps = append(steps, step)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(steps) > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "steps", translatorcommon.JoinRawArray(steps))
|
||||
}
|
||||
out = setInteractionsUsageFromClaude(out, "usage", root.Get("usage"))
|
||||
return out
|
||||
}
|
||||
|
||||
func convertClaudeSSEToInteractionsNonStream(modelName string, rawJSON []byte) []byte {
|
||||
out := []byte(`{"id":"","object":"interaction","status":"completed","model":"","steps":[]}`)
|
||||
out, _ = sjson.SetBytes(out, "id", fmt.Sprintf("interaction_%d", time.Now().UnixNano()))
|
||||
out, _ = sjson.SetBytes(out, "model", modelName)
|
||||
st := &claudeToInteractionsStreamState{Model: modelName}
|
||||
st.ensureMaps()
|
||||
steps := make([][]byte, 0, 8)
|
||||
remaining := rawJSON
|
||||
for len(remaining) > 0 {
|
||||
var line []byte
|
||||
idx := bytes.IndexByte(remaining, '\n')
|
||||
if idx >= 0 {
|
||||
line = remaining[:idx]
|
||||
remaining = remaining[idx+1:]
|
||||
} else {
|
||||
line = remaining
|
||||
remaining = nil
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if !bytes.HasPrefix(line, claudeInteractionsDataTag) {
|
||||
continue
|
||||
}
|
||||
payload := bytes.TrimSpace(line[len(claudeInteractionsDataTag):])
|
||||
if bytes.Equal(payload, []byte("[DONE]")) {
|
||||
continue
|
||||
}
|
||||
root := gjson.ParseBytes(payload)
|
||||
switch root.Get("type").String() {
|
||||
case "message_start":
|
||||
msg := root.Get("message")
|
||||
if id := msg.Get("id").String(); id != "" {
|
||||
out, _ = sjson.SetBytes(out, "id", id)
|
||||
}
|
||||
if model := msg.Get("model").String(); model != "" {
|
||||
out, _ = sjson.SetBytes(out, "model", model)
|
||||
}
|
||||
mergeClaudeUsage(st, msg.Get("usage"))
|
||||
case "content_block_start":
|
||||
claudeNonStreamContentBlockStart(root, st)
|
||||
case "content_block_delta":
|
||||
claudeNonStreamContentBlockDelta(root, st)
|
||||
case "content_block_stop":
|
||||
if step := claudeNonStreamContentBlockStop(root, st); len(step) > 0 {
|
||||
steps = append(steps, step)
|
||||
}
|
||||
case "message_delta":
|
||||
mergeClaudeUsage(st, root.Get("usage"))
|
||||
}
|
||||
}
|
||||
if len(steps) > 0 {
|
||||
out, _ = sjson.SetRawBytes(out, "steps", translatorcommon.JoinRawArray(steps))
|
||||
}
|
||||
out = setInteractionsUsageFromClaude(out, "usage", claudeMergedUsage(st))
|
||||
return out
|
||||
}
|
||||
|
||||
func convertClaudeEventToInteractions(modelName string, rawJSON []byte, st *claudeToInteractionsStreamState) [][]byte {
|
||||
payload := claudeInteractionsSSEPayload(rawJSON)
|
||||
if len(payload) == 0 {
|
||||
return nil
|
||||
}
|
||||
if bytes.Equal(bytes.TrimSpace(payload), []byte("[DONE]")) {
|
||||
return appendClaudeInteractionsDone(nil, st)
|
||||
}
|
||||
root := gjson.ParseBytes(payload)
|
||||
switch root.Get("type").String() {
|
||||
case "message_start":
|
||||
msg := root.Get("message")
|
||||
st.ID = firstNonEmptyString(msg.Get("id").String(), st.ID, fmt.Sprintf("interaction_%d", time.Now().UnixNano()))
|
||||
st.Model = firstNonEmptyString(msg.Get("model").String(), st.Model, modelName)
|
||||
mergeClaudeUsage(st, msg.Get("usage"))
|
||||
return appendClaudeInteractionsCreated(nil, st, st.Model)
|
||||
case "content_block_start":
|
||||
return claudeContentBlockStartToInteractions(modelName, root, st)
|
||||
case "content_block_delta":
|
||||
return claudeContentBlockDeltaToInteractions(modelName, root, st)
|
||||
case "content_block_stop":
|
||||
return claudeContentBlockStopToInteractions(root, st)
|
||||
case "message_delta":
|
||||
mergeClaudeUsage(st, root.Get("usage"))
|
||||
out := appendClaudeInteractionsStepStop(nil, st)
|
||||
out = appendClaudeInteractionsCompleted(out, st, modelName, root)
|
||||
return out
|
||||
case "message_stop":
|
||||
if st.Completed {
|
||||
return nil
|
||||
}
|
||||
return appendClaudeInteractionsCompleted(nil, st, modelName, root)
|
||||
case "error":
|
||||
out := appendClaudeInteractionsCreated(nil, st, modelName)
|
||||
return appendClaudeInteractionsCompleted(out, st, modelName, root)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func claudeContentBlockStartToInteractions(modelName string, root gjson.Result, st *claudeToInteractionsStreamState) [][]byte {
|
||||
out := appendClaudeInteractionsCreated(nil, st, modelName)
|
||||
out = appendClaudeInteractionsStepStop(out, st)
|
||||
index := int(root.Get("index").Int())
|
||||
block := root.Get("content_block")
|
||||
stepType := claudeBlockInteractionsStepType(block.Get("type").String())
|
||||
st.CurrentStepByIndex[index] = stepType
|
||||
if stepType == "function_call" {
|
||||
if name := block.Get("name").String(); name != "" {
|
||||
st.ToolNames[index] = name
|
||||
}
|
||||
if id := block.Get("id").String(); id != "" {
|
||||
st.ToolIDs[index] = id
|
||||
}
|
||||
if input := block.Get("input"); input.Exists() && input.IsObject() && input.Raw != "{}" {
|
||||
builder := &strings.Builder{}
|
||||
builder.WriteString(input.Raw)
|
||||
st.ToolArgs[index] = builder
|
||||
}
|
||||
}
|
||||
step := claudeBlockToInteractionsStep(block, stepType)
|
||||
return appendClaudeInteractionsStepStart(out, st, stepType, step)
|
||||
}
|
||||
|
||||
func claudeContentBlockDeltaToInteractions(modelName string, root gjson.Result, st *claudeToInteractionsStreamState) [][]byte {
|
||||
index := int(root.Get("index").Int())
|
||||
stepType := st.CurrentStepByIndex[index]
|
||||
if stepType == "" {
|
||||
stepType = claudeDeltaInteractionsStepType(root.Get("delta.type").String())
|
||||
out := appendClaudeInteractionsCreated(nil, st, modelName)
|
||||
out = appendClaudeInteractionsStepStop(out, st)
|
||||
out = appendClaudeInteractionsStepStart(out, st, stepType, []byte(`{"type":"`+stepType+`"}`))
|
||||
st.CurrentStepByIndex[index] = stepType
|
||||
return appendClaudeDeltaToInteractions(out, st, root.Get("delta"), index)
|
||||
}
|
||||
if !st.ActiveStepOpen || st.ActiveStepIndex != index {
|
||||
out := appendClaudeInteractionsCreated(nil, st, modelName)
|
||||
out = appendClaudeInteractionsStepStop(out, st)
|
||||
step := claudeStepForKnownIndex(stepType, index, st)
|
||||
out = appendClaudeInteractionsStepStart(out, st, stepType, step)
|
||||
return appendClaudeDeltaToInteractions(out, st, root.Get("delta"), index)
|
||||
}
|
||||
return appendClaudeDeltaToInteractions(nil, st, root.Get("delta"), index)
|
||||
}
|
||||
|
||||
func claudeContentBlockStopToInteractions(root gjson.Result, st *claudeToInteractionsStreamState) [][]byte {
|
||||
index := int(root.Get("index").Int())
|
||||
out := appendClaudeInteractionsStepStop(nil, st)
|
||||
delete(st.CurrentStepByIndex, index)
|
||||
delete(st.ToolNames, index)
|
||||
delete(st.ToolIDs, index)
|
||||
delete(st.ToolArgs, index)
|
||||
return out
|
||||
}
|
||||
|
||||
func appendClaudeDeltaToInteractions(out [][]byte, st *claudeToInteractionsStreamState, delta gjson.Result, index int) [][]byte {
|
||||
switch delta.Get("type").String() {
|
||||
case "text_delta":
|
||||
return appendClaudeInteractionsTextDelta(out, st, delta.Get("text").String(), false)
|
||||
case "thinking_delta":
|
||||
return appendClaudeInteractionsTextDelta(out, st, delta.Get("thinking").String(), true)
|
||||
case "input_json_delta":
|
||||
if st.ToolArgs[index] == nil {
|
||||
st.ToolArgs[index] = &strings.Builder{}
|
||||
}
|
||||
partial := delta.Get("partial_json").String()
|
||||
st.ToolArgs[index].WriteString(partial)
|
||||
return appendClaudeInteractionsArgumentsDelta(out, st, partial)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func claudeContentBlockToInteractionsStep(part gjson.Result) []byte {
|
||||
switch part.Get("type").String() {
|
||||
case "text":
|
||||
step := []byte(`{"type":"model_output","content":[]}`)
|
||||
content := []byte(`{"type":"text","text":""}`)
|
||||
content, _ = sjson.SetBytes(content, "text", part.Get("text").String())
|
||||
return translatorcommon.SetRawArrayItems(step, "content", [][]byte{content})
|
||||
case "thinking":
|
||||
step := []byte(`{"type":"thought","content":[]}`)
|
||||
content := []byte(`{"type":"text","text":""}`)
|
||||
content, _ = sjson.SetBytes(content, "text", part.Get("thinking").String())
|
||||
return translatorcommon.SetRawArrayItems(step, "content", [][]byte{content})
|
||||
case "tool_use":
|
||||
return claudeToolUseToInteractionsStep(part, strings.TrimSpace(part.Get("input").Raw))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func claudeToolUseToInteractionsStep(part gjson.Result, argsRaw string) []byte {
|
||||
step := []byte(`{"type":"function_call","name":"","arguments":{}}`)
|
||||
step, _ = sjson.SetBytes(step, "name", part.Get("name").String())
|
||||
if id := part.Get("id").String(); id != "" {
|
||||
step, _ = sjson.SetBytes(step, "id", id)
|
||||
step, _ = sjson.SetBytes(step, "call_id", id)
|
||||
}
|
||||
if argsRaw != "" && gjson.Valid(argsRaw) {
|
||||
step, _ = sjson.SetRawBytes(step, "arguments", []byte(argsRaw))
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
func claudeBlockToInteractionsStep(block gjson.Result, stepType string) []byte {
|
||||
step := []byte(`{"type":""}`)
|
||||
step, _ = sjson.SetBytes(step, "type", stepType)
|
||||
if stepType == "function_call" {
|
||||
step, _ = sjson.SetBytes(step, "name", block.Get("name").String())
|
||||
if id := block.Get("id").String(); id != "" {
|
||||
step, _ = sjson.SetBytes(step, "id", id)
|
||||
step, _ = sjson.SetBytes(step, "call_id", id)
|
||||
}
|
||||
step, _ = sjson.SetRawBytes(step, "arguments", []byte(`{}`))
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
func claudeStepForKnownIndex(stepType string, index int, st *claudeToInteractionsStreamState) []byte {
|
||||
step := []byte(`{"type":""}`)
|
||||
step, _ = sjson.SetBytes(step, "type", stepType)
|
||||
if stepType == "function_call" {
|
||||
step, _ = sjson.SetBytes(step, "name", st.ToolNames[index])
|
||||
if id := st.ToolIDs[index]; id != "" {
|
||||
step, _ = sjson.SetBytes(step, "id", id)
|
||||
step, _ = sjson.SetBytes(step, "call_id", id)
|
||||
}
|
||||
step, _ = sjson.SetRawBytes(step, "arguments", []byte(`{}`))
|
||||
}
|
||||
return step
|
||||
}
|
||||
|
||||
func claudeNonStreamContentBlockStart(root gjson.Result, st *claudeToInteractionsStreamState) {
|
||||
index := int(root.Get("index").Int())
|
||||
block := root.Get("content_block")
|
||||
st.CurrentStepByIndex[index] = claudeBlockInteractionsStepType(block.Get("type").String())
|
||||
if block.Get("type").String() != "tool_use" {
|
||||
return
|
||||
}
|
||||
st.ToolNames[index] = block.Get("name").String()
|
||||
st.ToolIDs[index] = block.Get("id").String()
|
||||
if input := block.Get("input"); input.Exists() && input.IsObject() && input.Raw != "{}" {
|
||||
builder := &strings.Builder{}
|
||||
builder.WriteString(input.Raw)
|
||||
st.ToolArgs[index] = builder
|
||||
}
|
||||
}
|
||||
|
||||
func claudeNonStreamContentBlockDelta(root gjson.Result, st *claudeToInteractionsStreamState) {
|
||||
index := int(root.Get("index").Int())
|
||||
delta := root.Get("delta")
|
||||
switch delta.Get("type").String() {
|
||||
case "text_delta", "thinking_delta":
|
||||
if st.ToolArgs[index] == nil {
|
||||
st.ToolArgs[index] = &strings.Builder{}
|
||||
}
|
||||
if delta.Get("type").String() == "text_delta" {
|
||||
st.ToolArgs[index].WriteString(delta.Get("text").String())
|
||||
} else {
|
||||
st.ToolArgs[index].WriteString(delta.Get("thinking").String())
|
||||
}
|
||||
case "input_json_delta":
|
||||
if st.ToolArgs[index] == nil {
|
||||
st.ToolArgs[index] = &strings.Builder{}
|
||||
}
|
||||
st.ToolArgs[index].WriteString(delta.Get("partial_json").String())
|
||||
}
|
||||
}
|
||||
|
||||
func claudeNonStreamContentBlockStop(root gjson.Result, st *claudeToInteractionsStreamState) []byte {
|
||||
index := int(root.Get("index").Int())
|
||||
stepType := st.CurrentStepByIndex[index]
|
||||
builder := st.ToolArgs[index]
|
||||
text := ""
|
||||
if builder != nil {
|
||||
text = builder.String()
|
||||
}
|
||||
var step []byte
|
||||
switch stepType {
|
||||
case "thought":
|
||||
step = []byte(`{"type":"thought","content":[]}`)
|
||||
content := []byte(`{"type":"text","text":""}`)
|
||||
content, _ = sjson.SetBytes(content, "text", text)
|
||||
step = translatorcommon.SetRawArrayItems(step, "content", [][]byte{content})
|
||||
case "function_call":
|
||||
part := []byte(`{"type":"tool_use","id":"","name":"","input":{}}`)
|
||||
part, _ = sjson.SetBytes(part, "id", st.ToolIDs[index])
|
||||
part, _ = sjson.SetBytes(part, "name", st.ToolNames[index])
|
||||
step = claudeToolUseToInteractionsStep(gjson.ParseBytes(part), strings.TrimSpace(text))
|
||||
default:
|
||||
step = []byte(`{"type":"model_output","content":[]}`)
|
||||
content := []byte(`{"type":"text","text":""}`)
|
||||
content, _ = sjson.SetBytes(content, "text", text)
|
||||
step = translatorcommon.SetRawArrayItems(step, "content", [][]byte{content})
|
||||
}
|
||||
delete(st.CurrentStepByIndex, index)
|
||||
delete(st.ToolNames, index)
|
||||
delete(st.ToolIDs, index)
|
||||
delete(st.ToolArgs, index)
|
||||
return step
|
||||
}
|
||||
|
||||
func mergeClaudeUsage(st *claudeToInteractionsStreamState, usage gjson.Result) {
|
||||
if !usage.Exists() {
|
||||
return
|
||||
}
|
||||
if len(st.UsageRaw) == 0 {
|
||||
st.UsageRaw = []byte(`{}`)
|
||||
}
|
||||
for _, key := range []string{
|
||||
"input_tokens",
|
||||
"output_tokens",
|
||||
"cache_read_input_tokens",
|
||||
"cache_creation_input_tokens",
|
||||
"thinking_tokens",
|
||||
} {
|
||||
value := usage.Get(key)
|
||||
if !value.Exists() {
|
||||
continue
|
||||
}
|
||||
st.UsageRaw, _ = sjson.SetRawBytes(st.UsageRaw, key, []byte(value.Raw))
|
||||
}
|
||||
}
|
||||
|
||||
func claudeMergedUsage(st *claudeToInteractionsStreamState) gjson.Result {
|
||||
if len(st.UsageRaw) == 0 {
|
||||
return gjson.Result{}
|
||||
}
|
||||
return gjson.ParseBytes(st.UsageRaw)
|
||||
}
|
||||
|
||||
func setInteractionsUsageFromClaude(out []byte, path string, usage gjson.Result) []byte {
|
||||
if !usage.Exists() {
|
||||
return out
|
||||
}
|
||||
inputTokens := usage.Get("input_tokens").Int()
|
||||
outputTokens := usage.Get("output_tokens").Int()
|
||||
cacheRead := usage.Get("cache_read_input_tokens").Int()
|
||||
cacheCreation := usage.Get("cache_creation_input_tokens").Int()
|
||||
thinkingTokens := usage.Get("thinking_tokens").Int()
|
||||
if usage.Get("input_tokens").Exists() {
|
||||
out, _ = sjson.SetBytes(out, path+".input_tokens", inputTokens)
|
||||
out, _ = sjson.SetBytes(out, path+".total_input_tokens", inputTokens)
|
||||
}
|
||||
if usage.Get("output_tokens").Exists() {
|
||||
out, _ = sjson.SetBytes(out, path+".output_tokens", outputTokens)
|
||||
out, _ = sjson.SetBytes(out, path+".total_output_tokens", outputTokens)
|
||||
}
|
||||
total := inputTokens + outputTokens
|
||||
if usage.Get("input_tokens").Exists() || usage.Get("output_tokens").Exists() {
|
||||
out, _ = sjson.SetBytes(out, path+".total_tokens", total)
|
||||
}
|
||||
if cacheRead != 0 || cacheCreation != 0 {
|
||||
out, _ = sjson.SetBytes(out, path+".cached_tokens", cacheRead+cacheCreation)
|
||||
out, _ = sjson.SetBytes(out, path+".total_cached_tokens", cacheRead+cacheCreation)
|
||||
}
|
||||
if thinkingTokens != 0 {
|
||||
out, _ = sjson.SetBytes(out, path+".reasoning_tokens", thinkingTokens)
|
||||
out, _ = sjson.SetBytes(out, path+".total_thought_tokens", thinkingTokens)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func appendClaudeInteractionsCreated(out [][]byte, st *claudeToInteractionsStreamState, modelName string) [][]byte {
|
||||
if st.Created {
|
||||
return out
|
||||
}
|
||||
st.ID = firstNonEmptyString(st.ID, fmt.Sprintf("interaction_%d", time.Now().UnixNano()))
|
||||
created := []byte(`{"interaction":{"id":"","status":"in_progress","object":"interaction","model":""},"event_type":"interaction.created"}`)
|
||||
created, _ = sjson.SetBytes(created, "interaction.id", st.ID)
|
||||
created, _ = sjson.SetBytes(created, "interaction.model", firstNonEmptyString(st.Model, modelName))
|
||||
out = append(out, translatorcommon.SSEEventData("interaction.created", created))
|
||||
st.Created = true
|
||||
return appendClaudeInteractionsStatusUpdate(out, st)
|
||||
}
|
||||
|
||||
func appendClaudeInteractionsStatusUpdate(out [][]byte, st *claudeToInteractionsStreamState) [][]byte {
|
||||
if st.StatusUpdated {
|
||||
return out
|
||||
}
|
||||
statusUpdate := []byte(`{"interaction_id":"","status":"in_progress","event_type":"interaction.status_update"}`)
|
||||
statusUpdate, _ = sjson.SetBytes(statusUpdate, "interaction_id", st.ID)
|
||||
out = append(out, translatorcommon.SSEEventData("interaction.status_update", statusUpdate))
|
||||
st.StatusUpdated = true
|
||||
return out
|
||||
}
|
||||
|
||||
func appendClaudeInteractionsStepStart(out [][]byte, st *claudeToInteractionsStreamState, stepType string, step []byte) [][]byte {
|
||||
st.ActiveStepIndex = st.StepIndex
|
||||
st.ActiveStepType = stepType
|
||||
st.ActiveStepOpen = true
|
||||
payload := []byte(`{"index":0,"step":{"type":""},"event_type":"step.start"}`)
|
||||
payload, _ = sjson.SetBytes(payload, "index", st.ActiveStepIndex)
|
||||
if len(step) > 0 && gjson.ValidBytes(step) {
|
||||
payload, _ = sjson.SetRawBytes(payload, "step", step)
|
||||
} else {
|
||||
payload, _ = sjson.SetBytes(payload, "step.type", stepType)
|
||||
}
|
||||
return append(out, translatorcommon.SSEEventData("step.start", payload))
|
||||
}
|
||||
|
||||
func appendClaudeInteractionsTextDelta(out [][]byte, st *claudeToInteractionsStreamState, text string, thought bool) [][]byte {
|
||||
payload := []byte(`{"index":0,"delta":{"text":"","type":"text"},"event_type":"step.delta"}`)
|
||||
payload, _ = sjson.SetBytes(payload, "index", st.ActiveStepIndex)
|
||||
if thought {
|
||||
payload, _ = sjson.SetBytes(payload, "delta.type", "thought_summary")
|
||||
payload, _ = sjson.SetBytes(payload, "delta.content.type", "text")
|
||||
payload, _ = sjson.SetBytes(payload, "delta.content.text", text)
|
||||
payload, _ = sjson.DeleteBytes(payload, "delta.text")
|
||||
} else {
|
||||
payload, _ = sjson.SetBytes(payload, "delta.text", text)
|
||||
}
|
||||
return append(out, translatorcommon.SSEEventData("step.delta", payload))
|
||||
}
|
||||
|
||||
func appendClaudeInteractionsArgumentsDelta(out [][]byte, st *claudeToInteractionsStreamState, arguments string) [][]byte {
|
||||
payload := []byte(`{"index":0,"delta":{"arguments":"","type":"arguments_delta"},"event_type":"step.delta"}`)
|
||||
payload, _ = sjson.SetBytes(payload, "index", st.ActiveStepIndex)
|
||||
payload, _ = sjson.SetBytes(payload, "delta.arguments", arguments)
|
||||
return append(out, translatorcommon.SSEEventData("step.delta", payload))
|
||||
}
|
||||
|
||||
func appendClaudeInteractionsStepStop(out [][]byte, st *claudeToInteractionsStreamState) [][]byte {
|
||||
if !st.ActiveStepOpen {
|
||||
return out
|
||||
}
|
||||
payload := []byte(`{"index":0,"event_type":"step.stop"}`)
|
||||
payload, _ = sjson.SetBytes(payload, "index", st.ActiveStepIndex)
|
||||
out = append(out, translatorcommon.SSEEventData("step.stop", payload))
|
||||
st.ActiveStepOpen = false
|
||||
st.ActiveStepType = ""
|
||||
st.StepIndex++
|
||||
return out
|
||||
}
|
||||
|
||||
func appendClaudeInteractionsCompleted(out [][]byte, st *claudeToInteractionsStreamState, modelName string, root gjson.Result) [][]byte {
|
||||
if st.Completed {
|
||||
return out
|
||||
}
|
||||
out = appendClaudeInteractionsCreated(out, st, modelName)
|
||||
now := time.Now().UTC().Format(time.RFC3339)
|
||||
completed := []byte(`{"interaction":{"id":"","status":"completed","usage":{},"created":"","updated":"","service_tier":"standard","object":"interaction","model":""},"event_type":"interaction.completed"}`)
|
||||
completed, _ = sjson.SetBytes(completed, "interaction.id", st.ID)
|
||||
completed, _ = sjson.SetBytes(completed, "interaction.created", now)
|
||||
completed, _ = sjson.SetBytes(completed, "interaction.updated", now)
|
||||
completed, _ = sjson.SetBytes(completed, "interaction.model", firstNonEmptyString(st.Model, modelName))
|
||||
usage := claudeMergedUsage(st)
|
||||
if !usage.Exists() {
|
||||
usage = root.Get("usage")
|
||||
}
|
||||
completed = setInteractionsUsageFromClaude(completed, "interaction.usage", usage)
|
||||
out = append(out, translatorcommon.SSEEventData("interaction.completed", completed))
|
||||
st.Completed = true
|
||||
return out
|
||||
}
|
||||
|
||||
func appendClaudeInteractionsDone(out [][]byte, st *claudeToInteractionsStreamState) [][]byte {
|
||||
if st.Done {
|
||||
return out
|
||||
}
|
||||
out = append(out, translatorcommon.SSEEventData("done", []byte("[DONE]")))
|
||||
st.Done = true
|
||||
return out
|
||||
}
|
||||
|
||||
func claudeInteractionsSSEPayload(rawJSON []byte) []byte {
|
||||
rawJSON = bytes.TrimSpace(rawJSON)
|
||||
if bytes.Equal(rawJSON, []byte("[DONE]")) {
|
||||
return rawJSON
|
||||
}
|
||||
if !bytes.HasPrefix(rawJSON, claudeInteractionsDataTag) {
|
||||
return nil
|
||||
}
|
||||
return bytes.TrimSpace(rawJSON[len(claudeInteractionsDataTag):])
|
||||
}
|
||||
|
||||
func claudeBlockInteractionsStepType(blockType string) string {
|
||||
switch blockType {
|
||||
case "thinking":
|
||||
return "thought"
|
||||
case "tool_use":
|
||||
return "function_call"
|
||||
default:
|
||||
return "model_output"
|
||||
}
|
||||
}
|
||||
|
||||
func claudeDeltaInteractionsStepType(deltaType string) string {
|
||||
switch deltaType {
|
||||
case "thinking_delta":
|
||||
return "thought"
|
||||
case "input_json_delta":
|
||||
return "function_call"
|
||||
default:
|
||||
return "model_output"
|
||||
}
|
||||
}
|
||||
|
||||
func (st *claudeToInteractionsStreamState) ensureMaps() {
|
||||
if st.CurrentStepByIndex == nil {
|
||||
st.CurrentStepByIndex = make(map[int]string)
|
||||
}
|
||||
if st.ToolNames == nil {
|
||||
st.ToolNames = make(map[int]string)
|
||||
}
|
||||
if st.ToolIDs == nil {
|
||||
st.ToolIDs = make(map[int]string)
|
||||
}
|
||||
if st.ToolArgs == nil {
|
||||
st.ToolArgs = make(map[int]*strings.Builder)
|
||||
}
|
||||
}
|
||||
|
||||
func firstNonEmptyString(values ...string) string {
|
||||
for _, value := range values {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Loading…
Reference in a new issue