Skip to content

Commit ca5cdac

Browse files
SergeyMenshykhsemenshiCopilot
authored andcommitted
Add foundry-toolbox-mcp-skills C# Agent Framework sample (#428)
* Add foundry-toolbox-mcp-skills C# Agent Framework sample Adds a hosted Agent Framework sample that discovers MCP-based skills from a Foundry Toolbox and exposes them via AgentSkillsProvider with progressive disclosure (advertise, load, read resources). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update model from gpt-5 to gpt-4.1 in agent.manifest.yaml Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Swap a2a/01-delegation and foundry-toolbox-mcp-skills order in README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: SergeyMenshykh <SergeMenshikh@outlook.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 16ab173 commit ca5cdac

16 files changed

Lines changed: 1012 additions & 0 deletions

File tree

.github/CODEOWNERS

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# See for instructions on this file https://help.github.com/articles/about-codeowners/
2+
3+
#### Team-owned sample areas ############################################################################
4+
# Each contributing team owns their sample paths for review routing. See CONTRIBUTING.md for setup guide.
5+
/samples/python/hosted-agents/ @microsoft-foundry/hosted-agents
6+
/samples/csharp/hosted-agents/ @microsoft-foundry/hosted-agents
7+
8+
#### files referenced in docs (DO NOT EDIT, except for Docs team!!!) ##########################################
9+
/infrastructure/infrastructure-setup-bicep/01-connections/connection-key-vault.bicep @microsoft-foundry/AI-Platform-Docs
10+
/infrastructure/infrastructure-setup-bicep/05-custom-policy-definitions/deny-disallowed-connections.json @microsoft-foundry/AI-Platform-Docs
11+
/infrastructure/infrastructure-setup-terraform/00-basic-azurerm/code/main.tf @microsoft-foundry/AI-Platform-Docs
12+
/infrastructure/infrastructure-setup-terraform/00-basic-azurerm/code/providers.tf @microsoft-foundry/AI-Platform-Docs
13+
/infrastructure/infrastructure-setup-terraform/00-basic-azurerm/code/variables.tf @microsoft-foundry/AI-Platform-Docs
14+
/infrastructure/infrastructure-setup-terraform/00-basic/code/main.tf @microsoft-foundry/AI-Platform-Docs
15+
/infrastructure/infrastructure-setup-terraform/00-basic/code/providers.tf @microsoft-foundry/AI-Platform-Docs
16+
/infrastructure/infrastructure-setup-terraform/00-basic/code/variables.tf @microsoft-foundry/AI-Platform-Docs
17+
/samples-classic/REST/quickstart/quickstart.sh @microsoft-foundry/AI-Platform-Docs
18+
/samples-classic/csharp/quickstart/Samples/.env.example @microsoft-foundry/AI-Platform-Docs
19+
/samples-classic/csharp/quickstart/Samples/AgentFileSearch.cs @microsoft-foundry/AI-Platform-Docs
20+
/samples-classic/csharp/quickstart/Samples/AgentService.cs @microsoft-foundry/AI-Platform-Docs
21+
/samples-classic/csharp/quickstart/Samples/SimpleInference.cs @microsoft-foundry/AI-Platform-Docs
22+
/samples-classic/java/quickstart/src/main/java/com/azure/ai/foundry/samples/AgentSample.java @microsoft-foundry/AI-Platform-Docs
23+
/samples-classic/java/quickstart/src/main/java/com/azure/ai/foundry/samples/ChatCompletionSample.java @microsoft-foundry/AI-Platform-Docs
24+
/samples-classic/java/quickstart/src/main/java/com/azure/ai/foundry/samples/FileSearchAgentSample.java @microsoft-foundry/AI-Platform-Docs
25+
/samples-classic/python/quickstart/create_project.py @microsoft-foundry/AI-Platform-Docs
26+
/samples-classic/python/quickstart/quickstart.py @microsoft-foundry/AI-Platform-Docs
27+
/samples-classic/typescript/quickstart/.env.template @microsoft-foundry/AI-Platform-Docs
28+
/samples-classic/typescript/quickstart/src/quickstart.ts @microsoft-foundry/AI-Platform-Docs
29+
/samples/REST/quickstart/quickstart-chat-with-agent.sh @microsoft-foundry/AI-Platform-Docs
30+
/samples/REST/quickstart/quickstart-create-agent.sh @microsoft-foundry/AI-Platform-Docs
31+
/samples/REST/quickstart/quickstart-responses.sh @microsoft-foundry/AI-Platform-Docs
32+
/samples/csharp/enterprise-agent-tutorial/1-idea-to-prototype/Evaluate/Program.cs @microsoft-foundry/AI-Platform-Docs
33+
/samples/csharp/enterprise-agent-tutorial/1-idea-to-prototype/ModernWorkplaceAssistant/Program.cs @microsoft-foundry/AI-Platform-Docs
34+
/samples/csharp/quickstart/chat-with-agent/quickstart-chat-with-agent.cs @microsoft-foundry/AI-Platform-Docs
35+
/samples/csharp/quickstart/create-agent/quickstart-create-agent.cs @microsoft-foundry/AI-Platform-Docs
36+
/samples/csharp/quickstart/responses/quickstart-responses.cs @microsoft-foundry/AI-Platform-Docs
37+
/samples/java/quickstart/chat-with-agent/src/main/java/com/azure/ai/agents/ChatWithAgent.java @microsoft-foundry/AI-Platform-Docs
38+
/samples/java/quickstart/create-agent/src/main/java/com/azure/ai/agents/CreateAgent.java @microsoft-foundry/AI-Platform-Docs
39+
/samples/python/enterprise-agent-tutorial/1-idea-to-prototype/evaluate.py @microsoft-foundry/AI-Platform-Docs
40+
/samples/python/enterprise-agent-tutorial/1-idea-to-prototype/main.py @microsoft-foundry/AI-Platform-Docs
41+
/samples/python/foundry-models/model-router/model-router-chat-completions.py @microsoft-foundry/AI-Platform-Docs
42+
/samples/python/foundry-models/model-router/model-router-foundry-responses.py @microsoft-foundry/AI-Platform-Docs
43+
/samples/python/quickstart/chat-with-agent/quickstart-chat-with-agent.py @microsoft-foundry/AI-Platform-Docs
44+
/samples/python/quickstart/create-agent/quickstart-create-agent.py @microsoft-foundry/AI-Platform-Docs
45+
/samples/python/quickstart/responses/quickstart-responses.py @microsoft-foundry/AI-Platform-Docs
46+
/samples/typescript/quickstart/chat-with-agent/src/quickstart-chat-with-agent.ts @microsoft-foundry/AI-Platform-Docs
47+
/samples/typescript/quickstart/create-agent/src/quickstart-create-agent.ts @microsoft-foundry/AI-Platform-Docs
48+
/samples/typescript/quickstart/responses/src/quickstart-responses.ts @microsoft-foundry/AI-Platform-Docs
49+

.github/copilot-instructions.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Files owned by the AI Platform Docs team
2+
3+
If a file is listed in the CODEOWNERS file with @azure-ai-foundry/ai-platform-docs as the owner, it is owned by the AI Platform Docs team. For these files:
4+
5+
- Do not change the filename or move the file.
6+
- Do not remove any comments which contain <some_text> or </some_text> (for any text in between the tags)
7+
- Do not remove any cell in a notebook if it contains metadata with "name:" in it.
8+
9+
In a code review, if any of the above rules are broken, please add the following text to your review:
10+
🛑STOP! This PR contains changes that may break documentation. Please post a message on [ai-platform-docs](https://teams.microsoft.com/l/team/19%3AHhf4F_YfPn3kYGdmWvePNwlbF5-RR8wciQEUwwrcggw1%40thread.tacv2/conversations?groupId=fdaf4412-8993-4ea6-a7d4-aeaded7fc854&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) to request help.
11+
12+
Only files owned by the AI Platform Docs team are subject to these rules.
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import subprocess
4+
import sys
5+
from dataclasses import dataclass
6+
from itertools import chain
7+
from pathlib import Path
8+
from typing import Dict, Iterable, List, Literal, Optional, Tuple
9+
10+
11+
@dataclass
12+
class GitDiffTreeRecord:
13+
"""Represents a line of output from 'git diff-tree'"""
14+
15+
src_mode: str
16+
src_hash: str
17+
dst_mode: str
18+
dst_hash: str
19+
src_path: Path
20+
dst_path: Optional[Path]
21+
status: Literal["A", "C", "D", "M", "R", "T", "U", "X"]
22+
score: Optional[int]
23+
24+
25+
@dataclass
26+
class GitChange:
27+
diff_record: GitDiffTreeRecord
28+
bytes_changed: int
29+
30+
31+
def parse_git_diff_tree_output(output: str) -> List[GitDiffTreeRecord]:
32+
"""Parses the output of `git diff-tree` as described in the "Raw Output" section
33+
of the man page
34+
"""
35+
36+
def make_record(line: str) -> GitDiffTreeRecord:
37+
src_mode, dst_mode, src_hash, dst_hash, rest = line[1:].split(" ", maxsplit=4)
38+
status_score_and_paths = rest.split("\t")
39+
return GitDiffTreeRecord(
40+
src_mode=src_mode,
41+
src_hash=src_hash,
42+
dst_mode=dst_mode,
43+
dst_hash=dst_hash,
44+
status=status_score_and_paths[0][0],
45+
score=int(status_score_and_paths[0][1:]) if len(status_score_and_paths[0]) > 1 else None,
46+
src_path=Path(status_score_and_paths[1]),
47+
dst_path=Path(status_score_and_paths[2]) if len(status_score_and_paths) >= 3 else None,
48+
)
49+
50+
return [make_record(line) for line in output.splitlines(keepends=False)]
51+
52+
53+
def get_blob_sizes(hashes: Iterable[str]) -> Dict[str, Optional[int]]:
54+
"""Fetches the sizes, in bytes, of git blobs
55+
56+
:param hashes: A iterable of git blob hashes
57+
:type hashes: Iterable[str]
58+
59+
:return: A dictionary that mapping blob hashes to their size if the blob exists,
60+
or None otherwise
61+
:rtype: Dict[str, Optional[int]]
62+
"""
63+
input = "\n".join(set(hashes))
64+
cat_file_output = subprocess.run(
65+
["git", "cat-file", "--batch-check"],
66+
input=input,
67+
check=True,
68+
text=True,
69+
capture_output=True,
70+
).stdout
71+
72+
def make_object_size_tuple(line: str) -> Tuple[str, Optional[int]]:
73+
hash, *_, size = line.split()
74+
return (hash, int(size) if size != "missing" else None)
75+
76+
return dict(make_object_size_tuple(line) for line in cat_file_output.splitlines(keepends=False))
77+
78+
79+
def get_file_size_differences(commit_range: str) -> Dict[Path, GitChange]:
80+
"""Computes the size difference, in bytes, of files changed between two commits
81+
82+
:param commit_range: A git commit range (e.g. HEAD~3..HEAD)
83+
:type commit_range: str
84+
85+
:return: A dictionary mapping paths (relative to repository root) to size
86+
differences.
87+
:rtype: dict[Path, GitChange]
88+
"""
89+
changed_records = parse_git_diff_tree_output(
90+
subprocess.run(
91+
["git", "diff-tree", "-r", commit_range],
92+
capture_output=True,
93+
text=True,
94+
check=True,
95+
).stdout
96+
)
97+
98+
sizes = get_blob_sizes(chain.from_iterable((idx.src_hash, idx.dst_hash) for idx in changed_records))
99+
100+
assert {"A", "D", "M"}.issuperset(idx.status for idx in changed_records)
101+
102+
def as_int(maybe_num: Optional[int]) -> int:
103+
return maybe_num or 0
104+
105+
return {
106+
x.src_path: GitChange(
107+
diff_record=x,
108+
bytes_changed=as_int(sizes[x.dst_hash]) - as_int(sizes[x.src_hash]),
109+
)
110+
for x in changed_records
111+
}
112+
113+
114+
def main(
115+
commit_range: str,
116+
quiet: bool = False,
117+
limit: Optional[int] = None,
118+
show_n_largest_files: int = 30,
119+
) -> Literal[0, 1]:
120+
size_differences = get_file_size_differences(commit_range)
121+
cumulative_size_difference = sum(x.bytes_changed for x in size_differences.values())
122+
exceeds_limit = limit is not None and cumulative_size_difference > limit
123+
124+
def bytes_diff(num: int) -> str:
125+
return ("+" if num >= 0 else "") + human_friendly_bytes(num)
126+
127+
if not quiet:
128+
print(f"Total file size difference for commit range '{commit_range}': ")
129+
print(f"\t{bytes_diff(cumulative_size_difference)}", end="")
130+
print(f" (Exceeds set limit of {bytes_diff(limit)})" if exceeds_limit else "")
131+
132+
largest_n_sizes = sorted(size_differences.items(), key=lambda x: x[1].bytes_changed, reverse=True)[
133+
:show_n_largest_files
134+
]
135+
136+
if largest_n_sizes:
137+
print("")
138+
print(f"Largest {len(largest_n_sizes)} filesize differences:")
139+
140+
for path, val in largest_n_sizes:
141+
print(f"\t{bytes_diff(val.bytes_changed)}\t{path}")
142+
143+
return 1 if exceeds_limit else 0
144+
145+
146+
def num_bytes(arg: str) -> int:
147+
"""Converts a string to a number of bytes"""
148+
error = argparse.ArgumentTypeError(f"'{arg}' cannot be parsed into a number of bytes")
149+
try:
150+
return int(arg)
151+
except ValueError:
152+
pass
153+
154+
if len(arg) < 3:
155+
raise error
156+
157+
num, suffix = (arg[:-2], arg[-2:])
158+
shift_values = {
159+
"KB": 1,
160+
"MB": 2,
161+
"GB": 3,
162+
"TB": 4,
163+
"PB": 5,
164+
"EB": 6,
165+
"ZB": 7,
166+
"YB": 8,
167+
}
168+
169+
shift = shift_values.get(suffix)
170+
171+
if shift is None:
172+
raise error
173+
try:
174+
return int(num) << (shift * 10)
175+
except ValueError as e:
176+
raise error from e
177+
178+
179+
def human_friendly_bytes(num: int) -> str:
180+
"""Prints a number of bytes as a human friendly string"""
181+
for prefix in ["", "K", "M", "G", "T", "P", "E", "Z"]:
182+
if abs(num) < 1024.0:
183+
return f"{num:.1f}{prefix}B"
184+
num /= 1024.0
185+
return f"{num:.1f}YB"
186+
187+
188+
if __name__ == "__main__":
189+
parser = argparse.ArgumentParser(
190+
description="A program that summarizes the file size " + "differences between two git commits."
191+
)
192+
parser.add_argument(
193+
"commit_range",
194+
default="HEAD^..HEAD",
195+
type=str,
196+
help="Commit range to commit the diff for (e.g. HEAD~3..HEAD)",
197+
)
198+
parser.add_argument("--quiet", action="store_true", help="Silence all output")
199+
parser.add_argument(
200+
"--limit",
201+
type=num_bytes,
202+
help="Exit non-zero if total changes exceeds this value. "
203+
+ "Can be a raw number of bytes (e.g. 65536) or a suffixed value (e.g 2MB)",
204+
)
205+
parser.add_argument(
206+
"--show-n-largest-files",
207+
type=int,
208+
help="Show this many of the largest files in diff",
209+
default=30,
210+
)
211+
sys.exit(main(**vars(parser.parse_args())))

0 commit comments

Comments
 (0)