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
2 changes: 1 addition & 1 deletion .github/workflows/validate-templates.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ jobs:
echo "Checking PowerShell scripts..."
errors=0
for f in bin/ps/*.ps1; do
result=$(pwsh -NoProfile -Command "\$errs=\$null; [System.Management.Automation.Language.Parser]::ParseFile('$f', [ref]\$null, [ref]\$errs); if(\$errs -and \$errs.Count -gt 0){\$errs | ForEach-Object { \$_.Message }}" 2>&1)
result=$(pwsh -NoProfile -Command "\$errs=\$null; \$null = [System.Management.Automation.Language.Parser]::ParseFile('$f', [ref]\$null, [ref]\$errs); if(\$errs -and \$errs.Count -gt 0){\$errs | ForEach-Object { \$_.Message }}" 2>&1)
if [[ -n "$result" && "$result" != *"InvalidOperation"* ]]; then
echo "❌ $f: $result"
errors=$((errors + 1))
Expand Down
78 changes: 25 additions & 53 deletions sreagent-templates/bicep/Apply-Extras.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1062,61 +1062,40 @@ if ($DpTokenAvailable) {
if ($token) {
try {
$headers = @{ Authorization = "Bearer $token" }
$ghStatus = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v1/Github/auth/status" -Headers $headers
$ghConfigured = ($ghStatus.isConfigured -eq $true) -or ($ghStatus.hosts[0].isConfigured -eq $true)
$ghStatus = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/github/domains" -Headers $headers
$ghConfigured = ($ghStatus.values -and $ghStatus.values.Count -gt 0)
} catch { }
}

# Shared identity + connector body (used in both fast-path and OAuth wait)
$ident = if ($AgentUami) { $AgentUami } else {
Write-Host " WARN: agent has no user-assigned MI; falling back to SystemAssigned."
"SystemAssigned"
}
$connBody = @{
name = "github"
type = "AgentConnector"
properties = @{
dataConnectorType = "GitHubOAuth"
dataSource = "github-oauth"
identity = $ident
}
} | ConvertTo-Json -Compress -Depth 10
# NOTE: The old GitHubOAuth connector type was deprecated in platform build
# 26.4.216.0 (April 2026). Auth is now stored via /api/v2/github/domains.
# We no longer PUT /api/v2/extendedAgent/connectors/github.

$connectorOk = $false

# Fast path: if auth/status says configured (or PAT), try the connector PUT immediately
# Fast path: if domains shows configured (or PAT), go straight to repo wiring
if ($ghConfigured -or $env:GITHUB_PAT) {
Write-Host "-- Wiring GitHub connector + repos --"
try {
$token = Get-DpToken
$headers = @{
Authorization = "Bearer $token"
"Content-Type" = "application/json"
}
$null = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/extendedAgent/connectors/github" `
-Method Put -Headers $headers -Body $connBody -ContentType "application/json"
# Verify the connector is actually healthy (not just metadata)
Start-Sleep -Seconds 3
Write-Host "-- Wiring GitHub repos --"
# If GITHUB_PAT is provided and domains not yet configured, store PAT via domains API
if ($env:GITHUB_PAT -and -not $ghConfigured) {
try {
$connCheck = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/extendedAgent/connectors/github" `
-Method Get -Headers @{ Authorization = "Bearer $token" }
$provState = $connCheck.properties.provisioningState
if ($provState -eq 'Succeeded') {
Write-Host " ok connector/github (GitHubOAuth, identity=$($ident.Split('/')[-1]))"
$connectorOk = $true
} else {
Write-Host " WARN: connector created but state=$provState — falling back to OAuth wait..."
$token = Get-DpToken
$headers = @{
Authorization = "Bearer $token"
"Content-Type" = "application/json"
}
$patBody = @{ AuthType = "Pat"; Pat = $env:GITHUB_PAT } | ConvertTo-Json -Compress
$null = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/github/domains/github_com" `
-Method Put -Headers $headers -Body $patBody -ContentType "application/json"
Write-Host " ok github/domains/github_com (PAT)"
} catch {
Write-Host " ok connector/github (GitHubOAuth, identity=$($ident.Split('/')[-1]))"
$connectorOk = $true
Write-Host " FAILED -- PUT /api/v2/github/domains/github_com"
}
} catch {
Write-Host " WARN: connector PUT failed (stale auth?) — falling back to OAuth wait..."
}
$connectorOk = $true
}

# OAuth wait: if connector not yet created, show URL and poll until user completes auth
# OAuth wait: if not configured yet, show URL and poll until user completes auth
if (-not $connectorOk -and -not $env:GITHUB_PAT) {
Write-Host "-- GitHub OAuth sign-in required --"
Write-Host "Repos waiting: $($oauthRepos -join ' ')"
Expand All @@ -1125,7 +1104,7 @@ if ($DpTokenAvailable) {
if ($token) {
try {
$headers = @{ Authorization = "Bearer $token" }
$ghConfig = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v1/Github/config" -Headers $headers
$ghConfig = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/github/oauth/config" -Headers $headers
$oauthUrl = if ($ghConfig.oAuthUrl) { $ghConfig.oAuthUrl } elseif ($ghConfig.OAuthUrl) { $ghConfig.OAuthUrl } else { $null }
} catch { }
}
Expand All @@ -1139,18 +1118,11 @@ if ($DpTokenAvailable) {
Start-Sleep -Seconds 10
try {
$token = Get-DpToken
$headers = @{
Authorization = "Bearer $token"
"Content-Type" = "application/json"
}
# Check auth/status — only trust isConfigured, not connector PUT success
$ghCheck = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v1/Github/auth/status" -Headers $headers
$isAuth = ($ghCheck.isConfigured -eq $true) -or ($ghCheck.hosts -and $ghCheck.hosts[0].isConfigured -eq $true)
$headers = @{ Authorization = "Bearer $token" }
$ghCheck = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/github/domains" -Headers $headers
$isAuth = ($ghCheck.values -and $ghCheck.values.Count -gt 0)
if ($isAuth) {
$null = Invoke-RestMethod -TimeoutSec 30 -Uri "$AgentEndpoint/api/v2/extendedAgent/connectors/github" `
-Method Put -Headers $headers -Body $connBody -ContentType "application/json"
Write-Host " GitHub authorized!"
Write-Host " ok connector/github"
$connectorOk = $true
break
}
Expand All @@ -1164,7 +1136,7 @@ if ($DpTokenAvailable) {
Write-Host " Headless alternative: `$env:GITHUB_PAT='ghp_xxx' && re-run"
}
} else {
Write-Host " Could not fetch OAuth URL from $AgentEndpoint/api/v1/Github/config."
Write-Host " Could not fetch OAuth URL from $AgentEndpoint/api/v2/github/oauth/config."
Write-Host " Fallback: Azure portal -> agent -> Repos -> 'Authorize' next to each repo."
}
}
Expand Down
67 changes: 20 additions & 47 deletions sreagent-templates/bicep/apply-extras.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1058,14 +1058,14 @@ fi
echo

# ---------------------------------------------------------------------------
# GitHub: OAuth sign-in + connector + repo wiring.
# GitHub: OAuth sign-in + repo wiring.
# Three-state flow:
# - No repos requested → skip
# - Repos requested, OAuth NOT done → print sign-in URL, instruct re-run
# - Repos requested, OAuth DONE → create GitHubOAuth connector + PUT repos
# Repo PUT body and connector body shapes come from sreagent-investigation:
# src/Agent/Agent.Web/Client/src/src/Common/Clients/ExtendedAgentClient.ts
# src/Agent/Agent.Web/Client/src/src/Space/Settings/Connectors/Wizard/Common/DialogHelper.tsx
# - Repos requested, OAuth DONE → PUT repos
# NOTE: The old GitHubOAuth connector type was deprecated in platform build
# 26.4.216.0 (April 2026). Auth is now stored via /api/v2/github/domains.
# We no longer PUT /api/v2/extendedAgent/connectors/github.
# ---------------------------------------------------------------------------
if [[ ${#oauth_repos[@]} -gt 0 ]]; then
TOKEN=$(_dp_token 2>/dev/null || true)
Expand All @@ -1077,46 +1077,25 @@ if [[ ${#oauth_repos[@]} -gt 0 ]]; then
else
GH_CONFIGURED="false"
fi
# Also check if the github connector already exists (OAuth was done in a prior deploy)
if [[ "$GH_CONFIGURED" == "false" ]]; then
_connectors=$(curl -sS -H "Authorization: Bearer ${TOKEN}" -H "Accept: application/json" \
"${AGENT_ENDPOINT}/api/v2/extendedAgent/connectors" 2>/dev/null || echo '{}')
if echo "$_connectors" | jq -e '[.value // [] | .[] | select(.name == "github")] | length > 0' >/dev/null 2>&1; then
GH_CONFIGURED="true"
fi
fi

if [[ "$GH_CONFIGURED" == "true" || -n "${GITHUB_PAT:-}" ]]; then
# ── OAuth (or PAT) is in place — wire the connector + repos ──
echo "── Wiring GitHub connector + repos ──"
# ── OAuth (or PAT) is in place — wire repos ──
echo "── Wiring GitHub repos ──"

if [[ -z "$AGENT_UAMI" ]]; then
echo " WARN: agent has no user-assigned MI; falling back to SystemAssigned."
IDENT="SystemAssigned"
else
IDENT="$AGENT_UAMI"
fi

# 1) Create the GitHubOAuth connector (named 'github')
body=$(jq -nc --arg id "$IDENT" '{
name: "github",
type: "AgentConnector",
properties: {
dataConnectorType: "GitHubOAuth",
dataSource: "github-oauth",
identity: $id
}
}')
if curl -sS -f -X PUT "${AGENT_ENDPOINT}/api/v2/extendedAgent/connectors/github" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
--data "$body" >/dev/null; then
echo " ok connector/github (GitHubOAuth, identity=${IDENT##*/})"
else
echo " FAILED — PUT /api/v2/extendedAgent/connectors/github"
# If GITHUB_PAT is provided, store it via the domains API so the agent
# can use it for GitHub API calls (replaces deprecated connector PUT).
if [[ -n "${GITHUB_PAT:-}" && "$GH_CONFIGURED" != "true" ]]; then
if curl -sS -f -X PUT "${AGENT_ENDPOINT}/api/v2/github/domains/github_com" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"AuthType\":\"Pat\",\"Pat\":\"${GITHUB_PAT}\"}" >/dev/null; then
echo " ok github/domains/github_com (PAT)"
else
echo " FAILED — PUT /api/v2/github/domains/github_com"
fi
fi

# 2) Attach each repo via the v2 repos dataplane (CodeRepoApiController).
# Attach each repo via the v2 repos dataplane (CodeRepoApiController).
# Route: PUT /api/v2/repos/{name}
# Body : { name, type:"CodeRepo", properties:{ url, type:"GitHub"|"AzureDevOps", description? } }
count=$(jq '.repos // [] | length' "$FILE")
Expand Down Expand Up @@ -1177,12 +1156,6 @@ if [[ ${#oauth_repos[@]} -gt 0 ]]; then
_has_domain=$(echo "$_poll" | jq -r 'if (.values // []) | length > 0 then "true" else "false" end' 2>/dev/null)
if [[ "$_has_domain" == "true" ]]; then
echo " GitHub authorized!"
# Now create the connector
if [[ -z "$AGENT_UAMI" ]]; then IDENT="SystemAssigned"; else IDENT="$AGENT_UAMI"; fi
conn_body=$(jq -nc --arg id "$IDENT" '{name:"github",type:"AgentConnector",properties:{dataConnectorType:"GitHubOAuth",dataSource:"github-oauth",identity:$id}}')
curl -sS -f -X PUT "${AGENT_ENDPOINT}/api/v2/extendedAgent/connectors/github" \
-H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" \
--data "$conn_body" >/dev/null 2>&1 && echo " ok connector/github" || echo " WARN connector/github PUT failed"
auth_ok=true
break
fi
Expand All @@ -1191,7 +1164,7 @@ if [[ ${#oauth_repos[@]} -gt 0 ]]; then
echo

if [[ "$auth_ok" == "true" ]]; then
# Connector already created in polling loop — wire repos only
# OAuth token stored by platform callback — wire repos
echo "── Wiring GitHub repos ──"
TOKEN=$(_dp_token)
count=$(jq '.repos // [] | length' "$FILE")
Expand Down
9 changes: 5 additions & 4 deletions sreagent-templates/bicep/assemble-agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -301,17 +301,18 @@ fi
REPO_INSTRUCTIONS="[]"
[[ -f "${DIR}/data/repo-instructions.json" ]] && REPO_INSTRUCTIONS=$(cat "${DIR}/data/repo-instructions.json")

# Auto-discover .md files in data/ and data/knowledge/ → convert to knowledge items for upload
# Auto-discover .md files in data/ and data/knowledge/ → upload via AgentMemory (data-plane)
# These show in the portal Knowledge tab (RAG-indexed), NOT as KnowledgeFile connectors.
MD_FILES=$(find "${DIR}/data" -maxdepth 1 -name "*.md" -type f 2>/dev/null || true; \
find "${DIR}/data/knowledge" -maxdepth 1 -name "*.md" -type f 2>/dev/null || true)
if [[ -n "$MD_FILES" ]]; then
MD_COUNT=$(echo "$MD_FILES" | wc -l | tr -d ' ')
_log "Found ${MD_COUNT} knowledge .md file(s) in data/"
for mdf in $MD_FILES; do
fname=$(basename "$mdf")
content=$(cat "$mdf")
KNOWLEDGE_ITEMS=$(echo "$KNOWLEDGE_ITEMS" | jq --arg name "$fname" --arg content "$content" \
'. + [{"name": $name, "type": "KnowledgeText", "content": $content}]')
abs_path=$(cd "$(dirname "$mdf")" && pwd)/$(basename "$mdf")
KNOWLEDGE=$(echo "$KNOWLEDGE" | jq --arg fname "$fname" --arg path "$abs_path" \
'. + [{"filename": $fname, "mimeType": "text/markdown", "triggerIndexing": true, "localPath": $path}]')
done
fi

Expand Down
75 changes: 53 additions & 22 deletions sreagent-templates/bin/export-agent.sh
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,9 @@ else
fi

# ── 2. Knowledge items (KnowledgeText/File/WebPage/Repository via connectors API) ──
# NOTE: KnowledgeText items are exported as plain .md files in data/ so that
# on re-deploy they go through the AgentMemory data-plane upload (Knowledge tab)
# instead of being re-created as ARM KnowledgeFile connectors.
if [[ "$INCLUDE_KNOWLEDGE_ITEMS" == "true" ]]; then
_log "Reading knowledge items from connectors API..."
RAW_KNOWLEDGE_ITEMS=$(dp_get "/api/v2/extendedAgent/connectors")
Expand All @@ -690,37 +693,51 @@ if [[ "$INCLUDE_KNOWLEDGE_ITEMS" == "true" ]]; then
KI_COUNT=$(echo "$KNOWLEDGE_ITEMS" | jq 'length')
_log " Found ${KI_COUNT} knowledge item(s)"

# Download knowledge item content if requested
# Download knowledge item content and write KnowledgeText as .md in data/
if [[ "$DOWNLOAD_FILES" == "true" && "$KI_COUNT" -gt 0 ]]; then
_log " Downloading knowledge item content..."
KI_DIR="${FILES_DIR}/knowledge-items"
mkdir -p "$KI_DIR"
KI_TEXT_EXPORTED=0
KI_OTHER_ITEMS="[]"
for i in $(seq 0 $((KI_COUNT - 1))); do
kiname=$(echo "$KNOWLEDGE_ITEMS" | jq -r --argjson i "$i" '.[$i].name')
kitype=$(echo "$KNOWLEDGE_ITEMS" | jq -r --argjson i "$i" '.[$i].type')
# Determine file extension based on type
case "$kitype" in
KnowledgeText) ext=".md" ;;
KnowledgeWebPage) ext=".html" ;;
KnowledgeFile) ext="" ;;
*) ext=".json" ;;
KnowledgeText)
# Export as .md file in data/ → will be uploaded via AgentMemory on redeploy
fname="${kiname}.md"
# Try to strip trailing -md suffix from connector name for cleaner filenames
[[ "$kiname" == *-md ]] && fname="${kiname%-md}.md"
dp_download "/api/v2/extendedAgent/connectors/$(printf %s "$kiname" | jq -sRr @uri)/content" \
"${KI_DIR}/${fname}" 2>/dev/null && {
_log " ✓ ${kiname} → data/${fname} (will use AgentMemory on redeploy)"
KI_TEXT_EXPORTED=$((KI_TEXT_EXPORTED + 1))
} || _log " ✗ ${kiname} (could not download content)"
;;
*)
# Non-text items (WebPage, File, Repository) stay as knowledgeItems
ext=""
case "$kitype" in
KnowledgeWebPage) ext=".html" ;;
KnowledgeFile) ext="" ;;
*) ext=".json" ;;
esac
dp_download "/api/v2/extendedAgent/connectors/$(printf %s "$kiname" | jq -sRr @uri)/content" \
"${KI_DIR}/${kiname}${ext}" 2>/dev/null && \
_log " ✓ ${kiname} (${kitype})" || \
_log " ✗ ${kiname} (could not download content)"
KI_OTHER_ITEMS=$(echo "$KI_OTHER_ITEMS" | jq --argjson i "$i" --arg dir "$KI_DIR" --arg ext "$ext" \
--slurpfile items <(echo "$KNOWLEDGE_ITEMS") \
'. + [$items[0][$i] + {localPath: ($dir + "/" + $items[0][$i].name + $ext)}]')
;;
esac
dp_download "/api/v2/extendedAgent/connectors/$(printf %s "$kiname" | jq -sRr @uri)/content" \
"${KI_DIR}/${kiname}${ext}" 2>/dev/null && \
_log " ✓ ${kiname} (${kitype})" || \
_log " ✗ ${kiname} (could not download content)"
done
# Update entries with localPath
KNOWLEDGE_ITEMS=$(echo "$KNOWLEDGE_ITEMS" | jq -c --arg dir "$KI_DIR" '[
.[] | . + {
localPath: ($dir + "/" + .name + (
if .type == "KnowledgeText" then ".md"
elif .type == "KnowledgeWebPage" then ".html"
elif .type == "KnowledgeFile" then ""
else ".json" end
))
}
]')
# Only keep non-text items in KNOWLEDGE_ITEMS (text ones became .md files)
KNOWLEDGE_ITEMS="$KI_OTHER_ITEMS"
if [[ "$KI_TEXT_EXPORTED" -gt 0 ]]; then
_log " Migrated ${KI_TEXT_EXPORTED} KnowledgeText item(s) to data/ .md files (AgentMemory path)"
fi
fi
else
_log "Skipping knowledge items (use --include-knowledge-items to include)"
Expand Down Expand Up @@ -1421,12 +1438,26 @@ fi

