Skip to content
Merged
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
1,354 changes: 1,354 additions & 0 deletions .github/workflows/ai-inference-github-models.lock.yml

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions .github/workflows/ai-inference-github-models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
name: AI Inference with GitHub Models
on:
workflow_dispatch:
inputs:
issue_number:
description: 'The number of the issue to analyze'
required: true
issues:
types: [opened]

permissions:
contents: read
models: read

engine:
id: custom
max-turns: 3
steps:
- name: Setup AI Inference with GitHub Models
uses: actions/ai-inference@v1
id: ai_inference
with:
# Use gpt-4o-mini model
model: gpt-4o-mini
# Use the provided prompt or create one based on the event
prompt-file: ${{ env.GITHUB_AW_PROMPT }}
# Configure the AI inference settings
max_tokens: 1000
temperature: 0.7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Create Issue Comment
run: |
# Determine issue number based on event type
if [ "${{ github.event_name }}" == "issues" ]; then
ISSUE_NUMBER="${{ github.event.issue.number }}"
else
ISSUE_NUMBER="${{ github.event.inputs.issue_number }}"
fi

# Generate safe output for issue comment
echo "{\"type\": \"add-issue-comment\", \"issue_number\": \"$ISSUE_NUMBER\", \"body\": \"${{ steps.ai_inference.outputs.response }}\"}" >> $GITHUB_AW_SAFE_OUTPUTS

safe-outputs:
add-issue-comment:
max: 1
target: "*"
---

Summarize the issue inlined below and provide suggestions for next steps.

---

${{ needs.task.outputs.text }}
309 changes: 308 additions & 1 deletion .github/workflows/test-safe-outputs-custom-engine.lock.yml

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions .github/workflows/test-safe-outputs-custom-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ safe-outputs:
create-pull-request-review-comment:
max: 1
side: "RIGHT"
create-security-report:
max: 5

engine:
id: custom
Expand Down Expand Up @@ -92,6 +94,10 @@ engine:
run: |
echo '{"type": "missing-tool", "tool": "example-missing-tool", "reason": "This is a test of the missing-tool safe output functionality. No actual tool is missing.", "alternatives": "This is a simulated missing tool report generated by the custom engine test workflow.", "context": "test-safe-outputs-custom-engine workflow validation"}' >> $GITHUB_AW_SAFE_OUTPUTS

- name: Generate Security Report Output
run: |
echo '{"type": "create-security-report", "file": "README.md", "line": 1, "severity": "note", "message": "This is a test security finding generated by the custom engine workflow to validate the create-security-report safe output functionality. This is a notice-level finding used for testing purposes and does not represent an actual security issue.", "ruleIdSuffix": "test-custom-engine-notice"}' >> $GITHUB_AW_SAFE_OUTPUTS

- name: List generated outputs
run: |
echo "Generated safe output entries:"
Expand Down Expand Up @@ -124,6 +130,7 @@ This is a comprehensive test workflow that exercises every available safe output
- **missing-tool**: Reports missing functionality (test simulation)
- **create-discussion**: Creates repository discussions
- **create-pull-request-review-comment**: Creates PR review comments
- **create-security-report**: Generates SARIF security reports

## Custom Engine Implementation

Expand Down
288 changes: 288 additions & 0 deletions pkg/parser/engine_includes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
package parser

import (
"os"
"path/filepath"
"testing"
)

func TestExpandIncludesForEngines(t *testing.T) {
// Create temporary directory for test files
tmpDir := t.TempDir()

// Create include file with engine specification
includeContent := `---
engine: codex
tools:
github:
allowed: ["list_issues"]
---

# Include with Engine
`
includeFile := filepath.Join(tmpDir, "include-engine.md")
if err := os.WriteFile(includeFile, []byte(includeContent), 0644); err != nil {
t.Fatal(err)
}

// Create main markdown content with include directive
mainContent := `# Main Workflow

@include include-engine.md

Some content here.
`

// Test engine expansion
engines, err := ExpandIncludesForEngines(mainContent, tmpDir)
if err != nil {
t.Fatalf("Expected successful engine expansion, got error: %v", err)
}

// Should find one engine
if len(engines) != 1 {
t.Fatalf("Expected 1 engine, got %d", len(engines))
}

// Should extract "codex" engine
if engines[0] != `"codex"` {
t.Errorf("Expected engine 'codex', got %s", engines[0])
}
}

func TestExpandIncludesForEnginesObjectFormat(t *testing.T) {
// Create temporary directory for test files
tmpDir := t.TempDir()

// Create include file with object-format engine specification
includeContent := `---
engine:
id: claude
model: claude-3-5-sonnet-20241022
max-turns: 5
tools:
github:
allowed: ["list_issues"]
---

# Include with Object Engine
`
includeFile := filepath.Join(tmpDir, "include-object-engine.md")
if err := os.WriteFile(includeFile, []byte(includeContent), 0644); err != nil {
t.Fatal(err)
}

// Create main markdown content with include directive
mainContent := `# Main Workflow

@include include-object-engine.md

Some content here.
`

// Test engine expansion
engines, err := ExpandIncludesForEngines(mainContent, tmpDir)
if err != nil {
t.Fatalf("Expected successful engine expansion, got error: %v", err)
}

// Should find one engine
if len(engines) != 1 {
t.Fatalf("Expected 1 engine, got %d", len(engines))
}

// Should extract engine object as JSON
expectedFields := []string{`"id":"claude"`, `"model":"claude-3-5-sonnet-20241022"`, `"max-turns":5`}
for _, field := range expectedFields {
if !contains(engines[0], field) {
t.Errorf("Expected engine JSON to contain %s, got %s", field, engines[0])
}
}
}

func TestExpandIncludesForEnginesNoEngine(t *testing.T) {
// Create temporary directory for test files
tmpDir := t.TempDir()

// Create include file without engine specification
includeContent := `---
tools:
github:
allowed: ["list_issues"]
---

# Include without Engine
`
includeFile := filepath.Join(tmpDir, "include-no-engine.md")
if err := os.WriteFile(includeFile, []byte(includeContent), 0644); err != nil {
t.Fatal(err)
}

// Create main markdown content with include directive
mainContent := `# Main Workflow

@include include-no-engine.md

Some content here.
`

// Test engine expansion
engines, err := ExpandIncludesForEngines(mainContent, tmpDir)
if err != nil {
t.Fatalf("Expected successful engine expansion, got error: %v", err)
}

// Should find no engines
if len(engines) != 0 {
t.Errorf("Expected 0 engines, got %d: %v", len(engines), engines)
}
}

func TestExpandIncludesForEnginesMultipleIncludes(t *testing.T) {
// Create temporary directory for test files
tmpDir := t.TempDir()

// Create first include file with engine
include1Content := `---
engine: claude
tools:
github:
allowed: ["list_issues"]
---

# First Include
`
include1File := filepath.Join(tmpDir, "include1.md")
if err := os.WriteFile(include1File, []byte(include1Content), 0644); err != nil {
t.Fatal(err)
}

// Create second include file with engine
include2Content := `---
engine: codex
tools:
claude:
allowed: ["Read", "Write"]
---

# Second Include
`
include2File := filepath.Join(tmpDir, "include2.md")
if err := os.WriteFile(include2File, []byte(include2Content), 0644); err != nil {
t.Fatal(err)
}

// Create main markdown content with multiple include directives
mainContent := `# Main Workflow

@include include1.md

Some content here.

@include include2.md

More content.
`

// Test engine expansion
engines, err := ExpandIncludesForEngines(mainContent, tmpDir)
if err != nil {
t.Fatalf("Expected successful engine expansion, got error: %v", err)
}

// Should find two engines
if len(engines) != 2 {
t.Fatalf("Expected 2 engines, got %d: %v", len(engines), engines)
}

// Should extract both engines
if engines[0] != `"claude"` {
t.Errorf("Expected first engine 'claude', got %s", engines[0])
}
if engines[1] != `"codex"` {
t.Errorf("Expected second engine 'codex', got %s", engines[1])
}
}

func TestExpandIncludesForEnginesOptionalMissing(t *testing.T) {
// Create temporary directory for test files
tmpDir := t.TempDir()

// Create main markdown content with optional include directive to non-existent file
mainContent := `# Main Workflow

@include? missing-file.md

Some content here.
`

// Test engine expansion - should not fail for optional includes
engines, err := ExpandIncludesForEngines(mainContent, tmpDir)
if err != nil {
t.Fatalf("Expected successful engine expansion with optional missing include, got error: %v", err)
}

// Should find no engines
if len(engines) != 0 {
t.Errorf("Expected 0 engines, got %d: %v", len(engines), engines)
}
}

func TestExtractEngineFromContent(t *testing.T) {
tests := []struct {
name string
content string
expected string
}{
{
name: "string engine",
content: `---
engine: claude
---
# Test
`,
expected: `"claude"`,
},
{
name: "object engine",
content: `---
engine:
id: codex
model: gpt-4
---
# Test
`,
expected: `{"id":"codex","model":"gpt-4"}`,
},
{
name: "no engine",
content: `---
tools:
github: {}
---
# Test
`,
expected: "",
},
{
name: "no frontmatter",
content: `# Test

Just markdown content.
`,
expected: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := extractEngineFromContent(tt.content)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("Expected %q, got %q", tt.expected, result)
}
})
}
}
Loading