-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Add opaque metadata passthrough to SDK tool definitions
#1864
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,9 @@ public static class CopilotTool | |||||
| /// <summary>The key used in <see cref="AITool.AdditionalProperties"/> to carry the tool's <see cref="CopilotToolDefer"/> deferral mode.</summary> | ||||||
| internal const string DeferKey = "defer"; | ||||||
|
|
||||||
| /// <summary>The key used in <see cref="AITool.AdditionalProperties"/> to carry the tool's opaque host-defined metadata.</summary> | ||||||
| internal const string MetadataKey = "metadata"; | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Defines a tool for use in a <see cref="CopilotSession"/>. | ||||||
| /// </summary> | ||||||
|
|
@@ -87,7 +90,7 @@ static void ApplyToolInvocationBinding(AIFunctionFactoryOptions factoryOptions) | |||||
|
|
||||||
| static void ApplyToolOptions(AIFunctionFactoryOptions factoryOptions, CopilotToolOptions? toolOptions) | ||||||
| { | ||||||
| if (toolOptions is not null && (toolOptions.OverridesBuiltInTool || toolOptions.SkipPermission || toolOptions.Defer is not null)) | ||||||
| if (toolOptions is not null && (toolOptions.OverridesBuiltInTool || toolOptions.SkipPermission || toolOptions.Defer is not null || toolOptions.Metadata is not null)) | ||||||
| { | ||||||
| Dictionary<string, object?> additionalProperties = new(StringComparer.Ordinal); | ||||||
| if (factoryOptions.AdditionalProperties is not null) | ||||||
|
|
@@ -113,6 +116,11 @@ static void ApplyToolOptions(AIFunctionFactoryOptions factoryOptions, CopilotToo | |||||
| additionalProperties[DeferKey] = defer; | ||||||
| } | ||||||
|
|
||||||
| if (toolOptions.Metadata is { } metadata) | ||||||
| { | ||||||
| additionalProperties[MetadataKey] = metadata; | ||||||
| } | ||||||
|
|
||||||
| factoryOptions.AdditionalProperties = additionalProperties; | ||||||
| } | ||||||
| } | ||||||
|
|
@@ -151,6 +159,16 @@ public sealed class CopilotToolOptions | |||||
| /// SDK forwards it to the CLI as the tool's <c>defer</c> mode. Defaults to "auto". | ||||||
| /// </remarks> | ||||||
| public CopilotToolDefer? Defer { get; set; } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Gets or sets opaque, host-defined metadata associated with the tool definition. | ||||||
| /// </summary> | ||||||
| /// <remarks> | ||||||
| /// Keys are namespaced and not part of the stable public API. When set, the SDK forwards | ||||||
| /// the metadata verbatim to the CLI as the tool's <c>metadata</c> object, which the runtime | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: please call it "runtime" not "CLI" since we're decoupling from CLI
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also you don't need to distinguish the SDK from the runtime here, so probably don't even need to mention that at all. From the consumer's point of view, this is a library they are using and the way it's split into SDK-vs-runtime is an implementation detail that's not relevant to them here. Altogether you can just remove the |
||||||
| /// may recognize to inform host-specific behavior. Unknown keys are preserved. | ||||||
| /// </remarks> | ||||||
| public IDictionary<string, object>? Metadata { get; set; } | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we can define this type as A relatively easy fix would be to replace this with:
Suggested change
|
||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1269,6 +1269,12 @@ type Tool struct { | |
| // Defer controls whether the tool may be deferred (loaded lazily via tool | ||
| // search) rather than always pre-loaded. When empty, the runtime decides. | ||
| Defer ToolDefer `json:"defer,omitempty"` | ||
| // Metadata is opaque, host-defined metadata associated with the tool | ||
| // definition. Keys are namespaced and not part of the stable public API; | ||
| // the SDK forwards them verbatim to the runtime, which may recognize | ||
| // specific keys to inform host-specific behavior. Unknown keys are | ||
| // preserved and round-tripped untouched. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment about not mentioning runtime here as if it's a separate thing
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are other places in this PR too but I'll not comment on the others - I'm sure you can find them :) |
||
| Metadata map[string]any `json:"metadata,omitempty"` | ||
| // Handler is optional. When nil, the SDK exposes the tool declaration but does | ||
| // not automatically invoke it. | ||
| Handler ToolHandler `json:"-"` | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,6 +75,10 @@ | |
| * controls whether the tool may be deferred (loaded lazily via tool | ||
| * search) rather than always pre-loaded; {@code null} lets the | ||
| * runtime decide | ||
| * @param metadata | ||
| * opaque, host-defined metadata forwarded verbatim to the runtime; | ||
| * keys are namespaced and not part of the stable public API; | ||
| * {@code null} when unset | ||
| * @see SessionConfig#setTools(java.util.List) | ||
| * @see ToolHandler | ||
| * @since 1.0.0 | ||
|
|
@@ -83,7 +87,8 @@ | |
| public record ToolDefinition(@JsonProperty("name") String name, @JsonProperty("description") String description, | ||
| @JsonProperty("parameters") Object parameters, @JsonIgnore ToolHandler handler, | ||
| @JsonProperty("overridesBuiltInTool") Boolean overridesBuiltInTool, | ||
| @JsonProperty("skipPermission") Boolean skipPermission, @JsonProperty("defer") ToolDefer defer) { | ||
| @JsonProperty("skipPermission") Boolean skipPermission, @JsonProperty("defer") ToolDefer defer, | ||
| @JsonProperty("metadata") Map<String, Object> metadata) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like this would be a breaking change. @edburns What's the recommended way to handle this in Java? Should this be added purely as a setter and not a constructor parameter? Or should it be on a new constructor overload? |
||
|
|
||
| /** | ||
| * Creates a tool definition with a JSON schema for parameters. | ||
|
|
@@ -103,7 +108,7 @@ public record ToolDefinition(@JsonProperty("name") String name, @JsonProperty("d | |
| */ | ||
| public static ToolDefinition create(String name, String description, Map<String, Object> schema, | ||
| ToolHandler handler) { | ||
| return new ToolDefinition(name, description, schema, handler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, handler, null, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -127,7 +132,7 @@ public static ToolDefinition create(String name, String description, Map<String, | |
| */ | ||
| public static ToolDefinition createOverride(String name, String description, Map<String, Object> schema, | ||
| ToolHandler handler) { | ||
| return new ToolDefinition(name, description, schema, handler, true, null, null); | ||
| return new ToolDefinition(name, description, schema, handler, true, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -150,7 +155,7 @@ public static ToolDefinition createOverride(String name, String description, Map | |
| */ | ||
| public static ToolDefinition createSkipPermission(String name, String description, Map<String, Object> schema, | ||
| ToolHandler handler) { | ||
| return new ToolDefinition(name, description, schema, handler, null, true, null); | ||
| return new ToolDefinition(name, description, schema, handler, null, true, null, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -176,7 +181,32 @@ public static ToolDefinition createSkipPermission(String name, String descriptio | |
| */ | ||
| public static ToolDefinition createWithDefer(String name, String description, Map<String, Object> schema, | ||
| ToolHandler handler, ToolDefer defer) { | ||
| return new ToolDefinition(name, description, schema, handler, null, null, defer); | ||
| return new ToolDefinition(name, description, schema, handler, null, null, defer, null); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a tool definition with opaque, host-defined metadata. | ||
| * <p> | ||
| * Use this factory method to attach namespaced metadata that the SDK forwards | ||
| * verbatim to the runtime. The keys are not part of the stable public API; the | ||
| * runtime may recognize specific keys to inform host-specific behavior. | ||
| * | ||
| * @param name | ||
| * the unique name of the tool | ||
| * @param description | ||
| * a description of what the tool does | ||
| * @param schema | ||
| * the JSON Schema as a {@code Map} | ||
| * @param handler | ||
| * the handler function to execute when invoked | ||
| * @param metadata | ||
| * the opaque metadata map forwarded to the runtime | ||
| * @return a new tool definition with the metadata set | ||
| * @since 1.0.7 | ||
| */ | ||
| public static ToolDefinition createWithMetadata(String name, String description, Map<String, Object> schema, | ||
| ToolHandler handler, Map<String, Object> metadata) { | ||
| return new ToolDefinition(name, description, schema, handler, null, null, null, metadata); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -247,7 +277,7 @@ public static List<ToolDefinition> fromClass(Class<?> clazz) { | |
| */ | ||
| @CopilotExperimental | ||
| public ToolDefinition overridesBuiltInTool(boolean value) { | ||
| return new ToolDefinition(name, description, parameters, handler, value, skipPermission, defer); | ||
| return new ToolDefinition(name, description, parameters, handler, value, skipPermission, defer, metadata); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -261,7 +291,7 @@ public ToolDefinition overridesBuiltInTool(boolean value) { | |
| */ | ||
| @CopilotExperimental | ||
| public ToolDefinition skipPermission(boolean value) { | ||
| return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, value, defer); | ||
| return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, value, defer, metadata); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -275,7 +305,23 @@ public ToolDefinition skipPermission(boolean value) { | |
| */ | ||
| @CopilotExperimental | ||
| public ToolDefinition defer(ToolDefer value) { | ||
| return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, skipPermission, value); | ||
| return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, skipPermission, value, | ||
| metadata); | ||
| } | ||
|
|
||
| /** | ||
| * Returns a copy with the opaque {@code metadata} bag set. | ||
| * | ||
| * @param value | ||
| * the opaque, host-defined metadata forwarded verbatim to the | ||
| * runtime; keys are namespaced and not part of the stable public API | ||
| * @return a new {@code ToolDefinition} with the metadata applied | ||
| * @since 1.0.7 | ||
| */ | ||
| @CopilotExperimental | ||
| public ToolDefinition metadata(Map<String, Object> value) { | ||
| return new ToolDefinition(name, description, parameters, handler, overridesBuiltInTool, skipPermission, defer, | ||
| value); | ||
| } | ||
|
|
||
| // ------------------------------------------------------------------ | ||
|
|
@@ -319,7 +365,7 @@ public static <R> ToolDefinition from(String name, String description, Supplier< | |
| R result = handler.get(); | ||
| return CompletableFuture.completedFuture(formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -361,7 +407,7 @@ public static <T1, R> ToolDefinition from(String name, String description, Param | |
| R result = handler.apply(arg1); | ||
| return CompletableFuture.completedFuture(formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -409,7 +455,7 @@ public static <T1, T2, R> ToolDefinition from(String name, String description, P | |
| R result = handler.apply(arg1, arg2); | ||
| return CompletableFuture.completedFuture(formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| // ------------------------------------------------------------------ | ||
|
|
@@ -458,7 +504,7 @@ public static <R> ToolDefinition fromAsync(String name, String description, | |
| } | ||
| return future.thenApply(result -> formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -506,7 +552,7 @@ public static <T1, R> ToolDefinition fromAsync(String name, String description, | |
| } | ||
| return future.thenApply(result -> formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -551,7 +597,7 @@ public static <T1, T2, R> ToolDefinition fromAsync(String name, String descripti | |
| } | ||
| return future.thenApply(result -> formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| // ------------------------------------------------------------------ | ||
|
|
@@ -594,7 +640,7 @@ public static <R> ToolDefinition fromWithToolInvocation(String name, String desc | |
| R result = handler.apply(invocation); | ||
| return CompletableFuture.completedFuture(formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -640,7 +686,7 @@ public static <T1, R> ToolDefinition fromWithToolInvocation(String name, String | |
| R result = handler.apply(arg1, invocation); | ||
| return CompletableFuture.completedFuture(formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| // ------------------------------------------------------------------ | ||
|
|
@@ -689,7 +735,7 @@ public static <R> ToolDefinition fromAsyncWithToolInvocation(String name, String | |
| } | ||
| return future.thenApply(result -> formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -741,7 +787,7 @@ public static <T1, R> ToolDefinition fromAsyncWithToolInvocation(String name, St | |
| } | ||
| return future.thenApply(result -> formatResult(result, mapper)); | ||
| }; | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null); | ||
| return new ToolDefinition(name, description, schema, toolHandler, null, null, null, null); | ||
| } | ||
|
|
||
| // ------------------------------------------------------------------ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -276,7 +276,8 @@ private void writeToolDefinition(PrintWriter out, ExecutableElement method) { | |
| out.println(" },"); | ||
| out.println(" " + overridesArg + ","); | ||
| out.println(" " + skipPermArg + ","); | ||
| out.println(" " + deferArg); | ||
| out.println(" " + deferArg + ","); | ||
| out.println(" null"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we also be adding "metadata" to CopilotTool and including it in the output here instead of null? Or is there no way to represent arbitrary dictionaries on a Java annotation? |
||
| out.print(" )"); | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious if the
[property: JsonPropertyName("metadata")]here makes any difference. I can't see what it would do. Is it possibly redundant?