# Move downloaded files into data/ if they were downloaded
if [[ "$DOWNLOAD_FILES" == "true" && -d "$FILES_DIR" ]]; then
for subdir in knowledge knowledge-items synthesized-knowledge repo-instructions; do
for subdir in knowledge synthesized-knowledge repo-instructions; do
if [[ -d "${FILES_DIR}/${subdir}" ]]; then
mkdir -p "${DATA_DIR}/${subdir}"
cp -r "${FILES_DIR}/${subdir}/." "${DATA_DIR}/${subdir}/" 2>/dev/null || true
fi
done
# KnowledgeText .md files go to data/ root so assemble auto-discovers them
# for AgentMemory upload (not back into knowledge-items which creates ARM connectors)
if [[ -d "${FILES_DIR}/knowledge-items" ]]; then
for f in "${FILES_DIR}/knowledge-items/"*.md; do
[[ -f "$f" ]] && cp "$f" "${DATA_DIR}/" 2>/dev/null || true
done
# Non-.md files (WebPage, File) stay in knowledge-items/
for f in "${FILES_DIR}/knowledge-items/"*; do
[[ -f "$f" && "$f" != *.md ]] && {
mkdir -p "${DATA_DIR}/knowledge-items"
cp "$f" "${DATA_DIR}/knowledge-items/" 2>/dev/null || true
}
done
fi
# Clean up temp files dir if it was separate
[[ "$FILES_DIR" != "$DATA_DIR" && "$FILES_DIR" != "${EXPORT_DIR}/data" ]] && rm -rf "$FILES_DIR" 2>/dev/null || true
fi
Expand Down
Loading