diff --git a/connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go b/connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go index b6a452657a..f9b2f4b88d 100644 --- a/connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go +++ b/connect-go/gen/proto/wg/cosmo/node/v1/node.pb.go @@ -1010,9 +1010,10 @@ func (x *SelfRegisterResponse) GetRegistrationInfo() *RegistrationInfo { } type GenerateQueryRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - SchemaHash string `protobuf:"bytes,1,opt,name=schema_hash,json=schemaHash,proto3" json:"schema_hash,omitempty"` - Prompt string `protobuf:"bytes,2,opt,name=prompt,proto3" json:"prompt,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // Copied from RouterConfig.version in the active router configuration. + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + Prompt string `protobuf:"bytes,2,opt,name=prompt,proto3" json:"prompt,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1047,9 +1048,9 @@ func (*GenerateQueryRequest) Descriptor() ([]byte, []int) { return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{10} } -func (x *GenerateQueryRequest) GetSchemaHash() string { +func (x *GenerateQueryRequest) GetVersion() string { if x != nil { - return x.SchemaHash + return x.Version } return "" } @@ -5183,10 +5184,9 @@ const file_wg_cosmo_node_v1_node_proto_rawDesc = "" + "\x14SelfRegisterResponse\x126\n" + "\bresponse\x18\x01 \x01(\v2\x1a.wg.cosmo.node.v1.ResponseR\bresponse\x12S\n" + "\x10registrationInfo\x18\x02 \x01(\v2\".wg.cosmo.node.v1.RegistrationInfoH\x00R\x10registrationInfo\x88\x01\x01B\x13\n" + - "\x11_registrationInfo\"O\n" + - "\x14GenerateQueryRequest\x12\x1f\n" + - "\vschema_hash\x18\x01 \x01(\tR\n" + - "schemaHash\x12\x16\n" + + "\x11_registrationInfo\"H\n" + + "\x14GenerateQueryRequest\x12\x18\n" + + "\aversion\x18\x01 \x01(\tR\aversion\x12\x16\n" + "\x06prompt\x18\x02 \x01(\tR\x06prompt\"\x96\x01\n" + "\x15GenerateQueryResponse\x126\n" + "\bresponse\x18\x01 \x01(\v2\x1a.wg.cosmo.node.v1.ResponseR\bresponse\x12;\n" + diff --git a/connect/src/wg/cosmo/node/v1/node_pb.ts b/connect/src/wg/cosmo/node/v1/node_pb.ts index 8bc656290f..9256ba2bf7 100644 --- a/connect/src/wg/cosmo/node/v1/node_pb.ts +++ b/connect/src/wg/cosmo/node/v1/node_pb.ts @@ -12,7 +12,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file wg/cosmo/node/v1/node.proto. */ export const file_wg_cosmo_node_v1_node: GenFile = /*@__PURE__*/ - fileDesc("Cht3Zy9jb3Ntby9ub2RlL3YxL25vZGUucHJvdG8SEHdnLmNvc21vLm5vZGUudjEiOQoIU3ViZ3JhcGgSCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRITCgtyb3V0aW5nX3VybBgDIAEoCSKOAgohRmVhdHVyZUZsYWdSb3V0ZXJFeGVjdXRpb25Db25maWdzEnUKG2NvbmZpZ19ieV9mZWF0dXJlX2ZsYWdfbmFtZRgBIAMoCzJQLndnLmNvc21vLm5vZGUudjEuRmVhdHVyZUZsYWdSb3V0ZXJFeGVjdXRpb25Db25maWdzLkNvbmZpZ0J5RmVhdHVyZUZsYWdOYW1lRW50cnkacgocQ29uZmlnQnlGZWF0dXJlRmxhZ05hbWVFbnRyeRILCgNrZXkYASABKAkSQQoFdmFsdWUYAiABKAsyMi53Zy5jb3Ntby5ub2RlLnYxLkZlYXR1cmVGbGFnUm91dGVyRXhlY3V0aW9uQ29uZmlnOgI4ASKgAQogRmVhdHVyZUZsYWdSb3V0ZXJFeGVjdXRpb25Db25maWcSPAoNZW5naW5lX2NvbmZpZxgBIAEoCzIlLndnLmNvc21vLm5vZGUudjEuRW5naW5lQ29uZmlndXJhdGlvbhIPCgd2ZXJzaW9uGAIgASgJEi0KCXN1YmdyYXBocxgDIAMoCzIaLndnLmNvc21vLm5vZGUudjEuU3ViZ3JhcGginAIKDFJvdXRlckNvbmZpZxI8Cg1lbmdpbmVfY29uZmlnGAEgASgLMiUud2cuY29zbW8ubm9kZS52MS5FbmdpbmVDb25maWd1cmF0aW9uEg8KB3ZlcnNpb24YAiABKAkSLQoJc3ViZ3JhcGhzGAMgAygLMhoud2cuY29zbW8ubm9kZS52MS5TdWJncmFwaBJWChRmZWF0dXJlX2ZsYWdfY29uZmlncxgEIAEoCzIzLndnLmNvc21vLm5vZGUudjEuRmVhdHVyZUZsYWdSb3V0ZXJFeGVjdXRpb25Db25maWdzSACIAQESHQoVY29tcGF0aWJpbGl0eV92ZXJzaW9uGAUgASgJQhcKFV9mZWF0dXJlX2ZsYWdfY29uZmlncyJbCghSZXNwb25zZRItCgRjb2RlGAEgASgOMh8ud2cuY29zbW8uY29tbW9uLkVudW1TdGF0dXNDb2RlEhQKB2RldGFpbHMYAiABKAlIAIgBAUIKCghfZGV0YWlscyIvCg5SZXNwb25zZVN0YXR1cxIMCgRjb2RlGAEgASgFEg8KB21lc3NhZ2UYAiABKAkiZQoQUmVnaXN0cmF0aW9uSW5mbxI3Cg5hY2NvdW50X2xpbWl0cxgBIAEoCzIfLndnLmNvc21vLm5vZGUudjEuQWNjb3VudExpbWl0cxIYChBncmFwaF9wdWJsaWNfa2V5GAIgASgJIiwKDUFjY291bnRMaW1pdHMSGwoTdHJhY2Vfc2FtcGxpbmdfcmF0ZRgBIAEoAiIVChNTZWxmUmVnaXN0ZXJSZXF1ZXN0IpwBChRTZWxmUmVnaXN0ZXJSZXNwb25zZRIsCghyZXNwb25zZRgBIAEoCzIaLndnLmNvc21vLm5vZGUudjEuUmVzcG9uc2USQQoQcmVnaXN0cmF0aW9uSW5mbxgCIAEoCzIiLndnLmNvc21vLm5vZGUudjEuUmVnaXN0cmF0aW9uSW5mb0gAiAEBQhMKEV9yZWdpc3RyYXRpb25JbmZvIjsKFEdlbmVyYXRlUXVlcnlSZXF1ZXN0EhMKC3NjaGVtYV9oYXNoGAEgASgJEg4KBnByb21wdBgCIAEoCSKFAQoVR2VuZXJhdGVRdWVyeVJlc3BvbnNlEiwKCHJlc3BvbnNlGAEgASgLMhoud2cuY29zbW8ubm9kZS52MS5SZXNwb25zZRI0CgVxdWVyeRgCIAEoCzIgLndnLmNvc21vLm5vZGUudjEuU2F0aXNmaWVkUXVlcnlIAIgBAUIICgZfcXVlcnkiqwEKDlNhdGlzZmllZFF1ZXJ5EhMKC2Rlc2NyaXB0aW9uGAEgASgJEhAKCGRvY3VtZW50GAIgASgJEhYKDm9wZXJhdGlvbl9uYW1lGAMgASgJEkAKDm9wZXJhdGlvbl90eXBlGAQgASgOMigud2cuY29zbW8ubm9kZS52MS5TYXRpc2ZpZWRPcGVyYXRpb25UeXBlEhgKEHZhcmlhYmxlc19zY2hlbWEYBSABKAki5AMKE0VuZ2luZUNvbmZpZ3VyYXRpb24SHAoUZGVmYXVsdEZsdXNoSW50ZXJ2YWwYASABKAMSTAoZZGF0YXNvdXJjZV9jb25maWd1cmF0aW9ucxgCIAMoCzIpLndnLmNvc21vLm5vZGUudjEuRGF0YVNvdXJjZUNvbmZpZ3VyYXRpb24SQgoUZmllbGRfY29uZmlndXJhdGlvbnMYAyADKAsyJC53Zy5jb3Ntby5ub2RlLnYxLkZpZWxkQ29uZmlndXJhdGlvbhIVCg1ncmFwaHFsU2NoZW1hGAQgASgJEkAKE3R5cGVfY29uZmlndXJhdGlvbnMYBSADKAsyIy53Zy5jb3Ntby5ub2RlLnYxLlR5cGVDb25maWd1cmF0aW9uElAKDnN0cmluZ19zdG9yYWdlGAYgAygLMjgud2cuY29zbW8ubm9kZS52MS5FbmdpbmVDb25maWd1cmF0aW9uLlN0cmluZ1N0b3JhZ2VFbnRyeRIiChVncmFwaHFsX2NsaWVudF9zY2hlbWEYByABKAlIAIgBARo0ChJTdHJpbmdTdG9yYWdlRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4AUIYChZfZ3JhcGhxbF9jbGllbnRfc2NoZW1hIsYHChdEYXRhU291cmNlQ29uZmlndXJhdGlvbhIuCgRraW5kGAEgASgOMiAud2cuY29zbW8ubm9kZS52MS5EYXRhU291cmNlS2luZBIvCgpyb290X25vZGVzGAIgAygLMhsud2cuY29zbW8ubm9kZS52MS5UeXBlRmllbGQSMAoLY2hpbGRfbm9kZXMYAyADKAsyGy53Zy5jb3Ntby5ub2RlLnYxLlR5cGVGaWVsZBImCh5vdmVycmlkZV9maWVsZF9wYXRoX2Zyb21fYWxpYXMYBCABKAgSQgoOY3VzdG9tX2dyYXBocWwYBSABKAsyKi53Zy5jb3Ntby5ub2RlLnYxLkRhdGFTb3VyY2VDdXN0b21fR3JhcGhRTBJACg1jdXN0b21fc3RhdGljGAYgASgLMikud2cuY29zbW8ubm9kZS52MS5EYXRhU291cmNlQ3VzdG9tX1N0YXRpYxI8CgpkaXJlY3RpdmVzGAcgAygLMigud2cuY29zbW8ubm9kZS52MS5EaXJlY3RpdmVDb25maWd1cmF0aW9uEh8KF3JlcXVlc3RfdGltZW91dF9zZWNvbmRzGAggASgDEgoKAmlkGAkgASgJEi0KBGtleXMYCiADKAsyHy53Zy5jb3Ntby5ub2RlLnYxLlJlcXVpcmVkRmllbGQSMQoIcHJvdmlkZXMYCyADKAsyHy53Zy5jb3Ntby5ub2RlLnYxLlJlcXVpcmVkRmllbGQSMQoIcmVxdWlyZXMYDCADKAsyHy53Zy5jb3Ntby5ub2RlLnYxLlJlcXVpcmVkRmllbGQSPwoNY3VzdG9tX2V2ZW50cxgNIAEoCzIoLndnLmNvc21vLm5vZGUudjEuRGF0YVNvdXJjZUN1c3RvbUV2ZW50cxJJChFlbnRpdHlfaW50ZXJmYWNlcxgOIAMoCzIuLndnLmNvc21vLm5vZGUudjEuRW50aXR5SW50ZXJmYWNlQ29uZmlndXJhdGlvbhJJChFpbnRlcmZhY2Vfb2JqZWN0cxgPIAMoCzIuLndnLmNvc21vLm5vZGUudjEuRW50aXR5SW50ZXJmYWNlQ29uZmlndXJhdGlvbhI/ChJjb3N0X2NvbmZpZ3VyYXRpb24YECABKAsyIy53Zy5jb3Ntby5ub2RlLnYxLkNvc3RDb25maWd1cmF0aW9uElIKHGVudGl0eV9jYWNoaW5nX2NvbmZpZ3VyYXRpb24YESABKAsyLC53Zy5jb3Ntby5ub2RlLnYxLkVudGl0eUNhY2hpbmdDb25maWd1cmF0aW9uIpsCChpFbnRpdHlDYWNoaW5nQ29uZmlndXJhdGlvbhJPChtlbnRpdHlfY2FjaGVfY29uZmlndXJhdGlvbnMYASADKAsyKi53Zy5jb3Ntby5ub2RlLnYxLkVudGl0eUNhY2hlQ29uZmlndXJhdGlvbhJXCh9jYWNoZV9pbnZhbGlkYXRlX2NvbmZpZ3VyYXRpb25zGAIgAygLMi4ud2cuY29zbW8ubm9kZS52MS5DYWNoZUludmFsaWRhdGVDb25maWd1cmF0aW9uElMKHWNhY2hlX3BvcHVsYXRlX2NvbmZpZ3VyYXRpb25zGAMgAygLMiwud2cuY29zbW8ubm9kZS52MS5DYWNoZVBvcHVsYXRlQ29uZmlndXJhdGlvbiK1AQoYRW50aXR5Q2FjaGVDb25maWd1cmF0aW9uEhEKCXR5cGVfbmFtZRgBIAEoCRIXCg9tYXhfYWdlX3NlY29uZHMYAiABKAMSFwoPaW5jbHVkZV9oZWFkZXJzGAMgASgIEhoKEnBhcnRpYWxfY2FjaGVfbG9hZBgEIAEoCBITCgtzaGFkb3dfbW9kZRgFIAEoCBIjChtub3RfZm91bmRfY2FjaGVfdHRsX3NlY29uZHMYBiABKAMiZAocQ2FjaGVJbnZhbGlkYXRlQ29uZmlndXJhdGlvbhISCgpmaWVsZF9uYW1lGAEgASgJEhYKDm9wZXJhdGlvbl90eXBlGAIgASgJEhgKEGVudGl0eV90eXBlX25hbWUYAyABKAkiewoaQ2FjaGVQb3B1bGF0ZUNvbmZpZ3VyYXRpb24SEgoKZmllbGRfbmFtZRgBIAEoCRIWCg5vcGVyYXRpb25fdHlwZRgCIAEoCRIXCg9tYXhfYWdlX3NlY29uZHMYAyABKAMSGAoQZW50aXR5X3R5cGVfbmFtZRgEIAEoCSLAAwoRQ29zdENvbmZpZ3VyYXRpb24SQQoNZmllbGRfd2VpZ2h0cxgBIAMoCzIqLndnLmNvc21vLm5vZGUudjEuRmllbGRXZWlnaHRDb25maWd1cmF0aW9uEkAKCmxpc3Rfc2l6ZXMYAiADKAsyLC53Zy5jb3Ntby5ub2RlLnYxLkZpZWxkTGlzdFNpemVDb25maWd1cmF0aW9uEkoKDHR5cGVfd2VpZ2h0cxgDIAMoCzI0LndnLmNvc21vLm5vZGUudjEuQ29zdENvbmZpZ3VyYXRpb24uVHlwZVdlaWdodHNFbnRyeRJlChpkaXJlY3RpdmVfYXJndW1lbnRfd2VpZ2h0cxgEIAMoCzJBLndnLmNvc21vLm5vZGUudjEuQ29zdENvbmZpZ3VyYXRpb24uRGlyZWN0aXZlQXJndW1lbnRXZWlnaHRzRW50cnkaMgoQVHlwZVdlaWdodHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAU6AjgBGj8KHURpcmVjdGl2ZUFyZ3VtZW50V2VpZ2h0c0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBToCOAEiowMKGEZpZWxkV2VpZ2h0Q29uZmlndXJhdGlvbhIRCgl0eXBlX25hbWUYASABKAkSEgoKZmllbGRfbmFtZRgCIAEoCRITCgZ3ZWlnaHQYAyABKAVIAIgBARJZChBhcmd1bWVudF93ZWlnaHRzGAQgAygLMj8ud2cuY29zbW8ubm9kZS52MS5GaWVsZFdlaWdodENvbmZpZ3VyYXRpb24uQXJndW1lbnRXZWlnaHRzRW50cnkSbAoaZGlyZWN0aXZlX2FyZ3VtZW50X3dlaWdodHMYBSADKAsySC53Zy5jb3Ntby5ub2RlLnYxLkZpZWxkV2VpZ2h0Q29uZmlndXJhdGlvbi5EaXJlY3RpdmVBcmd1bWVudFdlaWdodHNFbnRyeRo2ChRBcmd1bWVudFdlaWdodHNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAU6AjgBGj8KHURpcmVjdGl2ZUFyZ3VtZW50V2VpZ2h0c0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBToCOAFCCQoHX3dlaWdodCLsAQoaRmllbGRMaXN0U2l6ZUNvbmZpZ3VyYXRpb24SEQoJdHlwZV9uYW1lGAEgASgJEhIKCmZpZWxkX25hbWUYAiABKAkSGQoMYXNzdW1lZF9zaXplGAMgASgFSACIAQESGQoRc2xpY2luZ19hcmd1bWVudHMYBCADKAkSFAoMc2l6ZWRfZmllbGRzGAUgAygJEikKHHJlcXVpcmVfb25lX3NsaWNpbmdfYXJndW1lbnQYBiABKAhIAYgBAUIPCg1fYXNzdW1lZF9zaXplQh8KHV9yZXF1aXJlX29uZV9zbGljaW5nX2FyZ3VtZW50IlwKFUFyZ3VtZW50Q29uZmlndXJhdGlvbhIMCgRuYW1lGAEgASgJEjUKC3NvdXJjZV90eXBlGAIgASgOMiAud2cuY29zbW8ubm9kZS52MS5Bcmd1bWVudFNvdXJjZSIlCgZTY29wZXMSGwoTcmVxdWlyZWRfYW5kX3Njb3BlcxgBIAMoCSKvAQoaQXV0aG9yaXphdGlvbkNvbmZpZ3VyYXRpb24SHwoXcmVxdWlyZXNfYXV0aGVudGljYXRpb24YASABKAgSNAoScmVxdWlyZWRfb3Jfc2NvcGVzGAIgAygLMhgud2cuY29zbW8ubm9kZS52MS5TY29wZXMSOgoYcmVxdWlyZWRfb3Jfc2NvcGVzX2J5X29yGAMgAygLMhgud2cuY29zbW8ubm9kZS52MS5TY29wZXMi1QIKEkZpZWxkQ29uZmlndXJhdGlvbhIRCgl0eXBlX25hbWUYASABKAkSEgoKZmllbGRfbmFtZRgCIAEoCRJIChdhcmd1bWVudHNfY29uZmlndXJhdGlvbhgDIAMoCzInLndnLmNvc21vLm5vZGUudjEuQXJndW1lbnRDb25maWd1cmF0aW9uElEKG2F1dGhvcml6YXRpb25fY29uZmlndXJhdGlvbhgEIAEoCzIsLndnLmNvc21vLm5vZGUudjEuQXV0aG9yaXphdGlvbkNvbmZpZ3VyYXRpb24SWQodc3Vic2NyaXB0aW9uX2ZpbHRlcl9jb25kaXRpb24YBSABKAsyLS53Zy5jb3Ntby5ub2RlLnYxLlN1YnNjcmlwdGlvbkZpbHRlckNvbmRpdGlvbkgAiAEBQiAKHl9zdWJzY3JpcHRpb25fZmlsdGVyX2NvbmRpdGlvbiI5ChFUeXBlQ29uZmlndXJhdGlvbhIRCgl0eXBlX25hbWUYASABKAkSEQoJcmVuYW1lX3RvGAIgASgJInwKCVR5cGVGaWVsZBIRCgl0eXBlX25hbWUYASABKAkSEwoLZmllbGRfbmFtZXMYAiADKAkSHAoUZXh0ZXJuYWxfZmllbGRfbmFtZXMYAyADKAkSKQohcmVxdWlyZV9mZXRjaF9yZWFzb25zX2ZpZWxkX25hbWVzGAQgAygJIjkKEEZpZWxkQ29vcmRpbmF0ZXMSEgoKZmllbGRfbmFtZRgBIAEoCRIRCgl0eXBlX25hbWUYAiABKAkiawoRRmllbGRTZXRDb25kaXRpb24SQgoWZmllbGRfY29vcmRpbmF0ZXNfcGF0aBgBIAMoCzIiLndnLmNvc21vLm5vZGUudjEuRmllbGRDb29yZGluYXRlcxISCgpmaWVsZF9wYXRoGAIgAygJIqcBCg1SZXF1aXJlZEZpZWxkEhEKCXR5cGVfbmFtZRgBIAEoCRISCgpmaWVsZF9uYW1lGAIgASgJEhUKDXNlbGVjdGlvbl9zZXQYAyABKAkSHwoXZGlzYWJsZV9lbnRpdHlfcmVzb2x2ZXIYBCABKAgSNwoKY29uZGl0aW9ucxgFIAMoCzIjLndnLmNvc21vLm5vZGUudjEuRmllbGRTZXRDb25kaXRpb24iWAocRW50aXR5SW50ZXJmYWNlQ29uZmlndXJhdGlvbhIbChNpbnRlcmZhY2VfdHlwZV9uYW1lGAEgASgJEhsKE2NvbmNyZXRlX3R5cGVfbmFtZXMYAiADKAkijQUKEkZldGNoQ29uZmlndXJhdGlvbhI0CgN1cmwYASABKAsyJy53Zy5jb3Ntby5ub2RlLnYxLkNvbmZpZ3VyYXRpb25WYXJpYWJsZRIsCgZtZXRob2QYAiABKA4yHC53Zy5jb3Ntby5ub2RlLnYxLkhUVFBNZXRob2QSQAoGaGVhZGVyGAMgAygLMjAud2cuY29zbW8ubm9kZS52MS5GZXRjaENvbmZpZ3VyYXRpb24uSGVhZGVyRW50cnkSNQoEYm9keRgEIAEoCzInLndnLmNvc21vLm5vZGUudjEuQ29uZmlndXJhdGlvblZhcmlhYmxlEjYKBXF1ZXJ5GAUgAygLMicud2cuY29zbW8ubm9kZS52MS5VUkxRdWVyeUNvbmZpZ3VyYXRpb24SFwoPdXJsX2VuY29kZV9ib2R5GAcgASgIEjEKBG10bHMYCCABKAsyIy53Zy5jb3Ntby5ub2RlLnYxLk1UTFNDb25maWd1cmF0aW9uEjkKCGJhc2VfdXJsGAkgASgLMicud2cuY29zbW8ubm9kZS52MS5Db25maWd1cmF0aW9uVmFyaWFibGUSNQoEcGF0aBgKIAEoCzInLndnLmNvc21vLm5vZGUudjEuQ29uZmlndXJhdGlvblZhcmlhYmxlEkQKDmh0dHBfcHJveHlfdXJsGAsgASgLMicud2cuY29zbW8ubm9kZS52MS5Db25maWd1cmF0aW9uVmFyaWFibGVIAIgBARpLCgtIZWFkZXJFbnRyeRILCgNrZXkYASABKAkSKwoFdmFsdWUYAiABKAsyHC53Zy5jb3Ntby5ub2RlLnYxLkhUVFBIZWFkZXI6AjgBQhEKD19odHRwX3Byb3h5X3VybCJlChVTdGF0dXNDb2RlVHlwZU1hcHBpbmcSEwoLc3RhdHVzX2NvZGUYASABKAMSEQoJdHlwZV9uYW1lGAIgASgJEiQKHGluamVjdF9zdGF0dXNfY29kZV9pbnRvX2JvZHkYAyABKAgikwMKGERhdGFTb3VyY2VDdXN0b21fR3JhcGhRTBIzCgVmZXRjaBgBIAEoCzIkLndnLmNvc21vLm5vZGUudjEuRmV0Y2hDb25maWd1cmF0aW9uEkgKDHN1YnNjcmlwdGlvbhgCIAEoCzIyLndnLmNvc21vLm5vZGUudjEuR3JhcGhRTFN1YnNjcmlwdGlvbkNvbmZpZ3VyYXRpb24SRAoKZmVkZXJhdGlvbhgDIAEoCzIwLndnLmNvc21vLm5vZGUudjEuR3JhcGhRTEZlZGVyYXRpb25Db25maWd1cmF0aW9uEjkKD3Vwc3RyZWFtX3NjaGVtYRgEIAEoCzIgLndnLmNvc21vLm5vZGUudjEuSW50ZXJuZWRTdHJpbmcSRAoZY3VzdG9tX3NjYWxhcl90eXBlX2ZpZWxkcxgGIAMoCzIhLndnLmNvc21vLm5vZGUudjEuU2luZ2xlVHlwZUZpZWxkEjEKBGdycGMYByABKAsyIy53Zy5jb3Ntby5ub2RlLnYxLkdSUENDb25maWd1cmF0aW9uIpABChFHUlBDQ29uZmlndXJhdGlvbhIuCgdtYXBwaW5nGAEgASgLMh0ud2cuY29zbW8ubm9kZS52MS5HUlBDTWFwcGluZxIUCgxwcm90b19zY2hlbWEYAiABKAkSNQoGcGx1Z2luGAMgASgLMiUud2cuY29zbW8ubm9kZS52MS5QbHVnaW5Db25maWd1cmF0aW9uIjcKDkltYWdlUmVmZXJlbmNlEhIKCnJlcG9zaXRvcnkYASABKAkSEQoJcmVmZXJlbmNlGAIgASgJIogBChNQbHVnaW5Db25maWd1cmF0aW9uEgwKBG5hbWUYASABKAkSDwoHdmVyc2lvbhgCIAEoCRI+Cg9pbWFnZV9yZWZlcmVuY2UYAyABKAsyIC53Zy5jb3Ntby5ub2RlLnYxLkltYWdlUmVmZXJlbmNlSACIAQFCEgoQX2ltYWdlX3JlZmVyZW5jZSIjChBTU0xDb25maWd1cmF0aW9uEg8KB2VuYWJsZWQYASABKAgi2wIKC0dSUENNYXBwaW5nEg8KB3ZlcnNpb24YASABKAUSDwoHc2VydmljZRgCIAEoCRI+ChJvcGVyYXRpb25fbWFwcGluZ3MYAyADKAsyIi53Zy5jb3Ntby5ub2RlLnYxLk9wZXJhdGlvbk1hcHBpbmcSOAoPZW50aXR5X21hcHBpbmdzGAQgAygLMh8ud2cuY29zbW8ubm9kZS52MS5FbnRpdHlNYXBwaW5nEj8KE3R5cGVfZmllbGRfbWFwcGluZ3MYBSADKAsyIi53Zy5jb3Ntby5ub2RlLnYxLlR5cGVGaWVsZE1hcHBpbmcSNAoNZW51bV9tYXBwaW5ncxgGIAMoCzIdLndnLmNvc21vLm5vZGUudjEuRW51bU1hcHBpbmcSOQoQcmVzb2x2ZV9tYXBwaW5ncxgHIAMoCzIfLndnLmNvc21vLm5vZGUudjEuTG9va3VwTWFwcGluZyKpAQoNTG9va3VwTWFwcGluZxIqCgR0eXBlGAEgASgOMhwud2cuY29zbW8ubm9kZS52MS5Mb29rdXBUeXBlEjwKDmxvb2t1cF9tYXBwaW5nGAIgASgLMiQud2cuY29zbW8ubm9kZS52MS5Mb29rdXBGaWVsZE1hcHBpbmcSCwoDcnBjGAMgASgJEg8KB3JlcXVlc3QYBCABKAkSEAoIcmVzcG9uc2UYBSABKAkiWQoSTG9va3VwRmllbGRNYXBwaW5nEgwKBHR5cGUYASABKAkSNQoNZmllbGRfbWFwcGluZxgCIAEoCzIeLndnLmNvc21vLm5vZGUudjEuRmllbGRNYXBwaW5nIoYBChBPcGVyYXRpb25NYXBwaW5nEi0KBHR5cGUYASABKA4yHy53Zy5jb3Ntby5ub2RlLnYxLk9wZXJhdGlvblR5cGUSEAoIb3JpZ2luYWwYAiABKAkSDgoGbWFwcGVkGAMgASgJEg8KB3JlcXVlc3QYBCABKAkSEAoIcmVzcG9uc2UYBSABKAkitgEKDUVudGl0eU1hcHBpbmcSEQoJdHlwZV9uYW1lGAEgASgJEgwKBGtpbmQYAiABKAkSCwoDa2V5GAMgASgJEgsKA3JwYxgEIAEoCRIPCgdyZXF1ZXN0GAUgASgJEhAKCHJlc3BvbnNlGAYgASgJEkcKF3JlcXVpcmVkX2ZpZWxkX21hcHBpbmdzGAcgAygLMiYud2cuY29zbW8ubm9kZS52MS5SZXF1aXJlZEZpZWxkTWFwcGluZyJ9ChRSZXF1aXJlZEZpZWxkTWFwcGluZxI1Cg1maWVsZF9tYXBwaW5nGAEgASgLMh4ud2cuY29zbW8ubm9kZS52MS5GaWVsZE1hcHBpbmcSCwoDcnBjGAIgASgJEg8KB3JlcXVlc3QYAyABKAkSEAoIcmVzcG9uc2UYBCABKAkiWAoQVHlwZUZpZWxkTWFwcGluZxIMCgR0eXBlGAEgASgJEjYKDmZpZWxkX21hcHBpbmdzGAIgAygLMh4ud2cuY29zbW8ubm9kZS52MS5GaWVsZE1hcHBpbmcibgoMRmllbGRNYXBwaW5nEhAKCG9yaWdpbmFsGAEgASgJEg4KBm1hcHBlZBgCIAEoCRI8ChFhcmd1bWVudF9tYXBwaW5ncxgDIAMoCzIhLndnLmNvc21vLm5vZGUudjEuQXJndW1lbnRNYXBwaW5nIjMKD0FyZ3VtZW50TWFwcGluZxIQCghvcmlnaW5hbBgBIAEoCRIOCgZtYXBwZWQYAiABKAkiTwoLRW51bU1hcHBpbmcSDAoEdHlwZRgBIAEoCRIyCgZ2YWx1ZXMYAiADKAsyIi53Zy5jb3Ntby5ub2RlLnYxLkVudW1WYWx1ZU1hcHBpbmciNAoQRW51bVZhbHVlTWFwcGluZxIQCghvcmlnaW5hbBgBIAEoCRIOCgZtYXBwZWQYAiABKAkiagoXTmF0c1N0cmVhbUNvbmZpZ3VyYXRpb24SFQoNY29uc3VtZXJfbmFtZRgBIAEoCRITCgtzdHJlYW1fbmFtZRgCIAEoCRIjChtjb25zdW1lcl9pbmFjdGl2ZV90aHJlc2hvbGQYAyABKAUiwwEKFk5hdHNFdmVudENvbmZpZ3VyYXRpb24STgoaZW5naW5lX2V2ZW50X2NvbmZpZ3VyYXRpb24YASABKAsyKi53Zy5jb3Ntby5ub2RlLnYxLkVuZ2luZUV2ZW50Q29uZmlndXJhdGlvbhIQCghzdWJqZWN0cxgCIAMoCRJHChRzdHJlYW1fY29uZmlndXJhdGlvbhgDIAEoCzIpLndnLmNvc21vLm5vZGUudjEuTmF0c1N0cmVhbUNvbmZpZ3VyYXRpb24ieQoXS2Fma2FFdmVudENvbmZpZ3VyYXRpb24STgoaZW5naW5lX2V2ZW50X2NvbmZpZ3VyYXRpb24YASABKAsyKi53Zy5jb3Ntby5ub2RlLnYxLkVuZ2luZUV2ZW50Q29uZmlndXJhdGlvbhIOCgZ0b3BpY3MYAiADKAkiewoXUmVkaXNFdmVudENvbmZpZ3VyYXRpb24STgoaZW5naW5lX2V2ZW50X2NvbmZpZ3VyYXRpb24YASABKAsyKi53Zy5jb3Ntby5ub2RlLnYxLkVuZ2luZUV2ZW50Q29uZmlndXJhdGlvbhIQCghjaGFubmVscxgCIAMoCSKBAQoYRW5naW5lRXZlbnRDb25maWd1cmF0aW9uEhMKC3Byb3ZpZGVyX2lkGAEgASgJEikKBHR5cGUYAiABKA4yGy53Zy5jb3Ntby5ub2RlLnYxLkV2ZW50VHlwZRIRCgl0eXBlX25hbWUYAyABKAkSEgoKZmllbGRfbmFtZRgEIAEoCSLEAQoWRGF0YVNvdXJjZUN1c3RvbUV2ZW50cxI2CgRuYXRzGAEgAygLMigud2cuY29zbW8ubm9kZS52MS5OYXRzRXZlbnRDb25maWd1cmF0aW9uEjgKBWthZmthGAIgAygLMikud2cuY29zbW8ubm9kZS52MS5LYWZrYUV2ZW50Q29uZmlndXJhdGlvbhI4CgVyZWRpcxgDIAMoCzIpLndnLmNvc21vLm5vZGUudjEuUmVkaXNFdmVudENvbmZpZ3VyYXRpb24iUAoXRGF0YVNvdXJjZUN1c3RvbV9TdGF0aWMSNQoEZGF0YRgBIAEoCzInLndnLmNvc21vLm5vZGUudjEuQ29uZmlndXJhdGlvblZhcmlhYmxlIuUBChVDb25maWd1cmF0aW9uVmFyaWFibGUSOQoEa2luZBgBIAEoDjIrLndnLmNvc21vLm5vZGUudjEuQ29uZmlndXJhdGlvblZhcmlhYmxlS2luZBIfChdzdGF0aWNfdmFyaWFibGVfY29udGVudBgCIAEoCRIhChllbnZpcm9ubWVudF92YXJpYWJsZV9uYW1lGAMgASgJEioKImVudmlyb25tZW50X3ZhcmlhYmxlX2RlZmF1bHRfdmFsdWUYBCABKAkSIQoZcGxhY2Vob2xkZXJfdmFyaWFibGVfbmFtZRgFIAEoCSJDChZEaXJlY3RpdmVDb25maWd1cmF0aW9uEhYKDmRpcmVjdGl2ZV9uYW1lGAEgASgJEhEKCXJlbmFtZV90bxgCIAEoCSI0ChVVUkxRdWVyeUNvbmZpZ3VyYXRpb24SDAoEbmFtZRgBIAEoCRINCgV2YWx1ZRgCIAEoCSJFCgpIVFRQSGVhZGVyEjcKBnZhbHVlcxgBIAMoCzInLndnLmNvc21vLm5vZGUudjEuQ29uZmlndXJhdGlvblZhcmlhYmxlIpwBChFNVExTQ29uZmlndXJhdGlvbhI0CgNrZXkYASABKAsyJy53Zy5jb3Ntby5ub2RlLnYxLkNvbmZpZ3VyYXRpb25WYXJpYWJsZRI1CgRjZXJ0GAIgASgLMicud2cuY29zbW8ubm9kZS52MS5Db25maWd1cmF0aW9uVmFyaWFibGUSGgoSaW5zZWN1cmVTa2lwVmVyaWZ5GAMgASgIIsUCCiBHcmFwaFFMU3Vic2NyaXB0aW9uQ29uZmlndXJhdGlvbhIPCgdlbmFibGVkGAEgASgIEjQKA3VybBgCIAEoCzInLndnLmNvc21vLm5vZGUudjEuQ29uZmlndXJhdGlvblZhcmlhYmxlEhMKBnVzZVNTRRgDIAEoCEgAiAEBEkMKCHByb3RvY29sGAQgASgOMiwud2cuY29zbW8uY29tbW9uLkdyYXBoUUxTdWJzY3JpcHRpb25Qcm90b2NvbEgBiAEBEk8KFHdlYnNvY2tldFN1YnByb3RvY29sGAUgASgOMiwud2cuY29zbW8uY29tbW9uLkdyYXBoUUxXZWJzb2NrZXRTdWJwcm90b2NvbEgCiAEBQgkKB191c2VTU0VCCwoJX3Byb3RvY29sQhcKFV93ZWJzb2NrZXRTdWJwcm90b2NvbCJFCh5HcmFwaFFMRmVkZXJhdGlvbkNvbmZpZ3VyYXRpb24SDwoHZW5hYmxlZBgBIAEoCBISCgpzZXJ2aWNlU2RsGAIgASgJIh0KDkludGVybmVkU3RyaW5nEgsKA2tleRgBIAEoCSI4Cg9TaW5nbGVUeXBlRmllbGQSEQoJdHlwZV9uYW1lGAEgASgJEhIKCmZpZWxkX25hbWUYAiABKAkiPgoaU3Vic2NyaXB0aW9uRmllbGRDb25kaXRpb24SEgoKZmllbGRfcGF0aBgBIAMoCRIMCgRqc29uGAIgASgJIqMCChtTdWJzY3JpcHRpb25GaWx0ZXJDb25kaXRpb24SOgoDYW5kGAEgAygLMi0ud2cuY29zbW8ubm9kZS52MS5TdWJzY3JpcHRpb25GaWx0ZXJDb25kaXRpb24SPQoCaW4YAiABKAsyLC53Zy5jb3Ntby5ub2RlLnYxLlN1YnNjcmlwdGlvbkZpZWxkQ29uZGl0aW9uSACIAQESPwoDbm90GAMgASgLMi0ud2cuY29zbW8ubm9kZS52MS5TdWJzY3JpcHRpb25GaWx0ZXJDb25kaXRpb25IAYgBARI5CgJvchgEIAMoCzItLndnLmNvc21vLm5vZGUudjEuU3Vic2NyaXB0aW9uRmlsdGVyQ29uZGl0aW9uQgUKA19pbkIGCgRfbm90IkgKFUNhY2hlV2FybWVyT3BlcmF0aW9ucxIvCgpvcGVyYXRpb25zGAEgAygLMhsud2cuY29zbW8ubm9kZS52MS5PcGVyYXRpb24ibgoJT3BlcmF0aW9uEjMKB3JlcXVlc3QYASABKAsyIi53Zy5jb3Ntby5ub2RlLnYxLk9wZXJhdGlvblJlcXVlc3QSLAoGY2xpZW50GAIgASgLMhwud2cuY29zbW8ubm9kZS52MS5DbGllbnRJbmZvImoKEE9wZXJhdGlvblJlcXVlc3QSFgoOb3BlcmF0aW9uX25hbWUYASABKAkSDQoFcXVlcnkYAiABKAkSLwoKZXh0ZW5zaW9ucxgDIAEoCzIbLndnLmNvc21vLm5vZGUudjEuRXh0ZW5zaW9uIkYKCUV4dGVuc2lvbhI5Cg9wZXJzaXN0ZWRfcXVlcnkYASABKAsyIC53Zy5jb3Ntby5ub2RlLnYxLlBlcnNpc3RlZFF1ZXJ5IjYKDlBlcnNpc3RlZFF1ZXJ5EhMKC3NoYTI1Nl9oYXNoGAEgASgJEg8KB3ZlcnNpb24YAiABKAUiKwoKQ2xpZW50SW5mbxIMCgRuYW1lGAEgASgJEg8KB3ZlcnNpb24YAiABKAkqQwoWU2F0aXNmaWVkT3BlcmF0aW9uVHlwZRIJCgVRVUVSWRAAEgwKCE1VVEFUSU9OEAESEAoMU1VCU0NSSVBUSU9OEAIqggEKG0FyZ3VtZW50UmVuZGVyQ29uZmlndXJhdGlvbhIbChdSRU5ERVJfQVJHVU1FTlRfREVGQVVMVBAAEiQKIFJFTkRFUl9BUkdVTUVOVF9BU19HUkFQSFFMX1ZBTFVFEAESIAocUkVOREVSX0FSR1VNRU5UX0FTX0FSUkFZX0NTVhACKjYKDkFyZ3VtZW50U291cmNlEhAKDE9CSkVDVF9GSUVMRBAAEhIKDkZJRUxEX0FSR1VNRU5UEAEqNQoORGF0YVNvdXJjZUtpbmQSCgoGU1RBVElDEAASCwoHR1JBUEhRTBABEgoKBlBVQlNVQhACKlwKCkxvb2t1cFR5cGUSGwoXTE9PS1VQX1RZUEVfVU5TUEVDSUZJRUQQABIXChNMT09LVVBfVFlQRV9SRVNPTFZFEAESGAoUTE9PS1VQX1RZUEVfUkVRVUlSRVMQAiqHAQoNT3BlcmF0aW9uVHlwZRIeChpPUEVSQVRJT05fVFlQRV9VTlNQRUNJRklFRBAAEhgKFE9QRVJBVElPTl9UWVBFX1FVRVJZEAESGwoXT1BFUkFUSU9OX1RZUEVfTVVUQVRJT04QAhIfChtPUEVSQVRJT05fVFlQRV9TVUJTQ1JJUFRJT04QAyo0CglFdmVudFR5cGUSCwoHUFVCTElTSBAAEgsKB1JFUVVFU1QQARINCglTVUJTQ1JJQkUQAiqGAQoZQ29uZmlndXJhdGlvblZhcmlhYmxlS2luZBIhCh1TVEFUSUNfQ09ORklHVVJBVElPTl9WQVJJQUJMRRAAEh4KGkVOVl9DT05GSUdVUkFUSU9OX1ZBUklBQkxFEAESJgoiUExBQ0VIT0xERVJfQ09ORklHVVJBVElPTl9WQVJJQUJMRRACKkEKCkhUVFBNZXRob2QSBwoDR0VUEAASCAoEUE9TVBABEgcKA1BVVBACEgoKBkRFTEVURRADEgsKB09QVElPTlMQBDLSAQoLTm9kZVNlcnZpY2USXwoMU2VsZlJlZ2lzdGVyEiUud2cuY29zbW8ubm9kZS52MS5TZWxmUmVnaXN0ZXJSZXF1ZXN0GiYud2cuY29zbW8ubm9kZS52MS5TZWxmUmVnaXN0ZXJSZXNwb25zZSIAEmIKDUdlbmVyYXRlUXVlcnkSJi53Zy5jb3Ntby5ub2RlLnYxLkdlbmVyYXRlUXVlcnlSZXF1ZXN0Gicud2cuY29zbW8ubm9kZS52MS5HZW5lcmF0ZVF1ZXJ5UmVzcG9uc2UiAGIGcHJvdG8z", [file_wg_cosmo_common_common]); + fileDesc("Cht3Zy9jb3Ntby9ub2RlL3YxL25vZGUucHJvdG8SEHdnLmNvc21vLm5vZGUudjEiOQoIU3ViZ3JhcGgSCgoCaWQYASABKAkSDAoEbmFtZRgCIAEoCRITCgtyb3V0aW5nX3VybBgDIAEoCSKOAgohRmVhdHVyZUZsYWdSb3V0ZXJFeGVjdXRpb25Db25maWdzEnUKG2NvbmZpZ19ieV9mZWF0dXJlX2ZsYWdfbmFtZRgBIAMoCzJQLndnLmNvc21vLm5vZGUudjEuRmVhdHVyZUZsYWdSb3V0ZXJFeGVjdXRpb25Db25maWdzLkNvbmZpZ0J5RmVhdHVyZUZsYWdOYW1lRW50cnkacgocQ29uZmlnQnlGZWF0dXJlRmxhZ05hbWVFbnRyeRILCgNrZXkYASABKAkSQQoFdmFsdWUYAiABKAsyMi53Zy5jb3Ntby5ub2RlLnYxLkZlYXR1cmVGbGFnUm91dGVyRXhlY3V0aW9uQ29uZmlnOgI4ASKgAQogRmVhdHVyZUZsYWdSb3V0ZXJFeGVjdXRpb25Db25maWcSPAoNZW5naW5lX2NvbmZpZxgBIAEoCzIlLndnLmNvc21vLm5vZGUudjEuRW5naW5lQ29uZmlndXJhdGlvbhIPCgd2ZXJzaW9uGAIgASgJEi0KCXN1YmdyYXBocxgDIAMoCzIaLndnLmNvc21vLm5vZGUudjEuU3ViZ3JhcGginAIKDFJvdXRlckNvbmZpZxI8Cg1lbmdpbmVfY29uZmlnGAEgASgLMiUud2cuY29zbW8ubm9kZS52MS5FbmdpbmVDb25maWd1cmF0aW9uEg8KB3ZlcnNpb24YAiABKAkSLQoJc3ViZ3JhcGhzGAMgAygLMhoud2cuY29zbW8ubm9kZS52MS5TdWJncmFwaBJWChRmZWF0dXJlX2ZsYWdfY29uZmlncxgEIAEoCzIzLndnLmNvc21vLm5vZGUudjEuRmVhdHVyZUZsYWdSb3V0ZXJFeGVjdXRpb25Db25maWdzSACIAQESHQoVY29tcGF0aWJpbGl0eV92ZXJzaW9uGAUgASgJQhcKFV9mZWF0dXJlX2ZsYWdfY29uZmlncyJbCghSZXNwb25zZRItCgRjb2RlGAEgASgOMh8ud2cuY29zbW8uY29tbW9uLkVudW1TdGF0dXNDb2RlEhQKB2RldGFpbHMYAiABKAlIAIgBAUIKCghfZGV0YWlscyIvCg5SZXNwb25zZVN0YXR1cxIMCgRjb2RlGAEgASgFEg8KB21lc3NhZ2UYAiABKAkiZQoQUmVnaXN0cmF0aW9uSW5mbxI3Cg5hY2NvdW50X2xpbWl0cxgBIAEoCzIfLndnLmNvc21vLm5vZGUudjEuQWNjb3VudExpbWl0cxIYChBncmFwaF9wdWJsaWNfa2V5GAIgASgJIiwKDUFjY291bnRMaW1pdHMSGwoTdHJhY2Vfc2FtcGxpbmdfcmF0ZRgBIAEoAiIVChNTZWxmUmVnaXN0ZXJSZXF1ZXN0IpwBChRTZWxmUmVnaXN0ZXJSZXNwb25zZRIsCghyZXNwb25zZRgBIAEoCzIaLndnLmNvc21vLm5vZGUudjEuUmVzcG9uc2USQQoQcmVnaXN0cmF0aW9uSW5mbxgCIAEoCzIiLndnLmNvc21vLm5vZGUudjEuUmVnaXN0cmF0aW9uSW5mb0gAiAEBQhMKEV9yZWdpc3RyYXRpb25JbmZvIjcKFEdlbmVyYXRlUXVlcnlSZXF1ZXN0Eg8KB3ZlcnNpb24YASABKAkSDgoGcHJvbXB0GAIgASgJIoUBChVHZW5lcmF0ZVF1ZXJ5UmVzcG9uc2USLAoIcmVzcG9uc2UYASABKAsyGi53Zy5jb3Ntby5ub2RlLnYxLlJlc3BvbnNlEjQKBXF1ZXJ5GAIgASgLMiAud2cuY29zbW8ubm9kZS52MS5TYXRpc2ZpZWRRdWVyeUgAiAEBQggKBl9xdWVyeSKrAQoOU2F0aXNmaWVkUXVlcnkSEwoLZGVzY3JpcHRpb24YASABKAkSEAoIZG9jdW1lbnQYAiABKAkSFgoOb3BlcmF0aW9uX25hbWUYAyABKAkSQAoOb3BlcmF0aW9uX3R5cGUYBCABKA4yKC53Zy5jb3Ntby5ub2RlLnYxLlNhdGlzZmllZE9wZXJhdGlvblR5cGUSGAoQdmFyaWFibGVzX3NjaGVtYRgFIAEoCSLkAwoTRW5naW5lQ29uZmlndXJhdGlvbhIcChRkZWZhdWx0Rmx1c2hJbnRlcnZhbBgBIAEoAxJMChlkYXRhc291cmNlX2NvbmZpZ3VyYXRpb25zGAIgAygLMikud2cuY29zbW8ubm9kZS52MS5EYXRhU291cmNlQ29uZmlndXJhdGlvbhJCChRmaWVsZF9jb25maWd1cmF0aW9ucxgDIAMoCzIkLndnLmNvc21vLm5vZGUudjEuRmllbGRDb25maWd1cmF0aW9uEhUKDWdyYXBocWxTY2hlbWEYBCABKAkSQAoTdHlwZV9jb25maWd1cmF0aW9ucxgFIAMoCzIjLndnLmNvc21vLm5vZGUudjEuVHlwZUNvbmZpZ3VyYXRpb24SUAoOc3RyaW5nX3N0b3JhZ2UYBiADKAsyOC53Zy5jb3Ntby5ub2RlLnYxLkVuZ2luZUNvbmZpZ3VyYXRpb24uU3RyaW5nU3RvcmFnZUVudHJ5EiIKFWdyYXBocWxfY2xpZW50X3NjaGVtYRgHIAEoCUgAiAEBGjQKElN0cmluZ1N0b3JhZ2VFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBQhgKFl9ncmFwaHFsX2NsaWVudF9zY2hlbWEixgcKF0RhdGFTb3VyY2VDb25maWd1cmF0aW9uEi4KBGtpbmQYASABKA4yIC53Zy5jb3Ntby5ub2RlLnYxLkRhdGFTb3VyY2VLaW5kEi8KCnJvb3Rfbm9kZXMYAiADKAsyGy53Zy5jb3Ntby5ub2RlLnYxLlR5cGVGaWVsZBIwCgtjaGlsZF9ub2RlcxgDIAMoCzIbLndnLmNvc21vLm5vZGUudjEuVHlwZUZpZWxkEiYKHm92ZXJyaWRlX2ZpZWxkX3BhdGhfZnJvbV9hbGlhcxgEIAEoCBJCCg5jdXN0b21fZ3JhcGhxbBgFIAEoCzIqLndnLmNvc21vLm5vZGUudjEuRGF0YVNvdXJjZUN1c3RvbV9HcmFwaFFMEkAKDWN1c3RvbV9zdGF0aWMYBiABKAsyKS53Zy5jb3Ntby5ub2RlLnYxLkRhdGFTb3VyY2VDdXN0b21fU3RhdGljEjwKCmRpcmVjdGl2ZXMYByADKAsyKC53Zy5jb3Ntby5ub2RlLnYxLkRpcmVjdGl2ZUNvbmZpZ3VyYXRpb24SHwoXcmVxdWVzdF90aW1lb3V0X3NlY29uZHMYCCABKAMSCgoCaWQYCSABKAkSLQoEa2V5cxgKIAMoCzIfLndnLmNvc21vLm5vZGUudjEuUmVxdWlyZWRGaWVsZBIxCghwcm92aWRlcxgLIAMoCzIfLndnLmNvc21vLm5vZGUudjEuUmVxdWlyZWRGaWVsZBIxCghyZXF1aXJlcxgMIAMoCzIfLndnLmNvc21vLm5vZGUudjEuUmVxdWlyZWRGaWVsZBI/Cg1jdXN0b21fZXZlbnRzGA0gASgLMigud2cuY29zbW8ubm9kZS52MS5EYXRhU291cmNlQ3VzdG9tRXZlbnRzEkkKEWVudGl0eV9pbnRlcmZhY2VzGA4gAygLMi4ud2cuY29zbW8ubm9kZS52MS5FbnRpdHlJbnRlcmZhY2VDb25maWd1cmF0aW9uEkkKEWludGVyZmFjZV9vYmplY3RzGA8gAygLMi4ud2cuY29zbW8ubm9kZS52MS5FbnRpdHlJbnRlcmZhY2VDb25maWd1cmF0aW9uEj8KEmNvc3RfY29uZmlndXJhdGlvbhgQIAEoCzIjLndnLmNvc21vLm5vZGUudjEuQ29zdENvbmZpZ3VyYXRpb24SUgocZW50aXR5X2NhY2hpbmdfY29uZmlndXJhdGlvbhgRIAEoCzIsLndnLmNvc21vLm5vZGUudjEuRW50aXR5Q2FjaGluZ0NvbmZpZ3VyYXRpb24imwIKGkVudGl0eUNhY2hpbmdDb25maWd1cmF0aW9uEk8KG2VudGl0eV9jYWNoZV9jb25maWd1cmF0aW9ucxgBIAMoCzIqLndnLmNvc21vLm5vZGUudjEuRW50aXR5Q2FjaGVDb25maWd1cmF0aW9uElcKH2NhY2hlX2ludmFsaWRhdGVfY29uZmlndXJhdGlvbnMYAiADKAsyLi53Zy5jb3Ntby5ub2RlLnYxLkNhY2hlSW52YWxpZGF0ZUNvbmZpZ3VyYXRpb24SUwodY2FjaGVfcG9wdWxhdGVfY29uZmlndXJhdGlvbnMYAyADKAsyLC53Zy5jb3Ntby5ub2RlLnYxLkNhY2hlUG9wdWxhdGVDb25maWd1cmF0aW9uIrUBChhFbnRpdHlDYWNoZUNvbmZpZ3VyYXRpb24SEQoJdHlwZV9uYW1lGAEgASgJEhcKD21heF9hZ2Vfc2Vjb25kcxgCIAEoAxIXCg9pbmNsdWRlX2hlYWRlcnMYAyABKAgSGgoScGFydGlhbF9jYWNoZV9sb2FkGAQgASgIEhMKC3NoYWRvd19tb2RlGAUgASgIEiMKG25vdF9mb3VuZF9jYWNoZV90dGxfc2Vjb25kcxgGIAEoAyJkChxDYWNoZUludmFsaWRhdGVDb25maWd1cmF0aW9uEhIKCmZpZWxkX25hbWUYASABKAkSFgoOb3BlcmF0aW9uX3R5cGUYAiABKAkSGAoQZW50aXR5X3R5cGVfbmFtZRgDIAEoCSJ7ChpDYWNoZVBvcHVsYXRlQ29uZmlndXJhdGlvbhISCgpmaWVsZF9uYW1lGAEgASgJEhYKDm9wZXJhdGlvbl90eXBlGAIgASgJEhcKD21heF9hZ2Vfc2Vjb25kcxgDIAEoAxIYChBlbnRpdHlfdHlwZV9uYW1lGAQgASgJIsADChFDb3N0Q29uZmlndXJhdGlvbhJBCg1maWVsZF93ZWlnaHRzGAEgAygLMioud2cuY29zbW8ubm9kZS52MS5GaWVsZFdlaWdodENvbmZpZ3VyYXRpb24SQAoKbGlzdF9zaXplcxgCIAMoCzIsLndnLmNvc21vLm5vZGUudjEuRmllbGRMaXN0U2l6ZUNvbmZpZ3VyYXRpb24SSgoMdHlwZV93ZWlnaHRzGAMgAygLMjQud2cuY29zbW8ubm9kZS52MS5Db3N0Q29uZmlndXJhdGlvbi5UeXBlV2VpZ2h0c0VudHJ5EmUKGmRpcmVjdGl2ZV9hcmd1bWVudF93ZWlnaHRzGAQgAygLMkEud2cuY29zbW8ubm9kZS52MS5Db3N0Q29uZmlndXJhdGlvbi5EaXJlY3RpdmVBcmd1bWVudFdlaWdodHNFbnRyeRoyChBUeXBlV2VpZ2h0c0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBToCOAEaPwodRGlyZWN0aXZlQXJndW1lbnRXZWlnaHRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgFOgI4ASKjAwoYRmllbGRXZWlnaHRDb25maWd1cmF0aW9uEhEKCXR5cGVfbmFtZRgBIAEoCRISCgpmaWVsZF9uYW1lGAIgASgJEhMKBndlaWdodBgDIAEoBUgAiAEBElkKEGFyZ3VtZW50X3dlaWdodHMYBCADKAsyPy53Zy5jb3Ntby5ub2RlLnYxLkZpZWxkV2VpZ2h0Q29uZmlndXJhdGlvbi5Bcmd1bWVudFdlaWdodHNFbnRyeRJsChpkaXJlY3RpdmVfYXJndW1lbnRfd2VpZ2h0cxgFIAMoCzJILndnLmNvc21vLm5vZGUudjEuRmllbGRXZWlnaHRDb25maWd1cmF0aW9uLkRpcmVjdGl2ZUFyZ3VtZW50V2VpZ2h0c0VudHJ5GjYKFEFyZ3VtZW50V2VpZ2h0c0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoBToCOAEaPwodRGlyZWN0aXZlQXJndW1lbnRXZWlnaHRzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgFOgI4AUIJCgdfd2VpZ2h0IuwBChpGaWVsZExpc3RTaXplQ29uZmlndXJhdGlvbhIRCgl0eXBlX25hbWUYASABKAkSEgoKZmllbGRfbmFtZRgCIAEoCRIZCgxhc3N1bWVkX3NpemUYAyABKAVIAIgBARIZChFzbGljaW5nX2FyZ3VtZW50cxgEIAMoCRIUCgxzaXplZF9maWVsZHMYBSADKAkSKQoccmVxdWlyZV9vbmVfc2xpY2luZ19hcmd1bWVudBgGIAEoCEgBiAEBQg8KDV9hc3N1bWVkX3NpemVCHwodX3JlcXVpcmVfb25lX3NsaWNpbmdfYXJndW1lbnQiXAoVQXJndW1lbnRDb25maWd1cmF0aW9uEgwKBG5hbWUYASABKAkSNQoLc291cmNlX3R5cGUYAiABKA4yIC53Zy5jb3Ntby5ub2RlLnYxLkFyZ3VtZW50U291cmNlIiUKBlNjb3BlcxIbChNyZXF1aXJlZF9hbmRfc2NvcGVzGAEgAygJIq8BChpBdXRob3JpemF0aW9uQ29uZmlndXJhdGlvbhIfChdyZXF1aXJlc19hdXRoZW50aWNhdGlvbhgBIAEoCBI0ChJyZXF1aXJlZF9vcl9zY29wZXMYAiADKAsyGC53Zy5jb3Ntby5ub2RlLnYxLlNjb3BlcxI6ChhyZXF1aXJlZF9vcl9zY29wZXNfYnlfb3IYAyADKAsyGC53Zy5jb3Ntby5ub2RlLnYxLlNjb3BlcyLVAgoSRmllbGRDb25maWd1cmF0aW9uEhEKCXR5cGVfbmFtZRgBIAEoCRISCgpmaWVsZF9uYW1lGAIgASgJEkgKF2FyZ3VtZW50c19jb25maWd1cmF0aW9uGAMgAygLMicud2cuY29zbW8ubm9kZS52MS5Bcmd1bWVudENvbmZpZ3VyYXRpb24SUQobYXV0aG9yaXphdGlvbl9jb25maWd1cmF0aW9uGAQgASgLMiwud2cuY29zbW8ubm9kZS52MS5BdXRob3JpemF0aW9uQ29uZmlndXJhdGlvbhJZCh1zdWJzY3JpcHRpb25fZmlsdGVyX2NvbmRpdGlvbhgFIAEoCzItLndnLmNvc21vLm5vZGUudjEuU3Vic2NyaXB0aW9uRmlsdGVyQ29uZGl0aW9uSACIAQFCIAoeX3N1YnNjcmlwdGlvbl9maWx0ZXJfY29uZGl0aW9uIjkKEVR5cGVDb25maWd1cmF0aW9uEhEKCXR5cGVfbmFtZRgBIAEoCRIRCglyZW5hbWVfdG8YAiABKAkifAoJVHlwZUZpZWxkEhEKCXR5cGVfbmFtZRgBIAEoCRITCgtmaWVsZF9uYW1lcxgCIAMoCRIcChRleHRlcm5hbF9maWVsZF9uYW1lcxgDIAMoCRIpCiFyZXF1aXJlX2ZldGNoX3JlYXNvbnNfZmllbGRfbmFtZXMYBCADKAkiOQoQRmllbGRDb29yZGluYXRlcxISCgpmaWVsZF9uYW1lGAEgASgJEhEKCXR5cGVfbmFtZRgCIAEoCSJrChFGaWVsZFNldENvbmRpdGlvbhJCChZmaWVsZF9jb29yZGluYXRlc19wYXRoGAEgAygLMiIud2cuY29zbW8ubm9kZS52MS5GaWVsZENvb3JkaW5hdGVzEhIKCmZpZWxkX3BhdGgYAiADKAkipwEKDVJlcXVpcmVkRmllbGQSEQoJdHlwZV9uYW1lGAEgASgJEhIKCmZpZWxkX25hbWUYAiABKAkSFQoNc2VsZWN0aW9uX3NldBgDIAEoCRIfChdkaXNhYmxlX2VudGl0eV9yZXNvbHZlchgEIAEoCBI3Cgpjb25kaXRpb25zGAUgAygLMiMud2cuY29zbW8ubm9kZS52MS5GaWVsZFNldENvbmRpdGlvbiJYChxFbnRpdHlJbnRlcmZhY2VDb25maWd1cmF0aW9uEhsKE2ludGVyZmFjZV90eXBlX25hbWUYASABKAkSGwoTY29uY3JldGVfdHlwZV9uYW1lcxgCIAMoCSKNBQoSRmV0Y2hDb25maWd1cmF0aW9uEjQKA3VybBgBIAEoCzInLndnLmNvc21vLm5vZGUudjEuQ29uZmlndXJhdGlvblZhcmlhYmxlEiwKBm1ldGhvZBgCIAEoDjIcLndnLmNvc21vLm5vZGUudjEuSFRUUE1ldGhvZBJACgZoZWFkZXIYAyADKAsyMC53Zy5jb3Ntby5ub2RlLnYxLkZldGNoQ29uZmlndXJhdGlvbi5IZWFkZXJFbnRyeRI1CgRib2R5GAQgASgLMicud2cuY29zbW8ubm9kZS52MS5Db25maWd1cmF0aW9uVmFyaWFibGUSNgoFcXVlcnkYBSADKAsyJy53Zy5jb3Ntby5ub2RlLnYxLlVSTFF1ZXJ5Q29uZmlndXJhdGlvbhIXCg91cmxfZW5jb2RlX2JvZHkYByABKAgSMQoEbXRscxgIIAEoCzIjLndnLmNvc21vLm5vZGUudjEuTVRMU0NvbmZpZ3VyYXRpb24SOQoIYmFzZV91cmwYCSABKAsyJy53Zy5jb3Ntby5ub2RlLnYxLkNvbmZpZ3VyYXRpb25WYXJpYWJsZRI1CgRwYXRoGAogASgLMicud2cuY29zbW8ubm9kZS52MS5Db25maWd1cmF0aW9uVmFyaWFibGUSRAoOaHR0cF9wcm94eV91cmwYCyABKAsyJy53Zy5jb3Ntby5ub2RlLnYxLkNvbmZpZ3VyYXRpb25WYXJpYWJsZUgAiAEBGksKC0hlYWRlckVudHJ5EgsKA2tleRgBIAEoCRIrCgV2YWx1ZRgCIAEoCzIcLndnLmNvc21vLm5vZGUudjEuSFRUUEhlYWRlcjoCOAFCEQoPX2h0dHBfcHJveHlfdXJsImUKFVN0YXR1c0NvZGVUeXBlTWFwcGluZxITCgtzdGF0dXNfY29kZRgBIAEoAxIRCgl0eXBlX25hbWUYAiABKAkSJAocaW5qZWN0X3N0YXR1c19jb2RlX2ludG9fYm9keRgDIAEoCCKTAwoYRGF0YVNvdXJjZUN1c3RvbV9HcmFwaFFMEjMKBWZldGNoGAEgASgLMiQud2cuY29zbW8ubm9kZS52MS5GZXRjaENvbmZpZ3VyYXRpb24SSAoMc3Vic2NyaXB0aW9uGAIgASgLMjIud2cuY29zbW8ubm9kZS52MS5HcmFwaFFMU3Vic2NyaXB0aW9uQ29uZmlndXJhdGlvbhJECgpmZWRlcmF0aW9uGAMgASgLMjAud2cuY29zbW8ubm9kZS52MS5HcmFwaFFMRmVkZXJhdGlvbkNvbmZpZ3VyYXRpb24SOQoPdXBzdHJlYW1fc2NoZW1hGAQgASgLMiAud2cuY29zbW8ubm9kZS52MS5JbnRlcm5lZFN0cmluZxJEChljdXN0b21fc2NhbGFyX3R5cGVfZmllbGRzGAYgAygLMiEud2cuY29zbW8ubm9kZS52MS5TaW5nbGVUeXBlRmllbGQSMQoEZ3JwYxgHIAEoCzIjLndnLmNvc21vLm5vZGUudjEuR1JQQ0NvbmZpZ3VyYXRpb24ikAEKEUdSUENDb25maWd1cmF0aW9uEi4KB21hcHBpbmcYASABKAsyHS53Zy5jb3Ntby5ub2RlLnYxLkdSUENNYXBwaW5nEhQKDHByb3RvX3NjaGVtYRgCIAEoCRI1CgZwbHVnaW4YAyABKAsyJS53Zy5jb3Ntby5ub2RlLnYxLlBsdWdpbkNvbmZpZ3VyYXRpb24iNwoOSW1hZ2VSZWZlcmVuY2USEgoKcmVwb3NpdG9yeRgBIAEoCRIRCglyZWZlcmVuY2UYAiABKAkiiAEKE1BsdWdpbkNvbmZpZ3VyYXRpb24SDAoEbmFtZRgBIAEoCRIPCgd2ZXJzaW9uGAIgASgJEj4KD2ltYWdlX3JlZmVyZW5jZRgDIAEoCzIgLndnLmNvc21vLm5vZGUudjEuSW1hZ2VSZWZlcmVuY2VIAIgBAUISChBfaW1hZ2VfcmVmZXJlbmNlIiMKEFNTTENvbmZpZ3VyYXRpb24SDwoHZW5hYmxlZBgBIAEoCCLbAgoLR1JQQ01hcHBpbmcSDwoHdmVyc2lvbhgBIAEoBRIPCgdzZXJ2aWNlGAIgASgJEj4KEm9wZXJhdGlvbl9tYXBwaW5ncxgDIAMoCzIiLndnLmNvc21vLm5vZGUudjEuT3BlcmF0aW9uTWFwcGluZxI4Cg9lbnRpdHlfbWFwcGluZ3MYBCADKAsyHy53Zy5jb3Ntby5ub2RlLnYxLkVudGl0eU1hcHBpbmcSPwoTdHlwZV9maWVsZF9tYXBwaW5ncxgFIAMoCzIiLndnLmNvc21vLm5vZGUudjEuVHlwZUZpZWxkTWFwcGluZxI0Cg1lbnVtX21hcHBpbmdzGAYgAygLMh0ud2cuY29zbW8ubm9kZS52MS5FbnVtTWFwcGluZxI5ChByZXNvbHZlX21hcHBpbmdzGAcgAygLMh8ud2cuY29zbW8ubm9kZS52MS5Mb29rdXBNYXBwaW5nIqkBCg1Mb29rdXBNYXBwaW5nEioKBHR5cGUYASABKA4yHC53Zy5jb3Ntby5ub2RlLnYxLkxvb2t1cFR5cGUSPAoObG9va3VwX21hcHBpbmcYAiABKAsyJC53Zy5jb3Ntby5ub2RlLnYxLkxvb2t1cEZpZWxkTWFwcGluZxILCgNycGMYAyABKAkSDwoHcmVxdWVzdBgEIAEoCRIQCghyZXNwb25zZRgFIAEoCSJZChJMb29rdXBGaWVsZE1hcHBpbmcSDAoEdHlwZRgBIAEoCRI1Cg1maWVsZF9tYXBwaW5nGAIgASgLMh4ud2cuY29zbW8ubm9kZS52MS5GaWVsZE1hcHBpbmcihgEKEE9wZXJhdGlvbk1hcHBpbmcSLQoEdHlwZRgBIAEoDjIfLndnLmNvc21vLm5vZGUudjEuT3BlcmF0aW9uVHlwZRIQCghvcmlnaW5hbBgCIAEoCRIOCgZtYXBwZWQYAyABKAkSDwoHcmVxdWVzdBgEIAEoCRIQCghyZXNwb25zZRgFIAEoCSK2AQoNRW50aXR5TWFwcGluZxIRCgl0eXBlX25hbWUYASABKAkSDAoEa2luZBgCIAEoCRILCgNrZXkYAyABKAkSCwoDcnBjGAQgASgJEg8KB3JlcXVlc3QYBSABKAkSEAoIcmVzcG9uc2UYBiABKAkSRwoXcmVxdWlyZWRfZmllbGRfbWFwcGluZ3MYByADKAsyJi53Zy5jb3Ntby5ub2RlLnYxLlJlcXVpcmVkRmllbGRNYXBwaW5nIn0KFFJlcXVpcmVkRmllbGRNYXBwaW5nEjUKDWZpZWxkX21hcHBpbmcYASABKAsyHi53Zy5jb3Ntby5ub2RlLnYxLkZpZWxkTWFwcGluZxILCgNycGMYAiABKAkSDwoHcmVxdWVzdBgDIAEoCRIQCghyZXNwb25zZRgEIAEoCSJYChBUeXBlRmllbGRNYXBwaW5nEgwKBHR5cGUYASABKAkSNgoOZmllbGRfbWFwcGluZ3MYAiADKAsyHi53Zy5jb3Ntby5ub2RlLnYxLkZpZWxkTWFwcGluZyJuCgxGaWVsZE1hcHBpbmcSEAoIb3JpZ2luYWwYASABKAkSDgoGbWFwcGVkGAIgASgJEjwKEWFyZ3VtZW50X21hcHBpbmdzGAMgAygLMiEud2cuY29zbW8ubm9kZS52MS5Bcmd1bWVudE1hcHBpbmciMwoPQXJndW1lbnRNYXBwaW5nEhAKCG9yaWdpbmFsGAEgASgJEg4KBm1hcHBlZBgCIAEoCSJPCgtFbnVtTWFwcGluZxIMCgR0eXBlGAEgASgJEjIKBnZhbHVlcxgCIAMoCzIiLndnLmNvc21vLm5vZGUudjEuRW51bVZhbHVlTWFwcGluZyI0ChBFbnVtVmFsdWVNYXBwaW5nEhAKCG9yaWdpbmFsGAEgASgJEg4KBm1hcHBlZBgCIAEoCSJqChdOYXRzU3RyZWFtQ29uZmlndXJhdGlvbhIVCg1jb25zdW1lcl9uYW1lGAEgASgJEhMKC3N0cmVhbV9uYW1lGAIgASgJEiMKG2NvbnN1bWVyX2luYWN0aXZlX3RocmVzaG9sZBgDIAEoBSLDAQoWTmF0c0V2ZW50Q29uZmlndXJhdGlvbhJOChplbmdpbmVfZXZlbnRfY29uZmlndXJhdGlvbhgBIAEoCzIqLndnLmNvc21vLm5vZGUudjEuRW5naW5lRXZlbnRDb25maWd1cmF0aW9uEhAKCHN1YmplY3RzGAIgAygJEkcKFHN0cmVhbV9jb25maWd1cmF0aW9uGAMgASgLMikud2cuY29zbW8ubm9kZS52MS5OYXRzU3RyZWFtQ29uZmlndXJhdGlvbiJ5ChdLYWZrYUV2ZW50Q29uZmlndXJhdGlvbhJOChplbmdpbmVfZXZlbnRfY29uZmlndXJhdGlvbhgBIAEoCzIqLndnLmNvc21vLm5vZGUudjEuRW5naW5lRXZlbnRDb25maWd1cmF0aW9uEg4KBnRvcGljcxgCIAMoCSJ7ChdSZWRpc0V2ZW50Q29uZmlndXJhdGlvbhJOChplbmdpbmVfZXZlbnRfY29uZmlndXJhdGlvbhgBIAEoCzIqLndnLmNvc21vLm5vZGUudjEuRW5naW5lRXZlbnRDb25maWd1cmF0aW9uEhAKCGNoYW5uZWxzGAIgAygJIoEBChhFbmdpbmVFdmVudENvbmZpZ3VyYXRpb24SEwoLcHJvdmlkZXJfaWQYASABKAkSKQoEdHlwZRgCIAEoDjIbLndnLmNvc21vLm5vZGUudjEuRXZlbnRUeXBlEhEKCXR5cGVfbmFtZRgDIAEoCRISCgpmaWVsZF9uYW1lGAQgASgJIsQBChZEYXRhU291cmNlQ3VzdG9tRXZlbnRzEjYKBG5hdHMYASADKAsyKC53Zy5jb3Ntby5ub2RlLnYxLk5hdHNFdmVudENvbmZpZ3VyYXRpb24SOAoFa2Fma2EYAiADKAsyKS53Zy5jb3Ntby5ub2RlLnYxLkthZmthRXZlbnRDb25maWd1cmF0aW9uEjgKBXJlZGlzGAMgAygLMikud2cuY29zbW8ubm9kZS52MS5SZWRpc0V2ZW50Q29uZmlndXJhdGlvbiJQChdEYXRhU291cmNlQ3VzdG9tX1N0YXRpYxI1CgRkYXRhGAEgASgLMicud2cuY29zbW8ubm9kZS52MS5Db25maWd1cmF0aW9uVmFyaWFibGUi5QEKFUNvbmZpZ3VyYXRpb25WYXJpYWJsZRI5CgRraW5kGAEgASgOMisud2cuY29zbW8ubm9kZS52MS5Db25maWd1cmF0aW9uVmFyaWFibGVLaW5kEh8KF3N0YXRpY192YXJpYWJsZV9jb250ZW50GAIgASgJEiEKGWVudmlyb25tZW50X3ZhcmlhYmxlX25hbWUYAyABKAkSKgoiZW52aXJvbm1lbnRfdmFyaWFibGVfZGVmYXVsdF92YWx1ZRgEIAEoCRIhChlwbGFjZWhvbGRlcl92YXJpYWJsZV9uYW1lGAUgASgJIkMKFkRpcmVjdGl2ZUNvbmZpZ3VyYXRpb24SFgoOZGlyZWN0aXZlX25hbWUYASABKAkSEQoJcmVuYW1lX3RvGAIgASgJIjQKFVVSTFF1ZXJ5Q29uZmlndXJhdGlvbhIMCgRuYW1lGAEgASgJEg0KBXZhbHVlGAIgASgJIkUKCkhUVFBIZWFkZXISNwoGdmFsdWVzGAEgAygLMicud2cuY29zbW8ubm9kZS52MS5Db25maWd1cmF0aW9uVmFyaWFibGUinAEKEU1UTFNDb25maWd1cmF0aW9uEjQKA2tleRgBIAEoCzInLndnLmNvc21vLm5vZGUudjEuQ29uZmlndXJhdGlvblZhcmlhYmxlEjUKBGNlcnQYAiABKAsyJy53Zy5jb3Ntby5ub2RlLnYxLkNvbmZpZ3VyYXRpb25WYXJpYWJsZRIaChJpbnNlY3VyZVNraXBWZXJpZnkYAyABKAgixQIKIEdyYXBoUUxTdWJzY3JpcHRpb25Db25maWd1cmF0aW9uEg8KB2VuYWJsZWQYASABKAgSNAoDdXJsGAIgASgLMicud2cuY29zbW8ubm9kZS52MS5Db25maWd1cmF0aW9uVmFyaWFibGUSEwoGdXNlU1NFGAMgASgISACIAQESQwoIcHJvdG9jb2wYBCABKA4yLC53Zy5jb3Ntby5jb21tb24uR3JhcGhRTFN1YnNjcmlwdGlvblByb3RvY29sSAGIAQESTwoUd2Vic29ja2V0U3VicHJvdG9jb2wYBSABKA4yLC53Zy5jb3Ntby5jb21tb24uR3JhcGhRTFdlYnNvY2tldFN1YnByb3RvY29sSAKIAQFCCQoHX3VzZVNTRUILCglfcHJvdG9jb2xCFwoVX3dlYnNvY2tldFN1YnByb3RvY29sIkUKHkdyYXBoUUxGZWRlcmF0aW9uQ29uZmlndXJhdGlvbhIPCgdlbmFibGVkGAEgASgIEhIKCnNlcnZpY2VTZGwYAiABKAkiHQoOSW50ZXJuZWRTdHJpbmcSCwoDa2V5GAEgASgJIjgKD1NpbmdsZVR5cGVGaWVsZBIRCgl0eXBlX25hbWUYASABKAkSEgoKZmllbGRfbmFtZRgCIAEoCSI+ChpTdWJzY3JpcHRpb25GaWVsZENvbmRpdGlvbhISCgpmaWVsZF9wYXRoGAEgAygJEgwKBGpzb24YAiABKAkiowIKG1N1YnNjcmlwdGlvbkZpbHRlckNvbmRpdGlvbhI6CgNhbmQYASADKAsyLS53Zy5jb3Ntby5ub2RlLnYxLlN1YnNjcmlwdGlvbkZpbHRlckNvbmRpdGlvbhI9CgJpbhgCIAEoCzIsLndnLmNvc21vLm5vZGUudjEuU3Vic2NyaXB0aW9uRmllbGRDb25kaXRpb25IAIgBARI/CgNub3QYAyABKAsyLS53Zy5jb3Ntby5ub2RlLnYxLlN1YnNjcmlwdGlvbkZpbHRlckNvbmRpdGlvbkgBiAEBEjkKAm9yGAQgAygLMi0ud2cuY29zbW8ubm9kZS52MS5TdWJzY3JpcHRpb25GaWx0ZXJDb25kaXRpb25CBQoDX2luQgYKBF9ub3QiSAoVQ2FjaGVXYXJtZXJPcGVyYXRpb25zEi8KCm9wZXJhdGlvbnMYASADKAsyGy53Zy5jb3Ntby5ub2RlLnYxLk9wZXJhdGlvbiJuCglPcGVyYXRpb24SMwoHcmVxdWVzdBgBIAEoCzIiLndnLmNvc21vLm5vZGUudjEuT3BlcmF0aW9uUmVxdWVzdBIsCgZjbGllbnQYAiABKAsyHC53Zy5jb3Ntby5ub2RlLnYxLkNsaWVudEluZm8iagoQT3BlcmF0aW9uUmVxdWVzdBIWCg5vcGVyYXRpb25fbmFtZRgBIAEoCRINCgVxdWVyeRgCIAEoCRIvCgpleHRlbnNpb25zGAMgASgLMhsud2cuY29zbW8ubm9kZS52MS5FeHRlbnNpb24iRgoJRXh0ZW5zaW9uEjkKD3BlcnNpc3RlZF9xdWVyeRgBIAEoCzIgLndnLmNvc21vLm5vZGUudjEuUGVyc2lzdGVkUXVlcnkiNgoOUGVyc2lzdGVkUXVlcnkSEwoLc2hhMjU2X2hhc2gYASABKAkSDwoHdmVyc2lvbhgCIAEoBSIrCgpDbGllbnRJbmZvEgwKBG5hbWUYASABKAkSDwoHdmVyc2lvbhgCIAEoCSpDChZTYXRpc2ZpZWRPcGVyYXRpb25UeXBlEgkKBVFVRVJZEAASDAoITVVUQVRJT04QARIQCgxTVUJTQ1JJUFRJT04QAiqCAQobQXJndW1lbnRSZW5kZXJDb25maWd1cmF0aW9uEhsKF1JFTkRFUl9BUkdVTUVOVF9ERUZBVUxUEAASJAogUkVOREVSX0FSR1VNRU5UX0FTX0dSQVBIUUxfVkFMVUUQARIgChxSRU5ERVJfQVJHVU1FTlRfQVNfQVJSQVlfQ1NWEAIqNgoOQXJndW1lbnRTb3VyY2USEAoMT0JKRUNUX0ZJRUxEEAASEgoORklFTERfQVJHVU1FTlQQASo1Cg5EYXRhU291cmNlS2luZBIKCgZTVEFUSUMQABILCgdHUkFQSFFMEAESCgoGUFVCU1VCEAIqXAoKTG9va3VwVHlwZRIbChdMT09LVVBfVFlQRV9VTlNQRUNJRklFRBAAEhcKE0xPT0tVUF9UWVBFX1JFU09MVkUQARIYChRMT09LVVBfVFlQRV9SRVFVSVJFUxACKocBCg1PcGVyYXRpb25UeXBlEh4KGk9QRVJBVElPTl9UWVBFX1VOU1BFQ0lGSUVEEAASGAoUT1BFUkFUSU9OX1RZUEVfUVVFUlkQARIbChdPUEVSQVRJT05fVFlQRV9NVVRBVElPThACEh8KG09QRVJBVElPTl9UWVBFX1NVQlNDUklQVElPThADKjQKCUV2ZW50VHlwZRILCgdQVUJMSVNIEAASCwoHUkVRVUVTVBABEg0KCVNVQlNDUklCRRACKoYBChlDb25maWd1cmF0aW9uVmFyaWFibGVLaW5kEiEKHVNUQVRJQ19DT05GSUdVUkFUSU9OX1ZBUklBQkxFEAASHgoaRU5WX0NPTkZJR1VSQVRJT05fVkFSSUFCTEUQARImCiJQTEFDRUhPTERFUl9DT05GSUdVUkFUSU9OX1ZBUklBQkxFEAIqQQoKSFRUUE1ldGhvZBIHCgNHRVQQABIICgRQT1NUEAESBwoDUFVUEAISCgoGREVMRVRFEAMSCwoHT1BUSU9OUxAEMtIBCgtOb2RlU2VydmljZRJfCgxTZWxmUmVnaXN0ZXISJS53Zy5jb3Ntby5ub2RlLnYxLlNlbGZSZWdpc3RlclJlcXVlc3QaJi53Zy5jb3Ntby5ub2RlLnYxLlNlbGZSZWdpc3RlclJlc3BvbnNlIgASYgoNR2VuZXJhdGVRdWVyeRImLndnLmNvc21vLm5vZGUudjEuR2VuZXJhdGVRdWVyeVJlcXVlc3QaJy53Zy5jb3Ntby5ub2RlLnYxLkdlbmVyYXRlUXVlcnlSZXNwb25zZSIAYgZwcm90bzM", [file_wg_cosmo_common_common]); /** * @generated from message wg.cosmo.node.v1.Subgraph @@ -252,9 +252,11 @@ export const SelfRegisterResponseSchema: GenMessage = /*@_ */ export type GenerateQueryRequest = Message<"wg.cosmo.node.v1.GenerateQueryRequest"> & { /** - * @generated from field: string schema_hash = 1; + * Copied from RouterConfig.version in the active router configuration. + * + * @generated from field: string version = 1; */ - schemaHash: string; + version: string; /** * @generated from field: string prompt = 2; diff --git a/controlplane/src/core/bufservices/NodeService.ts b/controlplane/src/core/bufservices/NodeService.ts index 8de53ee000..9e86899b3f 100644 --- a/controlplane/src/core/bufservices/NodeService.ts +++ b/controlplane/src/core/bufservices/NodeService.ts @@ -82,7 +82,7 @@ export default function (opts: RouterOptions): Partial { + async generateQuery( + federatedGraphId: string, + version: string, + prompt: string, + signal?: AbortSignal, + ): Promise { if (!this.serviceAddress) { // The feature doesn't seem to be configured correctly return create(GenerateQueryResponseSchema, { @@ -77,7 +94,7 @@ export class PromptToQueryService { } // Ensure that the provided parameters are valid - const parsed = validationSchema.safeParse({ schemaSha, prompt }); + const parsed = validationSchema.safeParse({ version, prompt }); if (!parsed.success) { return create(GenerateQueryResponseSchema, { response: { @@ -98,15 +115,41 @@ export class PromptToQueryService { }); } + const fedRepo = new FederatedGraphRepository(this.logger, this.db, this.organizationId); + const federatedGraph = await fedRepo.byId(federatedGraphId); + if (!federatedGraph) { + return create(GenerateQueryResponseSchema, { + response: { + code: EnumStatusCode.ERR_NOT_FOUND, + details: 'Federated graph not found', + }, + }); + } + + const schemaVersion = await fedRepo.getSdlBasedOnSchemaVersion({ + targetId: federatedGraph.targetId, + schemaVersionId: parsed.data.version, + }); + if (!schemaVersion?.sdl) { + return create(GenerateQueryResponseSchema, { + response: { + code: EnumStatusCode.ERR_NOT_FOUND, + details: 'Schema version not found for this federated graph', + }, + }); + } + // Invoke the `prompt to query` service try { + const indexId = await this.ensureIndex(schemaVersion.sdl, signal); const response = await this.#httpClient('/yoko.v1.YokoService/GenerateQuery', { method: 'POST', headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ - indexId: parsed.data.schemaSha, + indexId, prompt: parsed.data.prompt, }), + signal, }); const parsedResponse = ptqResponseSchema.safeParse(response.data); @@ -152,6 +195,43 @@ export class PromptToQueryService { }).catch((e) => this.logger.error(e, 'Failed to index schema due an unexpected error')); } + private async ensureIndex(schemaSDL: string, signal?: AbortSignal): Promise { + const response = await this.#httpClient('/yoko.v1.YokoService/EnsureIndex', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: JSON.stringify({ sdl: schemaSDL }), + signal, + }); + + let index = PromptToQueryService.parseIndexResponse(response.data); + while (index.status === 'INDEX_STATUS_INDEXING') { + await setTimeout(indexPollInterval, undefined, { signal }); + + const response = await this.#httpClient('/yoko.v1.YokoService/GetIndex', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: JSON.stringify({ indexId: index.indexId }), + signal, + }); + index = PromptToQueryService.parseIndexResponse(response.data); + } + + if (index.status === 'INDEX_STATUS_FAILED') { + throw new Error(`Prompt to Query index generation failed${index.error ? `: ${index.error}` : ''}`); + } + + return index.indexId; + } + + private static parseIndexResponse(response: unknown): z.infer['index'] { + const parsed = indexResponseSchema.safeParse(response); + if (!parsed.success) { + throw new Error('It was not possible to parse the response returned by the Prompt to Query index service'); + } + + return parsed.data.index; + } + private static getOperationType(type: z.infer['operationType']): SatisfiedOperationType { switch (type) { case 'query': { diff --git a/docs-website/router/mcp/oauth/configuration.mdx b/docs-website/router/mcp/oauth/configuration.mdx index 054f288906..775ab5d040 100644 --- a/docs-website/router/mcp/oauth/configuration.mdx +++ b/docs-website/router/mcp/oauth/configuration.mdx @@ -18,6 +18,7 @@ icon: 'sliders-up' | `oauth.scopes.execute_graphql` | Scopes required to call the `execute_graphql` built-in tool. Additive to `tools_call`. Only relevant when `enable_arbitrary_operations` is `true`. | `[]` | | `oauth.scopes.get_operation_info` | Scopes required to call the `get_operation_info` built-in tool. Additive to `tools_call`. | `[]` | | `oauth.scopes.get_schema` | Scopes required to call the `get_schema` built-in tool. Additive to `tools_call`. Only relevant when `expose_schema` is `true`. | `[]` | +| `oauth.scopes.generate_query` | Scopes required to call the `generate_query` built-in tool. Additive to `tools_call`. Only relevant when a graph-token-backed control-plane client is configured. | `[]` | | `oauth.jwks` | List of JWKS providers for JWT verification. Supports remote JWKS URLs or symmetric secrets. | `[]` | ## JWKS Configuration @@ -67,6 +68,7 @@ oauth: | `MCP_OAUTH_AUTHORIZATION_SERVER_URL` | `mcp.oauth.authorization_server_url` | | `MCP_OAUTH_SCOPE_CHALLENGE_INCLUDE_TOKEN_SCOPES` | `mcp.oauth.scope_challenge_include_token_scopes` | | `MCP_OAUTH_MAX_SCOPE_COMBINATIONS` | `mcp.oauth.max_scope_combinations` | +| `MCP_OAUTH_SCOPES_GENERATE_QUERY` | `mcp.oauth.scopes.generate_query` | ## HTTP Error Responses diff --git a/docs-website/router/mcp/tools.mdx b/docs-website/router/mcp/tools.mdx index 6523d97cf2..644f88c23c 100644 --- a/docs-website/router/mcp/tools.mdx +++ b/docs-website/router/mcp/tools.mdx @@ -16,6 +16,7 @@ The MCP server gives AI models a set of tools they can discover and execute. It | `get_operation_info` | Returns instructions for executing the operation behind one of your tools directly via HTTP and integrating it into an application. | | `get_schema` | Returns the full GraphQL schema of the API, helping AI models understand the entire API structure. | | `execute_graphql` | Executes an arbitrary GraphQL query or mutation, letting AI models craft operations beyond the tools you have created. | +| `generate_query` | Uses Cosmo Cloud Prompt to Query to generate a GraphQL operation from a natural-language prompt. | `get_schema` and `execute_graphql` are disabled by default because they expose your full API surface to AI models. @@ -24,6 +25,35 @@ The MCP server gives AI models a set of tools they can discover and execute. It intended. Prefer creating focused tools. +### Generate a GraphQL Operation with Cosmo Cloud + +The `generate_query` tool is available when the router has a graph token and router registration is enabled. It sends the +prompt and a hash of the router's active schema to Cosmo Cloud's Prompt to Query service. The generated operation is +returned to the MCP client but is not executed. + +The tool accepts one required field: + +```json +{ + "prompt": "List the names and email addresses of the five most recently created users" +} +``` + +A successful response contains the GraphQL document and the metadata needed to use it: + +```json +{ + "description": "Lists the five most recently created users", + "document": "query RecentUsers { users(first: 5, orderBy: CREATED_AT_DESC) { name email } }", + "operationName": "RecentUsers", + "operationType": "query", + "variablesSchema": "{\"type\":\"object\"}" +} +``` + +Availability also depends on the Prompt to Query feature being enabled for the Cosmo Cloud organization associated with +the graph token. Control-plane or entitlement errors are returned as MCP tool errors. + ## Creating Tools Create a directory for your tools (as specified in your [storage provider configuration](/router/mcp/configuration#storage-providers)) and add `.graphql` or `.gql` files containing GraphQL operations. diff --git a/proto/wg/cosmo/node/v1/node.proto b/proto/wg/cosmo/node/v1/node.proto index 168fdc8e25..476e9867f2 100644 --- a/proto/wg/cosmo/node/v1/node.proto +++ b/proto/wg/cosmo/node/v1/node.proto @@ -61,7 +61,8 @@ message SelfRegisterResponse { } message GenerateQueryRequest { - string schema_hash = 1; + // Copied from RouterConfig.version in the active router configuration. + string version = 1; string prompt = 2; } diff --git a/router/core/graph_server.go b/router/core/graph_server.go index 3cc45d5f9e..9b779be06e 100644 --- a/router/core/graph_server.go +++ b/router/core/graph_server.go @@ -1611,7 +1611,7 @@ func (s *graphServer) buildGraphMux( // We support the MCP only on the base graph. Feature flags are not supported yet. if opts.IsBaseGraph() && s.mcpServer != nil { - if mErr := s.mcpServer.Reload(executor.ClientSchema, opts.EngineConfig.FieldConfigurations); mErr != nil { + if mErr := s.mcpServer.Reload(executor.ClientSchema, opts.EngineConfig.FieldConfigurations, opts.RouterConfigVersion); mErr != nil { return nil, fmt.Errorf("failed to reload MCP server: %w", mErr) } } diff --git a/router/core/router.go b/router/core/router.go index 3491a7e516..b1028d6bbc 100644 --- a/router/core/router.go +++ b/router/core/router.go @@ -1218,6 +1218,9 @@ func (r *Router) startMCPServer(ctx context.Context) error { mcpserver.WithServerTitle(r.mcp.Server.Title), mcpserver.WithServerDescription(r.mcp.Server.Description), } + if r.promptToQueryClient != nil { + mcpOpts = append(mcpOpts, mcpserver.WithPromptToQueryClient(r.promptToQueryClient)) + } if r.corsOptions != nil { mcpOpts = append(mcpOpts, mcpserver.WithCORS(*r.corsOptions)) @@ -2089,6 +2092,13 @@ func WithSelfRegistration(sr selfregister.SelfRegister) Option { } } +// WithPromptToQueryClient sets the control-plane client used by the MCP generate_query tool. +func WithPromptToQueryClient(client mcpserver.PromptToQueryClient) Option { + return func(r *Router) { + r.promptToQueryClient = client + } +} + // WithGracePeriod sets the grace period for the router to shutdown. func WithGracePeriod(timeout time.Duration) Option { return func(r *Router) { diff --git a/router/core/router_config.go b/router/core/router_config.go index c61bd97167..94cc20bc6c 100644 --- a/router/core/router_config.go +++ b/router/core/router_config.go @@ -143,6 +143,7 @@ type Config struct { // Poller configPoller configpoller.ConfigPoller selfRegister selfregister.SelfRegister + promptToQueryClient mcpserver.PromptToQueryClient registrationInfo *nodev1.RegistrationInfo securityConfiguration config.SecurityConfiguration customModules []Module diff --git a/router/core/supervisor_instance.go b/router/core/supervisor_instance.go index 73570b166a..77aed82a71 100644 --- a/router/core/supervisor_instance.go +++ b/router/core/supervisor_instance.go @@ -9,6 +9,8 @@ import ( "github.com/KimMachineGun/automemlimit/memlimit" "github.com/dustin/go-humanize" + "github.com/wundergraph/cosmo/router/internal/controlplane" + "github.com/wundergraph/cosmo/router/internal/prompttoquery" "github.com/wundergraph/cosmo/router/pkg/authentication" "github.com/wundergraph/cosmo/router/pkg/config" "github.com/wundergraph/cosmo/router/pkg/controlplane/selfregister" @@ -159,6 +161,19 @@ func newRouter(ctx context.Context, params RouterResources, additionalOptions .. options = append(options, WithSelfRegistration(selfRegister)) } + if cfg.MCP.Enabled && cfg.Graph.Token != "" { + controlplaneTransport, err := controlplane.NewTransport(cfg.Graph.Token, logger) + if err != nil { + return nil, fmt.Errorf("could not create controlplane transport: %w", err) + } + + promptToQueryClient, err := prompttoquery.New(cfg.ControlplaneURL, controlplaneTransport) + if err != nil { + return nil, fmt.Errorf("could not create prompt-to-query client: %w", err) + } + options = append(options, WithPromptToQueryClient(promptToQueryClient)) + } + if opt := optionFromExecutionConfig(&cfg.ExecutionConfig, cfg.RouterConfigPath); opt != nil { options = append(options, opt) } else { diff --git a/router/gen/proto/wg/cosmo/node/v1/node.pb.go b/router/gen/proto/wg/cosmo/node/v1/node.pb.go index 3dc0d344e0..7dc9dc335a 100644 --- a/router/gen/proto/wg/cosmo/node/v1/node.pb.go +++ b/router/gen/proto/wg/cosmo/node/v1/node.pb.go @@ -1010,9 +1010,10 @@ func (x *SelfRegisterResponse) GetRegistrationInfo() *RegistrationInfo { } type GenerateQueryRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - SchemaHash string `protobuf:"bytes,1,opt,name=schema_hash,json=schemaHash,proto3" json:"schema_hash,omitempty"` - Prompt string `protobuf:"bytes,2,opt,name=prompt,proto3" json:"prompt,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + // Copied from RouterConfig.version in the active router configuration. + Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"` + Prompt string `protobuf:"bytes,2,opt,name=prompt,proto3" json:"prompt,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1047,9 +1048,9 @@ func (*GenerateQueryRequest) Descriptor() ([]byte, []int) { return file_wg_cosmo_node_v1_node_proto_rawDescGZIP(), []int{10} } -func (x *GenerateQueryRequest) GetSchemaHash() string { +func (x *GenerateQueryRequest) GetVersion() string { if x != nil { - return x.SchemaHash + return x.Version } return "" } @@ -5183,10 +5184,9 @@ const file_wg_cosmo_node_v1_node_proto_rawDesc = "" + "\x14SelfRegisterResponse\x126\n" + "\bresponse\x18\x01 \x01(\v2\x1a.wg.cosmo.node.v1.ResponseR\bresponse\x12S\n" + "\x10registrationInfo\x18\x02 \x01(\v2\".wg.cosmo.node.v1.RegistrationInfoH\x00R\x10registrationInfo\x88\x01\x01B\x13\n" + - "\x11_registrationInfo\"O\n" + - "\x14GenerateQueryRequest\x12\x1f\n" + - "\vschema_hash\x18\x01 \x01(\tR\n" + - "schemaHash\x12\x16\n" + + "\x11_registrationInfo\"H\n" + + "\x14GenerateQueryRequest\x12\x18\n" + + "\aversion\x18\x01 \x01(\tR\aversion\x12\x16\n" + "\x06prompt\x18\x02 \x01(\tR\x06prompt\"\x96\x01\n" + "\x15GenerateQueryResponse\x126\n" + "\bresponse\x18\x01 \x01(\v2\x1a.wg.cosmo.node.v1.ResponseR\bresponse\x12;\n" + diff --git a/router/internal/controlplane/transport.go b/router/internal/controlplane/transport.go new file mode 100644 index 0000000000..6a863f19a0 --- /dev/null +++ b/router/internal/controlplane/transport.go @@ -0,0 +1,58 @@ +package controlplane + +import ( + "fmt" + "net/http" + "time" + + "github.com/hashicorp/go-retryablehttp" + "go.uber.org/zap" +) + +const ( + retryWaitMax = 15 * time.Second + retryMax = 3 +) + +type bearerAuthTransport struct { + token string + next http.RoundTripper +} + +// NewTransport creates an authenticated, retrying transport for control plane requests. +func NewTransport(token string, logger *zap.Logger) (http.RoundTripper, error) { + if token == "" { + return nil, fmt.Errorf("graph api token is required for controlplane requests") + } + + if logger == nil { + logger = zap.NewNop() + } + + retryClient := retryablehttp.NewClient() + retryClient.RetryWaitMax = retryWaitMax + retryClient.RetryMax = retryMax + retryClient.Backoff = retryablehttp.DefaultBackoff + retryClient.Logger = nil + retryClient.RequestLogHook = func(_ retryablehttp.Logger, _ *http.Request, retry int) { + if retry > 0 { + logger.Info("Retry controlplane request", zap.Int("retry", retry)) + } + } + + return newBearerAuthTransport(token, retryClient.StandardClient().Transport), nil +} + +func newBearerAuthTransport(token string, next http.RoundTripper) http.RoundTripper { + return &bearerAuthTransport{ + token: token, + next: next, + } +} + +func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req = req.Clone(req.Context()) + req.Header.Set("Authorization", "Bearer "+t.token) + + return t.next.RoundTrip(req) +} diff --git a/router/internal/prompttoquery/client.go b/router/internal/prompttoquery/client.go new file mode 100644 index 0000000000..31b268b773 --- /dev/null +++ b/router/internal/prompttoquery/client.go @@ -0,0 +1,56 @@ +package prompttoquery + +import ( + "context" + "fmt" + "net/http" + "time" + + "connectrpc.com/connect" + nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" + "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1/nodev1connect" + brotli "go.withmatt.com/connect-brotli" +) + +const clientTimeout = 15 * time.Second + +// Client generates GraphQL operations through the control plane. +type Client struct { + nodeServiceClient nodev1connect.NodeServiceClient +} + +func New(endpoint string, transport http.RoundTripper) (*Client, error) { + if endpoint == "" { + return nil, fmt.Errorf("controlplane endpoint is required for prompt to query") + } + + if transport == nil { + return nil, fmt.Errorf("controlplane transport is required for prompt to query") + } + + httpClient := &http.Client{ + Transport: transport, + Timeout: clientTimeout, + } + + nodeServiceClient := nodev1connect.NewNodeServiceClient(httpClient, endpoint, + brotli.WithCompression(), + connect.WithSendCompression(brotli.Name), + ) + + return &Client{nodeServiceClient: nodeServiceClient}, nil +} + +func (c *Client) GenerateQuery(ctx context.Context, schemaVersionID, prompt string) (*nodev1.GenerateQueryResponse, error) { + req := connect.NewRequest(&nodev1.GenerateQueryRequest{ + Version: schemaVersionID, + Prompt: prompt, + }) + + resp, err := c.nodeServiceClient.GenerateQuery(ctx, req) + if err != nil { + return nil, err + } + + return resp.Msg, nil +} diff --git a/router/pkg/config/config.go b/router/pkg/config/config.go index 781709a25d..613c6801fc 100644 --- a/router/pkg/config/config.go +++ b/router/pkg/config/config.go @@ -1397,6 +1397,9 @@ type MCPOAuthScopesConfiguration struct { // GetSchema specifies scopes required to call the get_schema built-in tool. // Additive to tools_call scopes. Only relevant when expose_schema is true. GetSchema []string `yaml:"get_schema,omitempty" env:"GET_SCHEMA"` + // GenerateQuery specifies scopes required to call the generate_query built-in tool. + // Additive to tools_call scopes. Only relevant when a control-plane client is configured. + GenerateQuery []string `yaml:"generate_query,omitempty" env:"GENERATE_QUERY"` } type MCPSessionConfig struct { diff --git a/router/pkg/config/config.schema.json b/router/pkg/config/config.schema.json index 8428a4a0e4..aed96dfac1 100644 --- a/router/pkg/config/config.schema.json +++ b/router/pkg/config/config.schema.json @@ -2810,6 +2810,11 @@ "type": "array", "description": "Scopes required to call the get_schema built-in tool. Additive to tools_call scopes. Only relevant when expose_schema is true.", "items": { "type": "string" } + }, + "generate_query": { + "type": "array", + "description": "Scopes required to call the generate_query built-in tool. Additive to tools_call scopes. Only relevant when a graph-token-backed control-plane client is configured.", + "items": { "type": "string" } } } }, diff --git a/router/pkg/config/testdata/config_defaults.json b/router/pkg/config/testdata/config_defaults.json index 080b9f7a30..f53703d266 100644 --- a/router/pkg/config/testdata/config_defaults.json +++ b/router/pkg/config/testdata/config_defaults.json @@ -221,7 +221,8 @@ "ToolsCall": null, "ExecuteGraphQL": null, "GetOperationInfo": null, - "GetSchema": null + "GetSchema": null, + "GenerateQuery": null }, "ScopeChallengeIncludeTokenScopes": false, "MaxScopeCombinations": 2048 diff --git a/router/pkg/config/testdata/config_full.json b/router/pkg/config/testdata/config_full.json index 446dcc3958..7baa6f1cb7 100644 --- a/router/pkg/config/testdata/config_full.json +++ b/router/pkg/config/testdata/config_full.json @@ -290,7 +290,8 @@ "ToolsCall": null, "ExecuteGraphQL": null, "GetOperationInfo": null, - "GetSchema": null + "GetSchema": null, + "GenerateQuery": null }, "ScopeChallengeIncludeTokenScopes": false, "MaxScopeCombinations": 2048 diff --git a/router/pkg/mcpserver/auth_middleware.go b/router/pkg/mcpserver/auth_middleware.go index 6c78a9bfc2..60cf80e2d2 100644 --- a/router/pkg/mcpserver/auth_middleware.go +++ b/router/pkg/mcpserver/auth_middleware.go @@ -91,6 +91,8 @@ func (m *MCPAuthMiddleware) getBuiltinToolScopes(toolName string) []string { return m.scopes.GetOperationInfo case "get_schema": return m.scopes.GetSchema + case "generate_query": + return m.scopes.GenerateQuery default: return nil } diff --git a/router/pkg/mcpserver/auth_middleware_test.go b/router/pkg/mcpserver/auth_middleware_test.go index b6591418ab..1ae460a4d3 100644 --- a/router/pkg/mcpserver/auth_middleware_test.go +++ b/router/pkg/mcpserver/auth_middleware_test.go @@ -607,6 +607,8 @@ func TestMCPAuthMiddlewareBuiltinToolScopes(t *testing.T) { return authentication.Claims{"sub": "user3", "scope": "mcp:connect mcp:tools:call mcp:graphql:execute"}, nil case "has-ops-read": return authentication.Claims{"sub": "user4", "scope": "mcp:connect mcp:tools:call mcp:ops:read"}, nil + case "has-query-generate": + return authentication.Claims{"sub": "user5", "scope": "mcp:connect mcp:tools:call mcp:query:generate"}, nil default: return nil, errors.New("invalid token") } @@ -619,6 +621,7 @@ func TestMCPAuthMiddlewareBuiltinToolScopes(t *testing.T) { ExecuteGraphQL: []string{"mcp:graphql:execute"}, GetOperationInfo: []string{"mcp:ops:read"}, GetSchema: []string{"mcp:schema:read"}, + GenerateQuery: []string{"mcp:query:generate"}, } tests := []struct { @@ -667,6 +670,19 @@ func TestMCPAuthMiddlewareBuiltinToolScopes(t *testing.T) { body: `{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_operation_info"}}`, wantStatusCode: 200, }, + { + name: "returns 403 when generate_query lacks required builtin scope", + token: "base-only", + body: `{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"generate_query"}}`, + wantStatusCode: 403, + wantScope: `scope="mcp:query:generate"`, + }, + { + name: "allows generate_query when token has required builtin scope", + token: "has-query-generate", + body: `{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"generate_query"}}`, + wantStatusCode: 200, + }, { name: "allows non-builtin tool regardless of builtin scopes", token: "base-only", diff --git a/router/pkg/mcpserver/discover_test.go b/router/pkg/mcpserver/discover_test.go index b5b74650f8..7330741e1d 100644 --- a/router/pkg/mcpserver/discover_test.go +++ b/router/pkg/mcpserver/discover_test.go @@ -36,7 +36,7 @@ func newTestSession(t *testing.T, opts ...func(*Options)) *mcp.ClientSession { }, opts...)..., ) require.NoError(t, err) - require.NoError(t, srv.Reload(&schemaDoc, nil)) + require.NoError(t, srv.Reload(&schemaDoc, nil, "schema-version-test")) serverTransport, clientTransport := mcp.NewInMemoryTransports() diff --git a/router/pkg/mcpserver/server.go b/router/pkg/mcpserver/server.go index 1a118a6858..9dc365dbbb 100644 --- a/router/pkg/mcpserver/server.go +++ b/router/pkg/mcpserver/server.go @@ -11,6 +11,7 @@ import ( "net/http" "slices" "strings" + "sync" "time" "github.com/hashicorp/go-retryablehttp" @@ -19,6 +20,7 @@ import ( "github.com/santhosh-tekuri/jsonschema/v6" "go.uber.org/zap" + "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/common" nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" "github.com/wundergraph/cosmo/router/internal/headers" "github.com/wundergraph/cosmo/router/pkg/authentication" @@ -35,6 +37,7 @@ import ( var reservedToolNames = []string{ "get_schema", "execute_graphql", + "generate_query", "get_operation_info", } @@ -110,6 +113,14 @@ type Options struct { // ServerDescription is a human-readable description reported in the MCP // serverInfo. ServerDescription string + // PromptToQueryClient generates GraphQL operations through the Cosmo control plane. + // When nil, the generate_query tool is not exposed. + PromptToQueryClient PromptToQueryClient +} + +// PromptToQueryClient generates a GraphQL operation for a schema version. +type PromptToQueryClient interface { + GenerateQuery(ctx context.Context, schemaVersionID, prompt string) (*nodev1.GenerateQueryResponse, error) } // GraphQLSchemaServer represents an MCP server that works with GraphQL schemas and operations @@ -137,6 +148,9 @@ type GraphQLSchemaServer struct { serverBaseURL string resourceDocumentation string authMiddleware *MCPAuthMiddleware + promptToQueryClient PromptToQueryClient + schemaVersionIDMu sync.RWMutex + schemaVersionID string } type graphqlRequest struct { @@ -150,6 +164,20 @@ type ExecuteGraphQLInput struct { Variables json.RawMessage `json:"variables,omitempty"` } +// GenerateQueryInput defines the input structure for the generate_query tool. +type GenerateQueryInput struct { + Prompt string `json:"prompt"` +} + +// GeneratedQueryResponse is returned by the generate_query tool. +type GeneratedQueryResponse struct { + Description string `json:"description,omitempty"` + Document string `json:"document"` + OperationName string `json:"operationName"` + OperationType string `json:"operationType"` + VariablesSchema string `json:"variablesSchema,omitempty"` +} + // operationHandler holds an operation and its compiled JSON schema type operationHandler struct { operation schemaloader.Operation @@ -344,6 +372,7 @@ func NewGraphQLSchemaServer(ctx context.Context, routerGraphQLEndpoint string, o serverBaseURL: options.ServerBaseURL, resourceDocumentation: options.ResourceDocumentation, authMiddleware: authMiddleware, + promptToQueryClient: options.PromptToQueryClient, } return gs, nil @@ -383,6 +412,13 @@ func WithServerDescription(description string) func(*Options) { } } +// WithPromptToQueryClient enables the generate_query tool using the provided control-plane client. +func WithPromptToQueryClient(client PromptToQueryClient) func(*Options) { + return func(o *Options) { + o.PromptToQueryClient = client + } +} + // WithGraphName sets the graph name. An empty name keeps the default. func WithGraphName(graphName string) func(*Options) { return func(o *Options) { @@ -568,13 +604,14 @@ func (s *GraphQLSchemaServer) Start() error { // Reload reloads the operations and schema, and computes per-tool scope // requirements from @requiresScopes directives in the field configurations. -func (s *GraphQLSchemaServer) Reload(schema *ast.Document, fieldConfigs []*nodev1.FieldConfiguration) error { +func (s *GraphQLSchemaServer) Reload(schema *ast.Document, fieldConfigs []*nodev1.FieldConfiguration, schemaVersionID string) error { if s.server == nil { return fmt.Errorf("server is not started") } s.schemaCompiler = NewSchemaCompiler(s.logger) s.operationsManager = NewOperationsManager(schema, s.logger, s.excludeMutations) + s.setSchemaVersionID(schemaVersionID) if s.operationsDir != "" { if err := s.operationsManager.LoadOperationsFromDirectory(s.operationsDir); err != nil { @@ -690,6 +727,36 @@ func (s *GraphQLSchemaServer) registerTools() error { s.registeredTools = append(s.registeredTools, "execute_graphql") } + if s.promptToQueryClient != nil { + openWorldHint := true + generateQueryTool := &mcp.Tool{ + Name: "generate_query", + Description: "Generates a GraphQL operation for this API from a natural-language prompt.", + InputSchema: map[string]any{ + "type": "object", + "description": "A natural-language description of the GraphQL operation to generate.", + "properties": map[string]any{ + "prompt": map[string]any{ + "type": "string", + "minLength": 1, + "description": "A natural-language description of the GraphQL operation to generate.", + }, + }, + "additionalProperties": false, + "required": []string{"prompt"}, + }, + Annotations: &mcp.ToolAnnotations{ + Title: "Generate GraphQL Operation", + ReadOnlyHint: true, + IdempotentHint: true, + OpenWorldHint: &openWorldHint, + }, + } + + s.server.AddTool(generateQueryTool, s.handleGenerateQuery()) + s.registeredTools = append(s.registeredTools, "generate_query") + } + // Get operations filtered by the excludeMutations setting operations := s.operationsManager.GetFilteredOperations() @@ -819,6 +886,81 @@ func (s *GraphQLSchemaServer) registerTools() error { return nil } +func (s *GraphQLSchemaServer) setSchemaVersionID(schemaVersionID string) { + s.schemaVersionIDMu.Lock() + defer s.schemaVersionIDMu.Unlock() + s.schemaVersionID = schemaVersionID +} + +func (s *GraphQLSchemaServer) getSchemaVersionID() string { + s.schemaVersionIDMu.RLock() + defer s.schemaVersionIDMu.RUnlock() + return s.schemaVersionID +} + +func toolError(message string) *mcp.CallToolResult { + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{Text: message}}, + IsError: true, + } +} + +func (s *GraphQLSchemaServer) handleGenerateQuery() func(ctx context.Context, request *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return func(ctx context.Context, request *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + var input GenerateQueryInput + if err := json.Unmarshal(request.Params.Arguments, &input); err != nil { + return toolError(fmt.Sprintf("Invalid input: %v", err)), nil + } + + input.Prompt = strings.TrimSpace(input.Prompt) + if input.Prompt == "" { + return toolError("Input validation failed: prompt is required"), nil + } + + schemaVersionID := s.getSchemaVersionID() + if schemaVersionID == "" { + return toolError("The active GraphQL schema version is not available"), nil + } + + response, err := s.promptToQueryClient.GenerateQuery(ctx, schemaVersionID, input.Prompt) + if err != nil { + s.logger.Error("failed to generate query through the control plane", zap.Error(err)) + return toolError("Failed to generate a GraphQL operation through the control plane"), nil + } + if response == nil || response.GetResponse() == nil { + return toolError("The control plane returned an invalid generate-query response"), nil + } + if response.GetResponse().GetCode() != common.EnumStatusCode_OK { + details := response.GetResponse().GetDetails() + if details == "" { + details = fmt.Sprintf("control plane returned status %s", response.GetResponse().GetCode()) + } + return toolError(details), nil + } + + query := response.GetQuery() + if query == nil { + return toolError("The control plane did not return a generated GraphQL operation"), nil + } + + result := GeneratedQueryResponse{ + Description: query.GetDescription(), + Document: query.GetDocument(), + OperationName: query.GetOperationName(), + OperationType: strings.ToLower(query.GetOperationType().String()), + VariablesSchema: query.GetVariablesSchema(), + } + resultJSON, err := json.Marshal(result) + if err != nil { + return nil, fmt.Errorf("failed to marshal generated query response: %w", err) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{Text: string(resultJSON)}}, + }, nil + } +} + // handleOperation handles a specific operation func (s *GraphQLSchemaServer) handleOperation(handler *operationHandler) func(ctx context.Context, request *mcp.CallToolRequest) (*mcp.CallToolResult, error) { return func(ctx context.Context, request *mcp.CallToolRequest) (*mcp.CallToolResult, error) { @@ -1124,6 +1266,9 @@ func (s *GraphQLSchemaServer) handleProtectedResourceMetadata(w http.ResponseWri if s.exposeSchema { scopeLists = append(scopeLists, s.oauthConfig.Scopes.GetSchema) } + if s.promptToQueryClient != nil { + scopeLists = append(scopeLists, s.oauthConfig.Scopes.GenerateQuery) + } for _, scopeList := range scopeLists { for _, scope := range scopeList { diff --git a/router/pkg/mcpserver/server_test.go b/router/pkg/mcpserver/server_test.go index 3a3c044609..590ad1fc23 100644 --- a/router/pkg/mcpserver/server_test.go +++ b/router/pkg/mcpserver/server_test.go @@ -1,12 +1,20 @@ package mcpserver import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" "os" "path/filepath" "testing" + "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/common" + nodev1 "github.com/wundergraph/cosmo/router/gen/proto/wg/cosmo/node/v1" + "github.com/wundergraph/cosmo/router/pkg/config" "github.com/wundergraph/graphql-go-tools/v2/pkg/astparser" "github.com/wundergraph/graphql-go-tools/v2/pkg/asttransform" "go.uber.org/zap" @@ -14,6 +22,19 @@ import ( "go.uber.org/zap/zaptest/observer" ) +type fakePromptToQueryClient struct { + response *nodev1.GenerateQueryResponse + err error + schemaVersionID string + prompt string +} + +func (f *fakePromptToQueryClient) GenerateQuery(_ context.Context, schemaVersionID, prompt string) (*nodev1.GenerateQueryResponse, error) { + f.schemaVersionID = schemaVersionID + f.prompt = prompt + return f.response, f.err +} + const testSchema = ` schema { query: Query @@ -90,14 +111,14 @@ func TestReload_NoToolDuplication(t *testing.T) { require.NoError(t, err) // First load - err = srv.Reload(&schemaDoc, nil) + err = srv.Reload(&schemaDoc, nil, "schema-version-test") require.NoError(t, err) firstLoadTools := make([]string, len(srv.registeredTools)) copy(firstLoadTools, srv.registeredTools) // Second load (simulates config reload) - err = srv.Reload(&schemaDoc, nil) + err = srv.Reload(&schemaDoc, nil, "schema-version-test") require.NoError(t, err) // registeredTools should be identical after reload — no duplicates @@ -136,7 +157,7 @@ func TestReload_ReservedToolNameCollision(t *testing.T) { ) require.NoError(t, err) - err = srv.Reload(&schemaDoc, nil) + err = srv.Reload(&schemaDoc, nil, "schema-version-test") require.NoError(t, err) // The operation "GetOperationInfo" (snake: "get_operation_info") should be skipped @@ -180,7 +201,7 @@ func TestReload_PrefixModeAvoidsReservedNameCollision(t *testing.T) { ) require.NoError(t, err) - err = srv.Reload(&schemaDoc, nil) + err = srv.Reload(&schemaDoc, nil, "schema-version-test") require.NoError(t, err) // No collisions because the prefix disambiguates from the reserved name @@ -195,3 +216,81 @@ func TestReload_PrefixModeAvoidsReservedNameCollision(t *testing.T) { "get_operation_info", }, srv.registeredTools) } + +func TestGenerateQueryTool(t *testing.T) { + schemaDoc, report := astparser.ParseGraphqlDocumentString(testSchema) + require.False(t, report.HasErrors()) + require.NoError(t, asttransform.MergeDefinitionWithBaseSchema(&schemaDoc)) + + client := &fakePromptToQueryClient{ + response: &nodev1.GenerateQueryResponse{ + Response: &nodev1.Response{Code: common.EnumStatusCode_OK}, + Query: &nodev1.SatisfiedQuery{ + Description: "Lists employees", + Document: "query ListEmployees { employees { id name } }", + OperationName: "ListEmployees", + OperationType: nodev1.SatisfiedOperationType_QUERY, + VariablesSchema: `{"type":"object"}`, + }, + }, + } + srv, err := NewGraphQLSchemaServer( + t.Context(), + "http://localhost:4000/graphql", + WithPromptToQueryClient(client), + WithOperationsDir(""), + ) + require.NoError(t, err) + require.NoError(t, srv.Reload(&schemaDoc, nil, "schema-version-first")) + require.Contains(t, srv.registeredTools, "generate_query") + + result, err := srv.handleGenerateQuery()(t.Context(), &mcp.CallToolRequest{ + Params: &mcp.CallToolParamsRaw{Arguments: json.RawMessage(`{"prompt":" List all employees "}`)}, + }) + + require.NoError(t, err) + require.False(t, result.IsError) + require.Equal(t, "schema-version-first", client.schemaVersionID) + require.Equal(t, "List all employees", client.prompt) + require.Len(t, result.Content, 1) + textContent, ok := result.Content[0].(*mcp.TextContent) + require.True(t, ok) + require.JSONEq(t, `{ + "description":"Lists employees", + "document":"query ListEmployees { employees { id name } }", + "operationName":"ListEmployees", + "operationType":"query", + "variablesSchema":"{\"type\":\"object\"}" + }`, textContent.Text) + + require.NoError(t, srv.Reload(&schemaDoc, nil, "schema-version-second")) + _, err = srv.handleGenerateQuery()(t.Context(), &mcp.CallToolRequest{ + Params: &mcp.CallToolParamsRaw{Arguments: json.RawMessage(`{"prompt":"List employees again"}`)}, + }) + require.NoError(t, err) + require.Equal(t, "schema-version-second", client.schemaVersionID) +} + +func TestGenerateQueryScopeIncludedInProtectedResourceMetadata(t *testing.T) { + srv := &GraphQLSchemaServer{ + oauthConfig: &config.MCPOAuthConfiguration{ + AuthorizationServerURL: "https://auth.example.com", + Scopes: config.MCPOAuthScopesConfiguration{ + ToolsCall: []string{"mcp:tools:call"}, + GenerateQuery: []string{"mcp:query:generate"}, + }, + }, + promptToQueryClient: &fakePromptToQueryClient{}, + serverBaseURL: "https://mcp.example.com", + logger: zap.NewNop(), + } + + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "/.well-known/oauth-protected-resource/mcp", nil) + srv.handleProtectedResourceMetadata(recorder, request) + + require.Equal(t, http.StatusOK, recorder.Code) + var metadata ProtectedResourceMetadata + require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &metadata)) + require.Equal(t, []string{"mcp:query:generate", "mcp:tools:call"}, metadata.ScopesSupported) +}