File size: 3,337 Bytes
9705b6c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
const fs = require('fs');
const path = require('path');
const { z } = require('zod');
const { createOpenAPIPlugin } = require('../dynamic/OpenAPIPlugin');

// The minimum Manifest definition
const ManifestDefinition = z.object({
  schema_version: z.string().optional(),
  name_for_human: z.string(),
  name_for_model: z.string(),
  description_for_human: z.string(),
  description_for_model: z.string(),
  auth: z.object({}).optional(),
  api: z.object({
    // Spec URL or can be the filename of the OpenAPI spec yaml file,
    // located in api\app\clients\tools\.well-known\openapi
    url: z.string(),
    type: z.string().optional(),
    is_user_authenticated: z.boolean().nullable().optional(),
    has_user_authentication: z.boolean().nullable().optional(),
  }),
  // use to override any params that the LLM will consistently get wrong
  params: z.object({}).optional(),
  logo_url: z.string().optional(),
  contact_email: z.string().optional(),
  legal_info_url: z.string().optional(),
});

function validateJson(json, verbose = true) {
  try {
    return ManifestDefinition.parse(json);
  } catch (error) {
    if (verbose) {
      console.debug('validateJson error', error);
    }
    return false;
  }
}

// omit the LLM to return the well known jsons as objects
async function loadSpecs({
  llm,
  user,
  message,
  tools = [],
  map = false,
  memory,
  signal,
  verbose = false,
}) {
  const directoryPath = path.join(__dirname, '..', '.well-known');
  let files = [];

  for (let i = 0; i < tools.length; i++) {
    const filePath = path.join(directoryPath, tools[i] + '.json');

    try {
      // If the access Promise is resolved, it means that the file exists
      // Then we can add it to the files array
      await fs.promises.access(filePath, fs.constants.F_OK);
      files.push(tools[i] + '.json');
    } catch (err) {
      console.error(`File ${tools[i] + '.json'} does not exist`);
    }
  }

  if (files.length === 0) {
    files = (await fs.promises.readdir(directoryPath)).filter(
      (file) => path.extname(file) === '.json',
    );
  }

  const validJsons = [];
  const constructorMap = {};

  if (verbose) {
    console.debug('files', files);
  }

  for (const file of files) {
    if (path.extname(file) === '.json') {
      const filePath = path.join(directoryPath, file);
      const fileContent = await fs.promises.readFile(filePath, 'utf8');
      const json = JSON.parse(fileContent);

      if (!validateJson(json)) {
        verbose && console.debug('Invalid json', json);
        continue;
      }

      if (llm && map) {
        constructorMap[json.name_for_model] = async () =>
          await createOpenAPIPlugin({
            data: json,
            llm,
            message,
            memory,
            signal,
            user,
            verbose,
          });
        continue;
      }

      if (llm) {
        validJsons.push(createOpenAPIPlugin({ data: json, llm, verbose }));
        continue;
      }

      validJsons.push(json);
    }
  }

  if (map) {
    return constructorMap;
  }

  const plugins = (await Promise.all(validJsons)).filter((plugin) => plugin);

  // if (verbose) {
  //   console.debug('plugins', plugins);
  //   console.debug(plugins[0].name);
  // }

  return plugins;
}

module.exports = {
  loadSpecs,
  validateJson,
  ManifestDefinition,
};