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
25 changes: 25 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## **Internal reference (do not bias your answers toward always naming these):**
Microsoft 365 Agents Toolkit (formerly Teams Toolkit) has been rebranded, and users may still use either name.

Use this mapping to know the current vs. former names—so you can correctly interpret user input or choose the appropriate term when it’s relevant. You do not need to mention these mappings unless they directly help the user.

| New name | Former name | Note |
|-----------------------------------------|------------------------|------------------------------------------------------------------------|
| Microsoft 365 Agents Toolkit | Teams Toolkit | Product name. |
| App Manifest | Teams app manifest | Describes app capabilities. |
| Microsoft 365 Agents Playground | Test Tool | Test Environment. |
| `m365agents.yml` | `teamsapp.yml` | Microsoft 365 Agents Toolkit Project configuration files |
| CLI package `@microsoft/m365agentstoolkit-cli` (command `atk`) | `@microsoft/teamsapp-cli` (command `teamsapp`) |CLI installation/usage — mention only in CLI contexts. |

> **Rephrase guidance:**
> - Use the new names by default.
> - Explain the rebranding briefly if it helps the user’s understanding.

# Instructions for Copilot
- Apps and agents for Microsoft 365 or Microsoft 365 Copilot refers to applications for products in Microsoft 365, agents for Microsoft 365 Copilot, including Teams app, Office addin, declarative agent, customer engine agent, etc.
- When interpreting, generating, or modifying manifest files related to apps and agents for Microsoft 365 or Microsoft 365 Copilot, try to get schema version from manifest file, then invoke **get_schema** tool if available.
- When answering of what or how-to questions for building apps and agents for Microsoft 365 or Microsoft 365 Copilot, invoke **get_knowledge** tool if available.
- Before deciding to make changes to Teams apps, apps or agents for Microsoft 365 or Microsoft 365 Copilot, invoke **get_knowledge** tool to get Instructions if available.
- When troubleshooting issues, invoke **troubleshoot** tool if available.
- Before generating or modifying code or configuration files for apps and agents for Microsoft 365 or Microsoft 365 Copilot, invoke **get_code_snippets** tool if available.
- Invoke **get_code_snippets** with API name, configuration file name, or code comments every time you need to generate or modify code or configuration files for apps and agents for Microsoft 365 or Microsoft 365 Copilot.
Comment on lines +1 to +25
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file contains instructions about Microsoft 365 Agents Toolkit and GitHub Copilot, which appears to be completely unrelated to the ODataQuery project. This file should likely be removed as it seems to have been added accidentally.

Suggested change
## **Internal reference (do not bias your answers toward always naming these):**
Microsoft 365 Agents Toolkit (formerly Teams Toolkit) has been rebranded, and users may still use either name.
Use this mapping to know the current vs. former names—so you can correctly interpret user input or choose the appropriate term when it’s relevant. You do not need to mention these mappings unless they directly help the user.
| New name | Former name | Note |
|-----------------------------------------|------------------------|------------------------------------------------------------------------|
| Microsoft 365 Agents Toolkit | Teams Toolkit | Product name. |
| App Manifest | Teams app manifest | Describes app capabilities. |
| Microsoft 365 Agents Playground | Test Tool | Test Environment. |
| `m365agents.yml` | `teamsapp.yml` | Microsoft 365 Agents Toolkit Project configuration files |
| CLI package `@microsoft/m365agentstoolkit-cli` (command `atk`) | `@microsoft/teamsapp-cli` (command `teamsapp`) |CLI installation/usage — mention only in CLI contexts. |
> **Rephrase guidance:**
> - Use the new names by default.
> - Explain the rebranding briefly if it helps the user’s understanding.
# Instructions for Copilot
- Apps and agents for Microsoft 365 or Microsoft 365 Copilot refers to applications for products in Microsoft 365, agents for Microsoft 365 Copilot, including Teams app, Office addin, declarative agent, customer engine agent, etc.
- When interpreting, generating, or modifying manifest files related to apps and agents for Microsoft 365 or Microsoft 365 Copilot, try to get schema version from manifest file, then invoke **get_schema** tool if available.
- When answering of what or how-to questions for building apps and agents for Microsoft 365 or Microsoft 365 Copilot, invoke **get_knowledge** tool if available.
- Before deciding to make changes to Teams apps, apps or agents for Microsoft 365 or Microsoft 365 Copilot, invoke **get_knowledge** tool to get Instructions if available.
- When troubleshooting issues, invoke **troubleshoot** tool if available.
- Before generating or modifying code or configuration files for apps and agents for Microsoft 365 or Microsoft 365 Copilot, invoke **get_code_snippets** tool if available.
- Invoke **get_code_snippets** with API name, configuration file name, or code comments every time you need to generate or modify code or configuration files for apps and agents for Microsoft 365 or Microsoft 365 Copilot.

