Skip to content

Add da-adaptive-card-inline-edit-python sample#126

Open
YugalPradhan31 wants to merge 2 commits intopnp:mainfrom
YugalPradhan31:da-adaptive-card-inline-edit-python
Open

Add da-adaptive-card-inline-edit-python sample#126
YugalPradhan31 wants to merge 2 commits intopnp:mainfrom
YugalPradhan31:da-adaptive-card-inline-edit-python

Conversation

@YugalPradhan31
Copy link
Copy Markdown
Contributor

No description provided.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new Python Azure Functions–backed declarative agent sample demonstrating inline editing of Adaptive Cards (Action.Execute) for car repair records, packaged as an API plugin for Microsoft 365 Copilot.

Changes:

  • Introduces an Azure Functions (Python) HTTP API to list and update repair records.
  • Adds Copilot app package assets (manifest, declarative agent, plugin manifest, OpenAPI spec, Adaptive Cards templates).
  • Adds provisioning/deployment configuration (Agents Toolkit YAML, Bicep/parameters, VS Code tasks/launch) and sample documentation/metadata.

Reviewed changes

Copilot reviewed 28 out of 31 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
samples/da-adaptive-card-inline-edit-python/src/repairsData.json Seed data for repairs displayed/edited in cards
samples/da-adaptive-card-inline-edit-python/src/key_gen.py Local helper to generate an API key
samples/da-adaptive-card-inline-edit-python/requirements.txt Python dependencies for the Azure Function
samples/da-adaptive-card-inline-edit-python/m365agents.yml Provision/deploy/publish workflow for cloud
samples/da-adaptive-card-inline-edit-python/m365agents.local.yml Provision/deploy workflow for local debugging
samples/da-adaptive-card-inline-edit-python/local.settings.json Local Azure Functions settings template
samples/da-adaptive-card-inline-edit-python/infra/azure.parameters.json ARM parameters wiring for provisioned resources
samples/da-adaptive-card-inline-edit-python/infra/azure.bicep Infrastructure definition for Function App + settings
samples/da-adaptive-card-inline-edit-python/host.json Azure Functions host configuration
samples/da-adaptive-card-inline-edit-python/function_app.py Python Azure Functions implementation (list + update)
samples/da-adaptive-card-inline-edit-python/env/.env.local Local environment variables template
samples/da-adaptive-card-inline-edit-python/env/.env.dev Dev environment variables template
samples/da-adaptive-card-inline-edit-python/assets/sample.json Sample catalog metadata entry
samples/da-adaptive-card-inline-edit-python/appPackage/repairDeclarativeAgent.json Declarative agent definition referencing the plugin
samples/da-adaptive-card-inline-edit-python/appPackage/outline.png Teams app icon (outline)
samples/da-adaptive-card-inline-edit-python/appPackage/manifest.json Teams app manifest for the sample
samples/da-adaptive-card-inline-edit-python/appPackage/instruction.txt Agent instructions used by declarative agent
samples/da-adaptive-card-inline-edit-python/appPackage/color.png Teams app icon (color)
samples/da-adaptive-card-inline-edit-python/appPackage/apiSpecificationFile/repair.yml OpenAPI contract for list/update operations
samples/da-adaptive-card-inline-edit-python/appPackage/ai-plugin.json Copilot plugin manifest (OpenAPI runtime)
samples/da-adaptive-card-inline-edit-python/appPackage/adaptiveCards/updateRepair.json Adaptive Card template for update response
samples/da-adaptive-card-inline-edit-python/appPackage/adaptiveCards/listRepairs.json Adaptive Card template for list response
samples/da-adaptive-card-inline-edit-python/app.py Local helper to start Functions Core Tools
samples/da-adaptive-card-inline-edit-python/README.md Sample documentation and setup steps
samples/da-adaptive-card-inline-edit-python/.vscode/tasks.json VS Code tasks for local workflow
samples/da-adaptive-card-inline-edit-python/.vscode/settings.json VS Code settings for the sample
samples/da-adaptive-card-inline-edit-python/.vscode/launch.json VS Code debug/preview configurations
samples/da-adaptive-card-inline-edit-python/.vscode/extensions.json Recommended extensions
samples/da-adaptive-card-inline-edit-python/.gitignore Git ignore rules for local/dev artifacts
samples/da-adaptive-card-inline-edit-python/.funcignore Deployment ignore rules for zip deploy

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

repair_records = json.loads(DATA_FILE.read_text(encoding="utf-8"))
idx = next((i for i, r in enumerate(repair_records) if r["id"] == repair_id), -1)
if idx < 0:
return func.HttpResponse(status_code=404)
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

This 404 response returns no body, but the OpenAPI spec documents a JSON error payload for 404. Returning a consistent JSON error (and mimetype) will make error handling more predictable for clients.

Suggested change
return func.HttpResponse(status_code=404)
return func.HttpResponse(
json.dumps({"error": "Repair record not found"}),
status_code=404,
mimetype="application/json",
)

Copilot uses AI. Check for mistakes.
Comment on lines +95 to +96
DATA_FILE.write_text(json.dumps(repair_records, indent=2), encoding="utf-8")

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

This function writes updates back to repairsData.json inside the deployed function package. With WEBSITE_RUN_FROM_PACKAGE=1 (set in infra/azure.bicep), the wwwroot content is typically read-only in Azure Functions, so PATCH may fail at runtime and/or changes won’t persist across instances. Use a writable backing store (e.g., Azure Storage/Cosmos DB) or write to an app data path designed for persistence instead of the deployed code package.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This sample designed as a local development demo only and is not intended for production use. So, no changes required.

Comment on lines +84 to +96
try:
repair_records = json.loads(DATA_FILE.read_text(encoding="utf-8"))
idx = next((i for i, r in enumerate(repair_records) if r["id"] == repair_id), -1)
if idx < 0:
return func.HttpResponse(status_code=404)

if new_title is not None:
repair_records[idx]["title"] = new_title
if new_assignee is not None:
repair_records[idx]["assignedTo"] = new_assignee

DATA_FILE.write_text(json.dumps(repair_records, indent=2), encoding="utf-8")

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

Updating a shared JSON file without any locking/ETag can corrupt data under concurrent PATCH requests (multiple function instances or overlapping executions). If you keep file-based storage for the sample, add at least a simple lock/swap strategy; for real deployments, move to a storage service with concurrency control.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

no changes required.

Comment on lines +70 to +77
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: 'Unauthorized'
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The function implementation returns 401 with no JSON body, but this spec declares an application/json error payload. Align the runtime responses and the OpenAPI contract so clients/tooling don’t disagree about the response shape.

Suggested change
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: 'Unauthorized'

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +18
key=os.environ.get("API_KEY", "")
print(f"Expected API Key: {key}")
return api_key == key
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

API key comparison uses a normal string equality check. For secret comparisons, prefer secrets.compare_digest to avoid timing side-channels (even if low risk for this sample).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No changes required.

Comment on lines +2 to +6
import base64

KEY_LENGTH = 12

key = base64.b64encode(secrets.token_bytes(KEY_LENGTH)).decode("utf-8")[:KEY_LENGTH]
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

This key generation approach slices a base64 string down to 12 characters, reducing entropy and potentially generating characters like '+' and '/' that can be awkward to copy/paste into env files or headers. Prefer a longer, URL-safe token (e.g., secrets.token_urlsafe / token_hex) and avoid truncation so the full randomness is preserved.

Suggested change
import base64
KEY_LENGTH = 12
key = base64.b64encode(secrets.token_bytes(KEY_LENGTH)).decode("utf-8")[:KEY_LENGTH]
KEY_LENGTH = 12
key = secrets.token_urlsafe(KEY_LENGTH)

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No changes required

Comment on lines +144 to +151
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: 'Repair not found'
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

The implementation returns 404 with an empty body, but this spec documents a JSON error payload for 404. Consider returning {"error": "..."} (or adjusting the spec) to keep the contract accurate.

Suggested change
content:
application/json:
schema:
type: object
properties:
error:
type: string
example: 'Repair not found'

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

@YugalPradhan31 YugalPradhan31 Apr 16, 2026

Choose a reason for hiding this comment

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

Updated backend to return JSON body according to frontend

"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "python",
"API_KEY": "{{SECRET_API_KEY}}"
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

local.settings.json uses "{{SECRET_API_KEY}}" placeholder syntax, which is inconsistent with the ${{...}} templating used elsewhere and won’t be automatically resolved by Azure Functions. Consider leaving API_KEY empty in the committed template (or rely on m365agents.local.yml to write it) to avoid confusion/misconfiguration when running without the toolkit.

Suggested change
"API_KEY": "{{SECRET_API_KEY}}"
"API_KEY": ""

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated.

Comment on lines +14 to +18
api_key = (req.headers.get("X-API-Key") or "").strip()
print(f"Received API Key: {api_key}")
key=os.environ.get("API_KEY", "")
print(f"Expected API Key: {key}")
return api_key == key
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

If the API_KEY environment variable is missing/empty, a request with no X-API-Key header will be treated as authorized because both values become "". Require a configured API key (fail closed) and return unauthorized when the expected key is not set.

Suggested change
api_key = (req.headers.get("X-API-Key") or "").strip()
print(f"Received API Key: {api_key}")
key=os.environ.get("API_KEY", "")
print(f"Expected API Key: {key}")
return api_key == key
key = os.environ.get("API_KEY")
if key is None or not key.strip():
logging.error("API_KEY environment variable is not configured")
return False
api_key = (req.headers.get("X-API-Key") or "").strip()
return api_key == key.strip()

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

@YugalPradhan31 YugalPradhan31 Apr 16, 2026

Choose a reason for hiding this comment

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

Updated the logic to handle empty strings.

Comment on lines +22 to +25
def repairs(req: func.HttpRequest) -> func.HttpResponse:
if not _is_api_key_valid(req):
return func.HttpResponse(status_code=401)

Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

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

These 401 responses return an empty body, but the OpenAPI spec in appPackage/apiSpecificationFile/repair.yml declares a JSON error payload for 401. Consider returning a consistent JSON body (and mimetype) so plugin/runtime consumers can reliably parse error details.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

@YugalPradhan31 YugalPradhan31 Apr 16, 2026

Choose a reason for hiding this comment

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

Updated backend to return JSON body, acc to frontend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants