fix(pat): preserve batch session metadata
This commit is contained in:
+20
-2
@@ -341,13 +341,31 @@ func extractToolAuthMetadata(schema map[string]any, productID string) *ToolAuthM
|
||||
if err := json.Unmarshal(data, &metadata); err != nil {
|
||||
return nil
|
||||
}
|
||||
if len(metadata.RequiredScopes) == 0 && len(metadata.RequiredPermissions) == 0 &&
|
||||
len(metadata.ClientObservedScopes) == 0 && metadata.AuthMetaHash == "" {
|
||||
if !hasMeaningfulToolAuthMetadata(metadata) {
|
||||
return nil
|
||||
}
|
||||
return &metadata
|
||||
}
|
||||
|
||||
func hasMeaningfulToolAuthMetadata(metadata ToolAuthMetadata) bool {
|
||||
return strings.TrimSpace(metadata.Version) != "" ||
|
||||
strings.TrimSpace(metadata.ProductCode) != "" ||
|
||||
strings.TrimSpace(metadata.Domain) != "" ||
|
||||
len(metadata.ClientObservedScopes) > 0 ||
|
||||
len(metadata.RequiredScopes) > 0 ||
|
||||
len(metadata.RequiredPermissions) > 0 ||
|
||||
len(metadata.RecommendedScopes) > 0 ||
|
||||
len(metadata.ExcludedScopes) > 0 ||
|
||||
len(metadata.GrantProductCodes) > 0 ||
|
||||
strings.TrimSpace(metadata.RiskHint) != "" ||
|
||||
strings.TrimSpace(metadata.RiskAction) != "" ||
|
||||
metadata.ConfirmationRequired ||
|
||||
len(metadata.Identities) > 0 ||
|
||||
strings.TrimSpace(metadata.Source) != "" ||
|
||||
strings.TrimSpace(metadata.AuthMetaVersion) != "" ||
|
||||
strings.TrimSpace(metadata.AuthMetaHash) != ""
|
||||
}
|
||||
|
||||
func productGrantAuthMetadata(productID string) *ToolAuthMetadata {
|
||||
productID = strings.TrimSpace(productID)
|
||||
if productID == "" {
|
||||
|
||||
@@ -265,6 +265,51 @@ func TestBuildCatalogCarriesDingTalkAuthMetadata(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCatalogCarriesGrantOnlyAuthMetadata(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
catalog := BuildCatalog([]discovery.RuntimeServer{
|
||||
{
|
||||
Server: market.ServerDescriptor{
|
||||
Key: "calendar-key",
|
||||
DisplayName: "日历",
|
||||
Endpoint: "https://example.com/server/calendar",
|
||||
},
|
||||
Tools: []transport.ToolDescriptor{
|
||||
{
|
||||
Name: "createEvent",
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"x-dingtalk-auth": map[string]any{
|
||||
"grantProductCodes": []any{"calendar"},
|
||||
"recommendedScopes": []any{"calendar.event:create"},
|
||||
"riskAction": "high-risk-write",
|
||||
"confirmationRequired": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
tool, ok := catalog.Products[0].FindTool("createEvent")
|
||||
if !ok {
|
||||
t.Fatalf("FindTool(createEvent) = not found")
|
||||
}
|
||||
if tool.Auth == nil {
|
||||
t.Fatal("tool.Auth = nil")
|
||||
}
|
||||
if got := tool.Auth.GrantProductCodes; len(got) != 1 || got[0] != "calendar" {
|
||||
t.Fatalf("GrantProductCodes = %#v, want calendar", got)
|
||||
}
|
||||
if got := tool.Auth.RecommendedScopes; len(got) != 1 || got[0] != "calendar.event:create" {
|
||||
t.Fatalf("RecommendedScopes = %#v, want calendar.event:create", got)
|
||||
}
|
||||
if !tool.Auth.ConfirmationRequired {
|
||||
t.Fatal("ConfirmationRequired = false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildCatalogFallsBackToProductGrantAuthMetadata(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ grantType 规则:
|
||||
|
||||
if c != nil && c.DryRun() {
|
||||
if usesPlan {
|
||||
planArgs := buildBatchPlanArgs(scopes, productCodes, recommend, grantType, true)
|
||||
planArgs := buildBatchPlanArgs(scopes, productCodes, recommend, grantType, sessionID, true)
|
||||
result, err := callPATBatchPlan(cmd.Context(), c, agentCode, sessionID, planArgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pat chmod plan failed: %w", err)
|
||||
@@ -244,7 +244,7 @@ grantType 规则:
|
||||
sessionID = resolveSessionIDFromEnv()
|
||||
}
|
||||
if usesPlan {
|
||||
planArgs := buildBatchPlanArgs(scopes, productCodes, recommend, grantType, true)
|
||||
planArgs := buildBatchPlanArgs(scopes, productCodes, recommend, grantType, sessionID, true)
|
||||
planResult, err := callPATBatchPlan(cmd.Context(), c, agentCode, sessionID, planArgs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pat chmod plan failed: %w", err)
|
||||
@@ -269,6 +269,7 @@ grantType 规则:
|
||||
toolArgs["agentCode"] = agentCode
|
||||
}
|
||||
if sessionID != "" {
|
||||
batchArgs["sessionId"] = sessionID
|
||||
toolArgs["sessionId"] = sessionID
|
||||
}
|
||||
// Legacy server schema accepted singular "scope"; clone the
|
||||
@@ -405,14 +406,18 @@ func callPATBatchPlan(ctx context.Context, c edition.ToolCaller, agentCode, sess
|
||||
})
|
||||
}
|
||||
|
||||
func buildBatchPlanArgs(scopes []string, productCodes []string, recommend bool, grantType string, dryRun bool) map[string]any {
|
||||
return map[string]any{
|
||||
func buildBatchPlanArgs(scopes []string, productCodes []string, recommend bool, grantType string, sessionID string, dryRun bool) map[string]any {
|
||||
args := map[string]any{
|
||||
"scopes": scopes,
|
||||
"productCodes": productCodes,
|
||||
"recommend": recommend,
|
||||
"grantType": grantType,
|
||||
"dryRun": dryRun,
|
||||
}
|
||||
if sessionID != "" {
|
||||
args["sessionId"] = sessionID
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func extractSelectedScopes(result *edition.ToolResult) ([]string, error) {
|
||||
|
||||
@@ -314,6 +314,37 @@ func TestChmod_productsFlagPlansThenGrantsSelectedScopes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChmod_productsSessionModePassesSessionIDToPlanAndGrant(t *testing.T) {
|
||||
t.Setenv(agentCodeEnv, "qoderwork")
|
||||
fake := &sequenceToolCaller{responses: []string{
|
||||
`{"success":true,"data":{"selectedScopes":["calendar.event:read"]}}`,
|
||||
`{"success":true,"data":{"grantedScopes":["calendar.event:read"]}}`,
|
||||
}}
|
||||
cmd := newChmodCommand(fake)
|
||||
_ = cmd.Flags().Set("products", "calendar")
|
||||
_ = cmd.Flags().Set("session-id", "session-123")
|
||||
|
||||
if err := cmd.RunE(cmd, nil); err != nil {
|
||||
t.Fatalf("chmod RunE error = %v", err)
|
||||
}
|
||||
|
||||
if len(fake.calls) != 2 {
|
||||
t.Fatalf("CallTool count = %d, want 2", len(fake.calls))
|
||||
}
|
||||
if got := fake.calls[0].args["grantType"]; got != "session" {
|
||||
t.Fatalf("plan grantType = %#v, want session", got)
|
||||
}
|
||||
if got := fake.calls[0].args["sessionId"]; got != "session-123" {
|
||||
t.Fatalf("plan sessionId = %#v, want session-123", got)
|
||||
}
|
||||
if got := fake.calls[1].args["grantType"]; got != "session" {
|
||||
t.Fatalf("grant grantType = %#v, want session", got)
|
||||
}
|
||||
if got := fake.calls[1].args["sessionId"]; got != "session-123" {
|
||||
t.Fatalf("grant sessionId = %#v, want session-123", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChmod_recommendFlagPlansThenGrantsWithoutPositionalScopes(t *testing.T) {
|
||||
t.Setenv(agentCodeEnv, "qoderwork")
|
||||
fake := &sequenceToolCaller{responses: []string{
|
||||
|
||||
Reference in New Issue
Block a user