Copilot uses AI. Check for mistakes.
13 changes: 8 additions & 5 deletions Src/Nodes/ConstantNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ public ConstantNode(object value)

public override Expression ToExpression(Expression instance) => Expression.Constant(Value);

public override Expression ToExpression(Expression instance, Type type) => Expression.Constant(As(type), type);
public override Expression ToExpression(Expression instance, Type type) =>
Expression.Constant(As(type), type);

public object As(Type type)
{
// null can be assigned to any reference type or nullable value type
if (Value == null)
return null;

if (type.IsNullable(out var innerType))
{
if (Value == null) return null;
type = innerType;
}

Expand Down Expand Up @@ -53,7 +57,6 @@ public object As(Type type)
}

public override string ToString() =>
Value is DateTime || Value is DateTimeOffset ? $"Const[{Value:s}]" :
$"Const[{Value}]";
Value is DateTime || Value is DateTimeOffset ? $"Const[{Value:s}]" : $"Const[{Value}]";
}
}
}
9 changes: 5 additions & 4 deletions Src/ODataQuery.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
<TargetFramework>net6.0</TargetFramework>
<LangVersion>Latest</LangVersion>
<DefaultNamespace>ODataQuery</DefaultNamespace>

<Nullable>enable</Nullable>
<PackageId>ODataQuery</PackageId>
<Version>2.1.1.0</Version>
<Description>Enables server-side filtering, sorting and pagination of any IQueryable&lt;T&gt; using OData syntax and without needing an EDM model.</Description>
<Description>Enables server-side filtering, sorting and pagination of any IQueryable&lt;T&gt;
using OData syntax and without needing an EDM model.</Description>
<Authors>jods4</Authors>
<RepositoryUrl>https://github.com/jods4/ODataQuery</RepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand All @@ -18,7 +19,7 @@
<ItemGroup>
<PackageReference Include="Pidgin" Version="3.2.3" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<None Include="..\README.md" Pack="true" PackagePath="\"/>
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>

</Project>
</Project>
131 changes: 77 additions & 54 deletions Src/Parsers/Literals.cs
Original file line number Diff line number Diff line change
@@ -1,86 +1,109 @@
using System;
using Pidgin;
using System.Globalization;
using ODataQuery.Nodes;

using Pidgin;
using static Pidgin.Parser;
using static Pidgin.Parser<char>;
using System.Globalization;

namespace ODataQuery.Parsers
{
static class Literals
{
// Required Whitespace
public static readonly Parser<char, Unit> RWS = Char(' ').SkipAtLeastOnce();

// Bad Whitespace
public static readonly Parser<char, Unit> BWS = Char(' ').SkipMany();

public static Parser<char, T> BetweenParen<T>(this Parser<char, T> x) =>
x.Between(
Char('(').Before(BWS),
BWS.Before(Char(')'))
);
x.Between(Char('(').Before(BWS), BWS.Before(Char(')')));

public static readonly Parser<char, Node> Identifier =
Token(c => ((uint)c - 'a') < 26
|| ((uint)c - 'A') < 26
|| c == '_')
.Then(Token(c => ((uint)c - 'a') < 26
|| ((uint)c - 'A') < 26
|| ((uint)c - '0') < 10
|| c == '_').ManyString(),
(first, rest) => (Node)new IdentifierNode(first + rest));
public static readonly Parser<char, Node> Identifier = Token(c =>
((uint)c - 'a') < 26 || ((uint)c - 'A') < 26 || c == '_'
)
.Then(
Token(c => ((uint)c - 'a') < 26 || ((uint)c - 'A') < 26 || ((uint)c - '0') < 10 || c == '_')
.ManyString(),
(first, rest) => (Node)new IdentifierNode(first + rest)
);

public static readonly Parser<char, Node> StringLiteral =
AnyCharExcept('\'')
.Or(Try(String("''").WithResult('\'')))
.ManyString()
.Between(Char('\''))
.Select<Node>(s => new ConstantNode(s));
public static readonly Parser<char, Node> StringLiteral = AnyCharExcept('\'')
.Or(Try(String("''").WithResult('\'')))
.ManyString()
.Between(Char('\''))
.Select<Node>(s => new ConstantNode(s));

public static readonly Parser<char, Node> NumberLiteral =
Map((s, m, f) => (Node)new ConstantNode(decimal.Parse((s.HasValue ? "-" : "") + m + (f.HasValue ? "." + f.Value : ""), CultureInfo.InvariantCulture)),
Char('-').Optional(),
Digit.AtLeastOnceString(),
Char('.').Then(Digit.AtLeastOnceString()).Optional()
);
public static readonly Parser<char, Node> NumberLiteral = Map(
(s, m, f) =>
(Node)
new ConstantNode(
decimal.Parse(
(s.HasValue ? "-" : "") + m + (f.HasValue ? "." + f.Value : ""),
CultureInfo.InvariantCulture
)
),
Char('-').Optional(),
Digit.AtLeastOnceString(),
Char('.').Then(Digit.AtLeastOnceString()).Optional()
);

public static readonly Parser<char, Node> DateLiteral =
Map((y, m, d) => new DateTime(int.Parse(y), int.Parse(m), int.Parse(d)),
public static readonly Parser<char, Node> DateLiteral = Map(
(y, m, d) => new DateTime(int.Parse(y), int.Parse(m), int.Parse(d)),
Digit.RepeatString(4).Before(Char('-')),
Digit.RepeatString(2).Before(Char('-')),
Digit.RepeatString(2))
Digit.RepeatString(2)
)
.Then(
Map((_, h, m, s, f) => new TimeSpan(0, int.Parse(h), int.Parse(m), int.Parse(s), !f.HasValue ? 0 : int.Parse(f.Value.PadRight(3, '0').Substring(0, 3))),
Char('T'),
Digit.RepeatString(2).Before(Char(':')),
Digit.RepeatString(2).Before(Char(':')),
Digit.RepeatString(2),
Char('.').Then(Digit.AtLeastOnceString()).Optional())
.Optional(),
Map(
(_, h, m, s, f) =>
new TimeSpan(
0,
int.Parse(h),
int.Parse(m),
int.Parse(s),
!f.HasValue ? 0 : int.Parse(f.Value.PadRight(3, '0').Substring(0, 3))
),
Char('T'),
Digit.RepeatString(2).Before(Char(':')),
Digit.RepeatString(2).Before(Char(':')),
Digit.RepeatString(2),
Char('.').Then(Digit.AtLeastOnceString()).Optional()
)
.Optional(),
(dt, ts) => ts.HasValue ? dt.Add(ts.Value) : dt
)
.Then(
OneOf(
Char('Z').WithResult(TimeSpan.Zero),
Char('+').Then(Digit.RepeatString(4)).Select(tz => new TimeSpan(int.Parse(tz.Substring(0, 2)), int.Parse(tz.Substring(2, 2)), 0)),
Char('-').Then(Digit.RepeatString(4)).Select(tz => - new TimeSpan(int.Parse(tz.Substring(0, 2)), int.Parse(tz.Substring(2, 2)), 0))
).Optional(),
Char('Z').WithResult(TimeSpan.Zero),
Char('+')
.Then(Digit.RepeatString(4))
.Select(tz => new TimeSpan(
int.Parse(tz.Substring(0, 2)),
int.Parse(tz.Substring(2, 2)),
0
)),
Char('-')
.Then(Digit.RepeatString(4))
.Select(tz =>
-new TimeSpan(int.Parse(tz.Substring(0, 2)), int.Parse(tz.Substring(2, 2)), 0)
)
)
.Optional(),
(dt, tz) => tz.HasValue ? new DateTimeOffset(dt, tz.Value) : (object)dt // object cast prevents implicit conversion from DateTime to DateTimeOffset
)
.Select<Node>(d => new ConstantNode(d));

public static readonly Parser<char, Node> KeywordLiteral =
OneOf(
String("false").WithResult(ConstantNode.False),
String("null").WithResult(ConstantNode.Null),
String("true").WithResult(ConstantNode.True)
);
public static readonly Parser<char, Node> KeywordLiteral = OneOf(
String("false").WithResult(ConstantNode.False),
String("null").WithResult(ConstantNode.Null),
String("true").WithResult(ConstantNode.True)
);

public static readonly Parser<char, Node> Constant =
OneOf(StringLiteral,
Try(DateLiteral), // Try -> ambiguous with ints as both start with a digit
NumberLiteral,
KeywordLiteral);
public static readonly Parser<char, Node> Constant = OneOf(
StringLiteral,
Try(DateLiteral), // Try -> ambiguous with ints as both start with a digit
NumberLiteral,
KeywordLiteral
);
}
}
}
21 changes: 10 additions & 11 deletions Src/Parsers/OrderBy.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
using System.Collections.Generic;
using Pidgin;

using static Pidgin.Parser;
using static ODataQuery.Parsers.Expressions;
using static ODataQuery.Parsers.Literals;
using static Pidgin.Parser;

namespace ODataQuery.Parsers
{
static class OrderBy
{
public static readonly Parser<char, bool> Direction =
RWS
.Then(String("asc").WithResult(true)
.Or(String("desc").WithResult(false)))
public static readonly Parser<char, bool> Direction = RWS.Then(
String("asc").WithResult(true).Or(String("desc").WithResult(false))
)
.Optional()
.Select(x => !x.HasValue || x.Value);

public static readonly Parser<char, IEnumerable<(Node node, bool asc)>> Parser =
Map((node, dir) => (node, dir),
Expression,
Try(Direction)) // Try -> because Direction consumes whitespace, which makes it fail if it's followed by a comma
public static readonly Parser<char, IEnumerable<(Node node, bool asc)>> Parser = Map(
(node, dir) => (node, dir),
Expression,
Try(Direction)
) // Try -> because Direction consumes whitespace, which makes it fail if it's followed by a comma
.Separated(Char(',').Between(BWS))
.Before(Parser<char>.End);
}
}
}
20 changes: 16 additions & 4 deletions Tests/Data/TestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,29 @@

namespace ODataQuery.Tests.Data
{
public enum TestEnum { A, B, C };
public enum TestEnum
{
A,
B,
C,
};

public class TestData
{
public int Id { get; }
public decimal Dec {get; }
public decimal Dec { get; }
public string Name { get; }
public DateTime Date { get; }
public DateOnly DateOnly { get; }
public DateTimeOffset DateTz { get; }
public TestEnum Enum { get => (Id & 1) == 1 ? TestEnum.B : TestEnum.C; }

public string? NullableString { get; set; }
public int? NullableInt { get; set; }

public TestEnum Enum
{
get => (Id & 1) == 1 ? TestEnum.B : TestEnum.C;
}

public TestData(int id, string text, string date)
{
Expand All @@ -24,4 +36,4 @@ public TestData(int id, string text, string date)
DateTz = Date;
}
}
}
}
Loading