foundry

package
v1.33.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package foundry holds helpers shared across the Microsoft Foundry azd extensions (agents, projects, connections, toolboxes, skills, routines).

Index

Constants

View Source
const CodeInvalidFileRef = "invalid_file_ref"

CodeInvalidFileRef is the structured error code emitted when a $ref include cannot be resolved or written. Foundry extensions surface it through azd's structured error channel.

Variables

View Source
var ErrServiceNotFound = errors.New("service entry not found")

ErrServiceNotFound is returned when a named service entry is absent and creation was not requested. It mirrors yamlnode.ErrNodeNotFound so callers can branch on a sentinel.

Functions

func EntryRef

func EntryRef(entry *yaml.Node) (string, bool)

EntryRef reports the $ref target of a service entry and whether the entry is $ref-backed. It recognizes the same directive key as ResolveFileRefs, so a writer and the resolver agree on which entries are file includes.

func EvaluateCondition added in v1.33.0

func EvaluateCondition(
	value string,
	getenv func(string) string,
) (bool, error)

EvaluateCondition reports whether a string condition enables a service.

It expands ${VAR} references with getenv. A nil getenv treats every variable as unset. An empty condition is enabled. After expansion, only 1, true, TRUE, True, yes, YES, and Yes enable the service; all other values disable it. Foundry ${{...}} expressions are not valid condition values. Malformed templates return an error.

func ExpandEnv

func ExpandEnv(value string, mapping func(string) string) (string, error)

ExpandEnv expands ${VAR} references in value against the azd environment (via mapping) while preserving Foundry server-side ${{...}} expressions verbatim. It supports default values (${VAR:-default}) and multiple expressions, matching drone/envsubst semantics for the ${VAR} portion. On expansion error the original value is returned unchanged alongside the error.

This is the single shared expander every Foundry field, in every Foundry extension, should route through so ${VAR} and ${{...}} are handled consistently. drone/envsubst cannot parse ${{...}}, so each span is masked with a sentinel placeholder, a single Eval expands the ${VAR} references, then the spans are restored. Masking rather than splitting preserves full ${VAR:-default} semantics even when a ${{...}} expression is the default value (e.g. ${MISSING:-${{event.body}}}). A ${VAR} inside a ${{...}} span is left as-is, since the span is reserved for Foundry.

func ResolveFileRefs

func ResolveFileRefs(cfg map[string]any, projectRoot string, opts ...ResolveOption) (map[string]any, error)

ResolveFileRefs resolves $ref file includes within a Foundry resource service configuration.

In the separate-services azure.yaml shape every Foundry resource is its own service entry, so each owning extension calls ResolveFileRefs on its resource's inline map: the entry keys that reach the extension over gRPC, where they arrive as a structpb.Struct decoded to map[string]any. The core ServiceConfig fields (host, the service key, uses) are stripped by core and never appear here. cfg therefore takes any of these shapes:

  • A service-entry-level $ref. In azure.yaml the service entry can be authored with host: plus $ref (and optional overlay keys). Core removes ServiceConfig fields such as host before the extension sees the config, so the cfg map passed here has $ref at its top level.
  • A deployment array-item $ref. Deployments stay an array on the project service, so each item in deployments may be its own $ref (e.g. ./deployments/gpt-4o.yaml).
  • Any nested $ref reached while walking the entry (a $ref inside a loaded file, or a sibling value), since resolution is recursive over every map and sequence node.

projectRoot is the directory that holds azure.yaml; relative $ref targets at the top level resolve against it, and rebased project/instructions paths are anchored to it.

Any object that contains a "$ref" string is replaced by the referenced YAML or JSON file, with the object's remaining keys overlaid on top. The overlay is a shallow, top-level merge: sibling scalars, arrays, and objects each replace the loaded value wholesale. Nested $ref directives inside a loaded file resolve relative to that file's own directory, and that file's relative project, instructions, and $ref paths are rebased so the returned config has every path anchored to projectRoot in clean, forward-slash form. Inline path values authored directly in azure.yaml are left exactly as written.

$ref values that are URLs are rejected (remote includes are not supported yet); only local relative or absolute file paths are accepted. Cyclic and excessively deep include chains return an error rather than looping. $ref targets are treated as trusted input, the same trust level as azure.yaml itself.

Path keys beyond the two core owns are rebased only when named with WithPathKeys.

Types

type EditTarget

type EditTarget int

EditTarget selects where a write to a service entry lands.

const (
	// EditInline writes the field on the service entry in the holding document, beside any
	// $ref. This is the spec §2.4 default (append inline): ResolveFileRefs reads such a key as
	// an overlay override layered on top of the referenced file, so the write is read back as
	// the winning value.
	EditInline EditTarget = iota

	// EditRefFile follows the entry's $ref and writes the field into the referenced split file
	// instead. If the entry is not $ref-backed it falls back to an inline write.
	EditRefFile
)

type ResolveOption added in v1.33.0

type ResolveOption func(*resolveOptions)

ResolveOption configures a call to ResolveFileRefs.

func WithPathKeys added in v1.33.0

func WithPathKeys(keys ...string) ResolveOption

WithPathKeys declares keys whose relative string values are filesystem paths.

Core rebases the path keys it owns -- project and instructions -- so that a value written inside a $ref file still resolves once it has been spliced into a document in another directory. A key core does not know about arrives verbatim, which leaves it resolving against the wrong directory: an evaluator pulled in from evaluators/quality.yaml carries `source: ./quality.json`, which then means quality.json beside the configuration rather than beside the file it was written in. The extension that owns those keys names them here.

type YAMLDocument

type YAMLDocument struct {
	// contains filtered or unexported fields
}

YAMLDocument is an editable, comment-preserving azure.yaml (or a referenced split file). It wraps a braydonk yaml.Node tree so edits keep comments, key order, and block-scalar style, matching how azd core round-trips azure.yaml.

It is the $ref-aware write counterpart to ResolveFileRefs: EntryRef recognizes a $ref entry with the same key the resolver uses, and EditRefFile resolves the split-file path with the resolver's shared path logic, so reads and writes of $ref entries agree. It is also intended for the #8049 composition command write path.

Edits mutate the tree in memory; call Save to persist. Writes through EditRefFile lazily load the referenced file, and Save persists every file touched this way alongside the main one.

func LoadYAMLDocument

func LoadYAMLDocument(path string) (*YAMLDocument, error)

LoadYAMLDocument reads and parses the YAML file at path into an editable document.

func ParseYAMLDocument

func ParseYAMLDocument(path string, data []byte) (*YAMLDocument, error)

ParseYAMLDocument parses data as the YAML document located (logically) at path. path is used to resolve relative $ref split-file targets and as the destination for Save; it need not exist on disk yet. Empty input yields an empty document whose root is created on first edit.

func (*YAMLDocument) Append added in v1.33.0

func (d *YAMLDocument) Append(path string, value any) error

Append adds value to the sequence at a yamlnode path, creating the sequence when it is absent -- a catalog is written into a file that does not list it yet.

func (*YAMLDocument) Bytes

func (d *YAMLDocument) Bytes() ([]byte, error)

Bytes serializes the document, preserving comments, key order, and block-scalar style with two-space indentation (the azure.yaml convention used by azd core).

func (*YAMLDocument) Find added in v1.33.0

func (d *YAMLDocument) Find(path string) (*yaml.Node, error)

Find returns the node at a yamlnode path such as "evaluators[0].source".

func (*YAMLDocument) Root added in v1.33.0

func (d *YAMLDocument) Root(create bool) (*yaml.Node, error)

Root returns the document's root mapping, creating it when the document is empty and create is set, or (nil, nil) when it is empty and create is not.

The rest of this type speaks in service entries, which is the shape azure.yaml has. An extension that edits a document of its own shape works from here instead, so that loading, saving, and $ref handling stay in one place rather than being reimplemented per extension.

func (*YAMLDocument) Save

func (d *YAMLDocument) Save() error

Save writes the document back to its path, then persists every referenced split file edited through EditRefFile.

func (*YAMLDocument) ServiceEntry

func (d *YAMLDocument) ServiceEntry(name string, create bool) (*yaml.Node, error)

ServiceEntry returns the mapping node for services.<name>. When create is true a missing services mapping and/or entry are added (and returned); when false a missing entry returns ErrServiceNotFound.

func (*YAMLDocument) Set added in v1.33.0

func (d *YAMLDocument) Set(path string, value any) error

Set writes value at a yamlnode path.

Absent intermediate nodes are not created unless the path marks them with yamlnode's `?` qualifier, so writing into a section that may not exist yet is Set("catalog?.dataset", v). Comments already on the value being replaced are carried over, since the document belongs to whoever wrote it.

func (*YAMLDocument) SetServiceField

func (d *YAMLDocument) SetServiceField(name, key string, value any, target EditTarget) error

SetServiceField sets services.<name>.<key> to value, creating the service entry if missing. It is $ref-aware:

  • EditInline writes the key on the entry node beside any $ref. ResolveFileRefs reads it as an overlay override on top of the referenced file.
  • EditRefFile writes the key into the file named by the entry's $ref instead (resolved relative to this document with the resolver's shared path logic). If the entry has no $ref it falls back to an inline write.

value may be any value yamlnode.Encode accepts (scalars, sequences, mappings).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL