Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/prc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ on:
pull_request:
merge_group:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
Expand Down
16 changes: 14 additions & 2 deletions agent/claudecode/claude_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"trpc.group/trpc-go/trpc-agent-go/event"
"trpc.group/trpc-go/trpc-agent-go/log"
"trpc.group/trpc-go/trpc-agent-go/model"
"trpc.group/trpc-go/trpc-agent-go/platform"
"trpc.group/trpc-go/trpc-agent-go/session"
"trpc.group/trpc-go/trpc-agent-go/tool"
)
Expand Down Expand Up @@ -115,6 +116,7 @@ func (a *claudeCodeAgent) runInvocation(ctx context.Context, invocation *agent.I
if len(combined) == 0 {
msg = runErr.Error()
}
msg = redactAgentErrorMessage(msg)
rsp := &model.Response{
Object: model.ObjectTypeError,
Done: true,
Expand Down Expand Up @@ -200,6 +202,8 @@ func (a *claudeCodeAgent) emitFlowError(
combined []byte,
flowErr error,
) {
content := redactAgentErrorMessage(string(combined))
message := redactAgentErrorMessage(flowErr.Error())
rsp := &model.Response{
Object: model.ObjectTypeError,
Done: true,
Expand All @@ -208,18 +212,26 @@ func (a *claudeCodeAgent) emitFlowError(
Index: 0,
Message: model.Message{
Role: model.RoleAssistant,
Content: string(combined),
Content: content,
},
},
},
Error: &model.ResponseError{
Type: model.ErrorTypeFlowError,
Message: flowErr.Error(),
Message: message,
},
}
a.emitEvent(ctx, invocation, out, event.NewResponseEvent(invocation.InvocationID, a.name, rsp))
}

func redactAgentErrorMessage(message string) string {
redactor, err := platform.NewRedactor()
if err != nil {
return message
}
return redactor.Redact(message)
}

// runWithSession executes the CLI with resume-first semantics and returns stdout/stderr.
func (a *claudeCodeAgent) runWithSession(ctx context.Context, sessionID, prompt string) ([]byte, []byte, error) {
// Copy base args to avoid mutating shared backing arrays across concurrent invocations.
Expand Down
26 changes: 21 additions & 5 deletions agent/claudecode/claude_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func TestClaudeCodeAgent_Run_CommandError(t *testing.T) {

runner := &scriptedRunner{
run: func(cmd command) ([]byte, []byte, error) {
return nil, []byte("boom\n"), errors.New("exit 2")
return nil, []byte("boom Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"), errors.New("exit 2")
},
}

Expand All @@ -214,8 +214,10 @@ func TestClaudeCodeAgent_Run_CommandError(t *testing.T) {
require.Len(t, events, 1)
require.True(t, events[0].IsFinalResponse())
require.NotNil(t, events[0].Error)
require.Equal(t, "boom", events[0].Error.Message)
require.Equal(t, "boom", events[0].Choices[0].Message.Content)
require.Contains(t, events[0].Error.Message, "boom")
require.Contains(t, events[0].Choices[0].Message.Content, "boom")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
assertRedactedAgentErrorMessage(t, events[0].Choices[0].Message.Content)
}

func TestClaudeCodeAgent_Run_RawOutputHook(t *testing.T) {
Expand All @@ -233,7 +235,7 @@ func TestClaudeCodeAgent_Run_RawOutputHook(t *testing.T) {
transcript := `[{"type":"result","result":"hello"}]`
runner := &scriptedRunner{
run: func(cmd command) ([]byte, []byte, error) {
return []byte(transcript), []byte("warn\n"), nil
return []byte(transcript), []byte("warn Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"), nil
},
}

Expand Down Expand Up @@ -285,7 +287,7 @@ func TestClaudeCodeAgent_Run_RawOutputHookError(t *testing.T) {
},
}

hookErr := errors.New("hook failed")
hookErr := errors.New("hook failed Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def")
var called bool
ag, err := New(
WithBin("claude"),
Expand All @@ -309,8 +311,22 @@ func TestClaudeCodeAgent_Run_RawOutputHookError(t *testing.T) {
require.Equal(t, model.ErrorTypeFlowError, events[0].Error.Type)
require.Contains(t, events[0].Error.Message, "raw output hook")
require.Contains(t, events[0].Error.Message, "hook failed")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
require.Contains(t, events[0].Choices[0].Message.Content, transcript)
require.Contains(t, events[0].Choices[0].Message.Content, "warn")
for _, secret := range []string{"raw-token", "sk-1234567890abcdef", "session=abc", "sid=def"} {
require.NotContains(t, events[0].Choices[0].Message.Content, secret)
}
}

func assertRedactedAgentErrorMessage(t *testing.T, message string) {
t.Helper()
for _, secret := range []string{"raw-token", "sk-1234567890abcdef", "session=abc", "sid=def"} {
require.NotContains(t, message, secret)
}
for _, redacted := range []string{"Authorization: ****", "api_key=****", "Cookie: ****"} {
require.Contains(t, message, redacted)
}
}

func TestClaudeCodeAgent_InfoAndRunnerArgs(t *testing.T) {
Expand Down
16 changes: 14 additions & 2 deletions agent/codex/codex_agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"trpc.group/trpc-go/trpc-agent-go/event"
"trpc.group/trpc-go/trpc-agent-go/log"
"trpc.group/trpc-go/trpc-agent-go/model"
"trpc.group/trpc-go/trpc-agent-go/platform"
"trpc.group/trpc-go/trpc-agent-go/session"
"trpc.group/trpc-go/trpc-agent-go/tool"
)
Expand Down Expand Up @@ -189,6 +190,7 @@ func (a *codexAgent) emitRunError(ctx context.Context, invocation *agent.Invocat
if len(combined) == 0 {
msg = runErr.Error()
}
msg = redactAgentErrorMessage(msg)
rsp := &model.Response{
Object: model.ObjectTypeError,
Done: true,
Expand All @@ -211,6 +213,8 @@ func (a *codexAgent) emitRunError(ctx context.Context, invocation *agent.Invocat

// emitFlowError emits an error response event and stops further invocation processing.
func (a *codexAgent) emitFlowError(ctx context.Context, invocation *agent.Invocation, out chan<- *event.Event, combined []byte, flowErr error) {
content := redactAgentErrorMessage(string(combined))
message := redactAgentErrorMessage(flowErr.Error())
rsp := &model.Response{
Object: model.ObjectTypeError,
Done: true,
Expand All @@ -219,18 +223,26 @@ func (a *codexAgent) emitFlowError(ctx context.Context, invocation *agent.Invoca
Index: 0,
Message: model.Message{
Role: model.RoleAssistant,
Content: string(combined),
Content: content,
},
},
},
Error: &model.ResponseError{
Type: model.ErrorTypeFlowError,
Message: flowErr.Error(),
Message: message,
},
}
a.emitEvent(ctx, invocation, out, event.NewResponseEvent(invocation.InvocationID, a.name, rsp))
}

func redactAgentErrorMessage(message string) string {
redactor, err := platform.NewRedactor()
if err != nil {
return message
}
return redactor.Redact(message)
}

// runWithSession executes the CLI with resume-first semantics and returns stdout/stderr.
func (a *codexAgent) runWithSession(ctx context.Context, threadID, prompt string) ([]byte, []byte, error) {
if strings.TrimSpace(threadID) != "" {
Expand Down
39 changes: 31 additions & 8 deletions agent/codex/codex_agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func TestCodexAgent_Run_ResumeAndCreateErrorsReturnRunError(t *testing.T) {
if len(cmd.args) > 1 && cmd.args[1] == "resume" {
return nil, []byte("resume unavailable"), errors.New("resume exit 1")
}
return nil, []byte("create unavailable"), errors.New("create exit 1")
return nil, []byte("create failed Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"), errors.New("create exit 1")
},
}
ag, err := New(withCommandRunner(runner))
Expand All @@ -196,7 +196,10 @@ func TestCodexAgent_Run_ResumeAndCreateErrorsReturnRunError(t *testing.T) {
require.Len(t, events, 1)
require.NotNil(t, events[0].Error)
require.Equal(t, model.ErrorTypeRunError, events[0].Error.Type)
require.Equal(t, "create unavailable", events[0].Error.Message)
require.Contains(t, events[0].Error.Message, "create failed")
require.Contains(t, events[0].Choices[0].Message.Content, "create failed")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
assertRedactedAgentErrorMessage(t, events[0].Choices[0].Message.Content)
calls := runner.Calls()
require.Len(t, calls, 2)
require.Equal(t, []string{"exec", "resume", "--json", "thread-1"}, calls[0].args)
Expand Down Expand Up @@ -250,7 +253,7 @@ func TestCodexAgent_Run_RawOutputHookReceivesCommandError(t *testing.T) {
runErr := errors.New("exit 1")
runner := &scriptedRunner{
run: func(cmd command) ([]byte, []byte, error) {
return []byte("stdout text"), []byte("stderr text"), runErr
return []byte("stdout text"), []byte("stderr Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"), runErr
},
}
var got RawOutputHookArgs
Expand All @@ -269,11 +272,15 @@ func TestCodexAgent_Run_RawOutputHookReceivesCommandError(t *testing.T) {
require.ErrorIs(t, got.Error, runErr)
require.Equal(t, "--help", got.Prompt)
require.Equal(t, "stdout text", string(got.Stdout))
require.Equal(t, "stderr text", string(got.Stderr))
require.Equal(t, "stderr Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def", string(got.Stderr))
require.NotNil(t, events[0].Error)
require.Equal(t, model.ErrorTypeRunError, events[0].Error.Type)
require.Equal(t, "stdout text\nstderr text", events[0].Error.Message)
require.Equal(t, "stdout text\nstderr text", events[0].Choices[0].Message.Content)
require.Contains(t, events[0].Error.Message, "stdout text")
require.Contains(t, events[0].Error.Message, "stderr")
require.Contains(t, events[0].Choices[0].Message.Content, "stdout text")
require.Contains(t, events[0].Choices[0].Message.Content, "stderr")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
assertRedactedAgentErrorMessage(t, events[0].Choices[0].Message.Content)
calls := runner.Calls()
require.Len(t, calls, 1)
require.Equal(t, []string{"exec", "--json"}, calls[0].args)
Expand All @@ -284,10 +291,10 @@ func TestCodexAgent_Run_RawOutputHookError(t *testing.T) {
ctx := context.Background()
sess := session.NewSession("app", "user", "sess-hook-2")
inv := newTestInvocation("inv-hook-2", sess, "Hi.")
hookErr := errors.New("hook failed")
hookErr := errors.New("hook failed Authorization: Bearer raw-token\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def")
runner := &scriptedRunner{
run: func(cmd command) ([]byte, []byte, error) {
return []byte(codexTranscript("thread-hook-2", "hello")), []byte("warn\n"), nil
return []byte(codexTranscript("thread-hook-2", "hello")), []byte("warn\napi_key=sk-1234567890abcdef\nCookie: session=abc; sid=def"), nil
},
}
ag, err := New(
Expand All @@ -307,8 +314,24 @@ func TestCodexAgent_Run_RawOutputHookError(t *testing.T) {
require.Equal(t, model.ErrorTypeFlowError, events[0].Error.Type)
require.Contains(t, events[0].Error.Message, "raw output hook")
require.Contains(t, events[0].Error.Message, "hook failed")
assertRedactedAgentErrorMessage(t, events[0].Error.Message)
require.Contains(t, events[0].Choices[0].Message.Content, "thread-hook-2")
require.Contains(t, events[0].Choices[0].Message.Content, "warn")
require.NotContains(t, events[0].Choices[0].Message.Content, "sk-1234567890abcdef")
require.NotContains(t, events[0].Choices[0].Message.Content, "session=abc")
require.NotContains(t, events[0].Choices[0].Message.Content, "sid=def")
require.Contains(t, events[0].Choices[0].Message.Content, "api_key=****")
require.Contains(t, events[0].Choices[0].Message.Content, "Cookie: ****")
}

func assertRedactedAgentErrorMessage(t *testing.T, message string) {
t.Helper()
for _, secret := range []string{"raw-token", "sk-1234567890abcdef", "session=abc", "sid=def"} {
require.NotContains(t, message, secret)
}
for _, redacted := range []string{"Authorization: ****", "api_key=****", "Cookie: ****"} {
require.Contains(t, message, redacted)
}
}

func TestCodexAgent_InfoAndRunnerArgs(t *testing.T) {
Expand Down
21 changes: 19 additions & 2 deletions internal/flow/processor/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
"trpc.group/trpc-go/trpc-agent-go/graph"
"trpc.group/trpc-go/trpc-agent-go/internal/fileref"
iflow "trpc.group/trpc-go/trpc-agent-go/internal/flow"
itelemetry "trpc.group/trpc-go/trpc-agent-go/internal/telemetry"
itrace "trpc.group/trpc-go/trpc-agent-go/internal/trace"
"trpc.group/trpc-go/trpc-agent-go/internal/util/message"
"trpc.group/trpc-go/trpc-agent-go/log"
"trpc.group/trpc-go/trpc-agent-go/memory"
Expand Down Expand Up @@ -3107,8 +3109,23 @@ func (p *ContentRequestProcessor) getAdaptivePreloadMemoryMessage(
Deduplicate: true,
HybridSearch: true,
}
memories, err := reader.SearchMemories(
ctx,
searchCtx, span, startedSpan := itrace.StartSpan(ctx, inv, itelemetry.NewMemorySearchSpanName())
var memories []*memory.Entry
if startedSpan {
defer func() {
itelemetry.TraceMemorySearch(
span,
searchOpts.MaxResults,
len(memories),
searchOpts.HybridSearch,
searchOpts.Deduplicate,
err,
)
span.End()
}()
}
memories, err = reader.SearchMemories(
searchCtx,
userKey,
query,
memory.WithSearchOptions(searchOpts),
Expand Down
Loading
Loading