@aigne/aigne-hub 0.4.8 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/lib/cjs/blocklet-aigne-hub-model.js +4 -3
- package/lib/cjs/cli-aigne-hub-model.js +1 -1
- package/lib/cjs/index.d.ts +5 -1
- package/lib/cjs/index.js +8 -3
- package/lib/cjs/util/constants.d.ts +13 -0
- package/lib/cjs/util/constants.js +29 -0
- package/lib/cjs/util/credential.d.ts +24 -0
- package/lib/cjs/util/credential.js +223 -0
- package/lib/cjs/util/crypto.d.ts +4 -0
- package/lib/cjs/util/crypto.js +19 -0
- package/lib/cjs/util/model.d.ts +12 -0
- package/lib/cjs/util/model.js +201 -0
- package/lib/cjs/util/type.d.ts +58 -0
- package/lib/cjs/util/type.js +2 -0
- package/lib/dts/index.d.ts +5 -1
- package/lib/dts/util/constants.d.ts +13 -0
- package/lib/dts/util/credential.d.ts +24 -0
- package/lib/dts/util/crypto.d.ts +4 -0
- package/lib/dts/util/model.d.ts +12 -0
- package/lib/dts/util/type.d.ts +58 -0
- package/lib/esm/blocklet-aigne-hub-model.js +2 -1
- package/lib/esm/cli-aigne-hub-model.js +1 -1
- package/lib/esm/index.d.ts +5 -1
- package/lib/esm/index.js +7 -2
- package/lib/esm/util/constants.d.ts +13 -0
- package/lib/esm/util/constants.js +15 -0
- package/lib/esm/util/credential.d.ts +24 -0
- package/lib/esm/util/credential.js +211 -0
- package/lib/esm/util/crypto.d.ts +4 -0
- package/lib/esm/util/crypto.js +9 -0
- package/lib/esm/util/model.d.ts +12 -0
- package/lib/esm/util/model.js +189 -0
- package/lib/esm/util/type.d.ts +58 -0
- package/lib/esm/util/type.js +1 -0
- package/package.json +22 -15
- package/lib/cjs/constants.d.ts +0 -28
- package/lib/cjs/constants.js +0 -123
- package/lib/dts/constants.d.ts +0 -28
- package/lib/esm/constants.d.ts +0 -28
- package/lib/esm/constants.js +0 -116
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { logger } from "@aigne/core/utils/logger.js";
|
|
2
|
+
import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
|
|
3
|
+
import inquirer from "inquirer";
|
|
4
|
+
import open from "open";
|
|
5
|
+
import pWaitFor from "p-wait-for";
|
|
6
|
+
import { joinURL, withQuery } from "ufo";
|
|
7
|
+
import { parse, stringify } from "yaml";
|
|
8
|
+
import { ACCESS_KEY_SESSION_API, AGENT_HUB_PROVIDER, AIGNE_ENV_FILE, AIGNE_HUB_DID, AIGNE_HUB_URL as DEFAULT_AIGNE_HUB_URL, isTest, WELLKNOWN_SERVICE_PATH_PREFIX, } from "./constants.js";
|
|
9
|
+
import { decrypt, encodeEncryptionKey } from "./crypto.js";
|
|
10
|
+
const request = async (config) => {
|
|
11
|
+
const headers = {};
|
|
12
|
+
if (config.requestCount !== undefined) {
|
|
13
|
+
headers["X-Request-Count"] = config.requestCount.toString();
|
|
14
|
+
}
|
|
15
|
+
const response = await fetch(config.url, { method: config.method || "GET", headers });
|
|
16
|
+
if (!response.ok)
|
|
17
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
return { data };
|
|
20
|
+
};
|
|
21
|
+
export const fetchConfigs = async ({ connectUrl, sessionId, fetchInterval, fetchTimeout, }) => {
|
|
22
|
+
const sessionURL = withQuery(joinURL(connectUrl, ACCESS_KEY_SESSION_API), { sid: sessionId });
|
|
23
|
+
let requestCount = 0;
|
|
24
|
+
const condition = async () => {
|
|
25
|
+
const { data: session } = await request({ url: sessionURL, requestCount });
|
|
26
|
+
requestCount++;
|
|
27
|
+
return Boolean(session.accessKeyId && session.accessKeySecret);
|
|
28
|
+
};
|
|
29
|
+
await pWaitFor(condition, { interval: fetchInterval, timeout: fetchTimeout });
|
|
30
|
+
const { data: session } = await request({ url: sessionURL, requestCount });
|
|
31
|
+
await request({ url: sessionURL, method: "DELETE" });
|
|
32
|
+
return {
|
|
33
|
+
...session,
|
|
34
|
+
accessKeyId: session.accessKeyId,
|
|
35
|
+
accessKeySecret: decrypt(session.accessKeySecret, session.accessKeyId, session.challenge),
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
function baseWrapSpinner(_, waiting) {
|
|
39
|
+
return Promise.resolve(waiting());
|
|
40
|
+
}
|
|
41
|
+
export async function createConnect({ connectUrl, openPage, fetchInterval = 3 * 1000, retry = 1500, source = "Blocklet CLI", connectAction = "connect-cli", wrapSpinner = baseWrapSpinner, closeOnSuccess, intervalFetchConfig, appName = "AIGNE CLI", appLogo = "https://www.aigne.io/favicon.ico?imageFilter=resize&w=32", }) {
|
|
42
|
+
const startSessionURL = joinURL(connectUrl, ACCESS_KEY_SESSION_API);
|
|
43
|
+
const { data: session } = await request({ url: startSessionURL, method: "POST" });
|
|
44
|
+
const token = session.id;
|
|
45
|
+
const pageUrl = withQuery(joinURL(connectUrl, connectAction), {
|
|
46
|
+
__token__: encodeEncryptionKey(token),
|
|
47
|
+
source,
|
|
48
|
+
closeOnSuccess,
|
|
49
|
+
cli: true,
|
|
50
|
+
appName: ` ${appName}`,
|
|
51
|
+
appLogo,
|
|
52
|
+
});
|
|
53
|
+
openPage?.(pageUrl);
|
|
54
|
+
return await wrapSpinner(`Waiting for connection: ${connectUrl}`, async () => {
|
|
55
|
+
const checkAuthorizeStatus = intervalFetchConfig ?? fetchConfigs;
|
|
56
|
+
const authorizeStatus = await checkAuthorizeStatus({
|
|
57
|
+
connectUrl,
|
|
58
|
+
sessionId: token,
|
|
59
|
+
fetchTimeout: retry * fetchInterval,
|
|
60
|
+
fetchInterval: retry,
|
|
61
|
+
});
|
|
62
|
+
return authorizeStatus;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
export async function getAIGNEHubMountPoint(url) {
|
|
66
|
+
const { origin } = new URL(url);
|
|
67
|
+
const BLOCKLET_JSON_PATH = "__blocklet__.js?type=json";
|
|
68
|
+
const blockletInfo = await fetch(joinURL(origin, BLOCKLET_JSON_PATH));
|
|
69
|
+
const blocklet = await blockletInfo.json();
|
|
70
|
+
const aigneHubMount = (blocklet?.componentMountPoints || []).find((m) => m.did === AIGNE_HUB_DID);
|
|
71
|
+
return joinURL(origin, aigneHubMount?.mountPoint || "");
|
|
72
|
+
}
|
|
73
|
+
export async function connectToAIGNEHub(url) {
|
|
74
|
+
const { origin, host } = new URL(url);
|
|
75
|
+
const connectUrl = joinURL(origin, WELLKNOWN_SERVICE_PATH_PREFIX);
|
|
76
|
+
const urlWithAIGNEHubMountPoint = await getAIGNEHubMountPoint(url);
|
|
77
|
+
try {
|
|
78
|
+
const openFn = isTest ? () => { } : open;
|
|
79
|
+
const result = await createConnect({
|
|
80
|
+
connectUrl: connectUrl,
|
|
81
|
+
connectAction: "gen-simple-access-key",
|
|
82
|
+
source: `@aigne/cli connect to AIGNE hub`,
|
|
83
|
+
closeOnSuccess: true,
|
|
84
|
+
openPage: (pageUrl) => openFn(pageUrl),
|
|
85
|
+
});
|
|
86
|
+
const accessKeyOptions = {
|
|
87
|
+
apiKey: result.accessKeySecret,
|
|
88
|
+
url: urlWithAIGNEHubMountPoint,
|
|
89
|
+
};
|
|
90
|
+
// After redirection, write the AIGNE Hub access token
|
|
91
|
+
const aigneDir = nodejs.path.join(nodejs.os.homedir(), ".aigne");
|
|
92
|
+
if (!nodejs.fsSync.existsSync(aigneDir)) {
|
|
93
|
+
nodejs.fsSync.mkdirSync(aigneDir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
const envs = parse(await nodejs.fs.readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
96
|
+
await nodejs.fs.writeFile(AIGNE_ENV_FILE, stringify({
|
|
97
|
+
...envs,
|
|
98
|
+
[host]: {
|
|
99
|
+
AIGNE_HUB_API_KEY: accessKeyOptions.apiKey,
|
|
100
|
+
AIGNE_HUB_API_URL: accessKeyOptions.url,
|
|
101
|
+
},
|
|
102
|
+
default: {
|
|
103
|
+
AIGNE_HUB_API_URL: accessKeyOptions.url,
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
return accessKeyOptions;
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
logger.error("Failed to connect to AIGNE Hub", error.message);
|
|
110
|
+
return { apiKey: undefined, url: undefined };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export const checkConnectionStatus = async (host) => {
|
|
114
|
+
// aigne-hub access token
|
|
115
|
+
if (!nodejs.fsSync.existsSync(AIGNE_ENV_FILE)) {
|
|
116
|
+
throw new Error("AIGNE_HUB_API_KEY file not found, need to login first");
|
|
117
|
+
}
|
|
118
|
+
const data = await nodejs.fs.readFile(AIGNE_ENV_FILE, "utf8");
|
|
119
|
+
if (!data.includes("AIGNE_HUB_API_KEY")) {
|
|
120
|
+
throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
|
|
121
|
+
}
|
|
122
|
+
const envs = parse(data);
|
|
123
|
+
if (!envs[host]) {
|
|
124
|
+
throw new Error("AIGNE_HUB_API_KEY host not found, need to login first");
|
|
125
|
+
}
|
|
126
|
+
const env = envs[host];
|
|
127
|
+
if (!env.AIGNE_HUB_API_KEY) {
|
|
128
|
+
throw new Error("AIGNE_HUB_API_KEY key not found, need to login first");
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
apiKey: env.AIGNE_HUB_API_KEY,
|
|
132
|
+
url: joinURL(env.AIGNE_HUB_API_URL),
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
export async function loadCredential(options) {
|
|
136
|
+
const isBlocklet = process.env.BLOCKLET_AIGNE_API_URL && process.env.BLOCKLET_AIGNE_API_PROVIDER;
|
|
137
|
+
if (isBlocklet)
|
|
138
|
+
return undefined;
|
|
139
|
+
const aigneDir = nodejs.path.join(nodejs.os.homedir(), ".aigne");
|
|
140
|
+
if (!nodejs.fsSync.existsSync(aigneDir)) {
|
|
141
|
+
nodejs.fsSync.mkdirSync(aigneDir, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
const envs = parse(await nodejs.fs.readFile(AIGNE_ENV_FILE, "utf8").catch(() => stringify({})));
|
|
144
|
+
const inquirerPrompt = (options?.inquirerPromptFn ?? inquirer.prompt);
|
|
145
|
+
const configUrl = options?.aigneHubUrl || process.env.AIGNE_HUB_API_URL;
|
|
146
|
+
const AIGNE_HUB_URL = configUrl || envs?.default?.AIGNE_HUB_API_URL || DEFAULT_AIGNE_HUB_URL;
|
|
147
|
+
const connectUrl = joinURL(new URL(AIGNE_HUB_URL).origin, WELLKNOWN_SERVICE_PATH_PREFIX);
|
|
148
|
+
const { host } = new URL(AIGNE_HUB_URL);
|
|
149
|
+
const modelName = options?.model || "";
|
|
150
|
+
const isAIGNEHubModel = (modelName.toLocaleLowerCase() || "").includes(AGENT_HUB_PROVIDER);
|
|
151
|
+
let credential = {};
|
|
152
|
+
if (isAIGNEHubModel) {
|
|
153
|
+
try {
|
|
154
|
+
credential = await checkConnectionStatus(host);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (error instanceof Error && error.message.includes("login first")) {
|
|
158
|
+
let aigneHubUrl = connectUrl;
|
|
159
|
+
if (!configUrl) {
|
|
160
|
+
const { subscribe } = await inquirerPrompt({
|
|
161
|
+
type: "list",
|
|
162
|
+
name: "subscribe",
|
|
163
|
+
message: "No LLM API Keys or AIGNE Hub connections found. How would you like to proceed?",
|
|
164
|
+
choices: [
|
|
165
|
+
{
|
|
166
|
+
name: "Connect to the Arcblock official AIGNE Hub (recommended, free credits for new users)",
|
|
167
|
+
value: "official",
|
|
168
|
+
},
|
|
169
|
+
connectUrl.includes(DEFAULT_AIGNE_HUB_URL)
|
|
170
|
+
? {
|
|
171
|
+
name: "Connect to your own AIGNE Hub instance (self-hosted)",
|
|
172
|
+
value: "custom",
|
|
173
|
+
}
|
|
174
|
+
: null,
|
|
175
|
+
{
|
|
176
|
+
name: "Exit and configure my own LLM API Keys",
|
|
177
|
+
value: "manual",
|
|
178
|
+
},
|
|
179
|
+
].filter(Boolean),
|
|
180
|
+
default: "official",
|
|
181
|
+
});
|
|
182
|
+
if (subscribe === "custom") {
|
|
183
|
+
const { customUrl } = await inquirerPrompt({
|
|
184
|
+
type: "input",
|
|
185
|
+
name: "customUrl",
|
|
186
|
+
message: "Enter the URL of your AIGNE Hub:",
|
|
187
|
+
validate(input) {
|
|
188
|
+
try {
|
|
189
|
+
const url = new URL(input);
|
|
190
|
+
return url.protocol.startsWith("http")
|
|
191
|
+
? true
|
|
192
|
+
: "Must be a valid URL with http or https";
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return "Invalid URL";
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
aigneHubUrl = customUrl;
|
|
200
|
+
}
|
|
201
|
+
else if (subscribe === "manual") {
|
|
202
|
+
console.log("You chose to configure your own LLM API Keys. Exiting...");
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
credential = await connectToAIGNEHub(aigneHubUrl);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return credential;
|
|
211
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const decrypt: (m: string, s: string, i: string) => string;
|
|
2
|
+
export declare const encrypt: (m: string, s: string, i: string) => string;
|
|
3
|
+
export declare const encodeEncryptionKey: (key: string) => string;
|
|
4
|
+
export declare const decodeEncryptionKey: (str: string) => Uint8Array<ArrayBuffer>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { AesCrypter } from "@ocap/mcrypto/lib/crypter/aes-legacy.js";
|
|
3
|
+
const aes = new AesCrypter();
|
|
4
|
+
export const decrypt = (m, s, i) => aes.decrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
|
|
5
|
+
export const encrypt = (m, s, i) => aes.encrypt(m, crypto.pbkdf2Sync(i, s, 256, 32, "sha512").toString("hex"));
|
|
6
|
+
const escapeFn = (str) => str.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
7
|
+
const unescapeFn = (str) => (str + "===".slice((str.length + 3) % 4)).replace(/-/g, "+").replace(/_/g, "/");
|
|
8
|
+
export const encodeEncryptionKey = (key) => escapeFn(Buffer.from(key).toString("base64"));
|
|
9
|
+
export const decodeEncryptionKey = (str) => new Uint8Array(Buffer.from(unescapeFn(str), "base64"));
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChatModel, ChatModelOptions } from "@aigne/core/agents/chat-model.js";
|
|
2
|
+
import type inquirer from "inquirer";
|
|
3
|
+
import type { LoadableModel, LoadCredentialOptions, Model } from "./type.js";
|
|
4
|
+
export declare function availableModels(): LoadableModel[];
|
|
5
|
+
export declare function findModel(models: LoadableModel[], provider: string): LoadableModel | undefined;
|
|
6
|
+
export declare const parseModelOption: (model?: string) => {
|
|
7
|
+
provider: string | undefined;
|
|
8
|
+
name: string | undefined;
|
|
9
|
+
};
|
|
10
|
+
export declare const formatModelName: (model: string, inquirerPrompt: typeof inquirer.prompt) => Promise<string>;
|
|
11
|
+
export declare function maskApiKey(apiKey?: string): string | undefined;
|
|
12
|
+
export declare function loadModel(model?: Model, modelOptions?: ChatModelOptions, options?: LoadCredentialOptions): Promise<ChatModel | undefined>;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { AnthropicChatModel } from "@aigne/anthropic";
|
|
2
|
+
import { BedrockChatModel } from "@aigne/bedrock";
|
|
3
|
+
import { DeepSeekChatModel } from "@aigne/deepseek";
|
|
4
|
+
import { GeminiChatModel } from "@aigne/gemini";
|
|
5
|
+
import { OllamaChatModel } from "@aigne/ollama";
|
|
6
|
+
import { OpenRouterChatModel } from "@aigne/open-router";
|
|
7
|
+
import { OpenAIChatModel } from "@aigne/openai";
|
|
8
|
+
import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
|
|
9
|
+
import { XAIChatModel } from "@aigne/xai";
|
|
10
|
+
import { NodeHttpHandler, streamCollector } from "@smithy/node-http-handler";
|
|
11
|
+
import boxen from "boxen";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
14
|
+
import { AIGNEHubChatModel } from "../index.js";
|
|
15
|
+
import { AGENT_HUB_PROVIDER, DEFAULT_AIGNE_HUB_PROVIDER_MODEL, DEFAULT_MODEL_PROVIDER, } from "./constants.js";
|
|
16
|
+
import { loadCredential } from "./credential.js";
|
|
17
|
+
const { MODEL_PROVIDER, MODEL_NAME } = nodejs.env;
|
|
18
|
+
export function availableModels() {
|
|
19
|
+
const proxy = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy"]
|
|
20
|
+
.map((i) => process.env[i])
|
|
21
|
+
.filter(Boolean)[0];
|
|
22
|
+
const httpAgent = proxy ? new HttpsProxyAgent(proxy) : undefined;
|
|
23
|
+
const clientOptions = {
|
|
24
|
+
fetchOptions: {
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
agent: httpAgent,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
name: OpenAIChatModel.name,
|
|
32
|
+
apiKeyEnvName: "OPENAI_API_KEY",
|
|
33
|
+
create: (params) => new OpenAIChatModel({ ...params, clientOptions }),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: AnthropicChatModel.name,
|
|
37
|
+
apiKeyEnvName: "ANTHROPIC_API_KEY",
|
|
38
|
+
create: (params) => new AnthropicChatModel({ ...params, clientOptions }),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: BedrockChatModel.name,
|
|
42
|
+
apiKeyEnvName: "AWS_ACCESS_KEY_ID",
|
|
43
|
+
create: (params) => new BedrockChatModel({
|
|
44
|
+
...params,
|
|
45
|
+
clientOptions: {
|
|
46
|
+
requestHandler: NodeHttpHandler.create({ httpAgent, httpsAgent: httpAgent }),
|
|
47
|
+
streamCollector,
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: DeepSeekChatModel.name,
|
|
53
|
+
apiKeyEnvName: "DEEPSEEK_API_KEY",
|
|
54
|
+
create: (params) => new DeepSeekChatModel({ ...params, clientOptions }),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: [GeminiChatModel.name, "google"],
|
|
58
|
+
apiKeyEnvName: ["GEMINI_API_KEY", "GOOGLE_API_KEY"],
|
|
59
|
+
create: (params) => new GeminiChatModel({ ...params, clientOptions }),
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: OllamaChatModel.name,
|
|
63
|
+
apiKeyEnvName: "OLLAMA_API_KEY",
|
|
64
|
+
create: (params) => new OllamaChatModel({ ...params, clientOptions }),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: OpenRouterChatModel.name,
|
|
68
|
+
apiKeyEnvName: "OPEN_ROUTER_API_KEY",
|
|
69
|
+
create: (params) => new OpenRouterChatModel({ ...params, clientOptions }),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: XAIChatModel.name,
|
|
73
|
+
apiKeyEnvName: "XAI_API_KEY",
|
|
74
|
+
create: (params) => new XAIChatModel({ ...params, clientOptions }),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: AIGNEHubChatModel.name,
|
|
78
|
+
apiKeyEnvName: "AIGNE_HUB_API_KEY",
|
|
79
|
+
create: (params) => new AIGNEHubChatModel({ ...params, clientOptions }),
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
export function findModel(models, provider) {
|
|
84
|
+
return models.find((m) => {
|
|
85
|
+
if (typeof m.name === "string") {
|
|
86
|
+
return m.name.toLowerCase().includes(provider.toLowerCase());
|
|
87
|
+
}
|
|
88
|
+
return m.name.some((n) => n.toLowerCase().includes(provider.toLowerCase()));
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
export const parseModelOption = (model) => {
|
|
92
|
+
const { provider, name } = (model || process.env.MODEL)?.match(/(?<provider>[^:]*)(:(?<name>(\S+)))?/)?.groups ?? {};
|
|
93
|
+
return { provider, name };
|
|
94
|
+
};
|
|
95
|
+
export const formatModelName = async (model, inquirerPrompt) => {
|
|
96
|
+
const models = availableModels();
|
|
97
|
+
if (!model)
|
|
98
|
+
return DEFAULT_AIGNE_HUB_PROVIDER_MODEL;
|
|
99
|
+
const { provider, name } = parseModelOption(model);
|
|
100
|
+
if (!provider) {
|
|
101
|
+
return DEFAULT_AIGNE_HUB_PROVIDER_MODEL;
|
|
102
|
+
}
|
|
103
|
+
const providerName = provider.replace(/-/g, "");
|
|
104
|
+
if (providerName.includes(AGENT_HUB_PROVIDER)) {
|
|
105
|
+
return model;
|
|
106
|
+
}
|
|
107
|
+
const m = findModel(models, providerName);
|
|
108
|
+
if (!m)
|
|
109
|
+
throw new Error(`Unsupported model: ${provider} ${name}`);
|
|
110
|
+
const apiKeyEnvName = Array.isArray(m.apiKeyEnvName) ? m.apiKeyEnvName : [m.apiKeyEnvName];
|
|
111
|
+
if (apiKeyEnvName.some((name) => name && process.env[name])) {
|
|
112
|
+
return model;
|
|
113
|
+
}
|
|
114
|
+
const result = await inquirerPrompt({
|
|
115
|
+
type: "list",
|
|
116
|
+
name: "useAigneHub",
|
|
117
|
+
message: `Seems no API Key configured for ${provider}/${name}, select your preferred way to continue:`,
|
|
118
|
+
choices: [
|
|
119
|
+
{
|
|
120
|
+
name: `Connect to AIGNE Hub to use ${name} (Recommended since free credits available)`,
|
|
121
|
+
value: true,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: `Exit and bring my owner API Key by set ${apiKeyEnvName.join(", ")}`,
|
|
125
|
+
value: false,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
default: true,
|
|
129
|
+
});
|
|
130
|
+
if (!result.useAigneHub) {
|
|
131
|
+
console.log(chalk.yellow(`You can use command "export ${apiKeyEnvName[0]}=xxx" to set API Key in your shell. Or you can set environment variables in .env file.`));
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
return `${AGENT_HUB_PROVIDER}:${provider}/${name}`;
|
|
135
|
+
};
|
|
136
|
+
export function maskApiKey(apiKey) {
|
|
137
|
+
if (!apiKey || apiKey.length <= 8)
|
|
138
|
+
return apiKey;
|
|
139
|
+
const start = apiKey.slice(0, 4);
|
|
140
|
+
const end = apiKey.slice(-4);
|
|
141
|
+
return `${start}${"*".repeat(8)}${end}`;
|
|
142
|
+
}
|
|
143
|
+
let printed = false;
|
|
144
|
+
function printChatModelInfoBox({ provider, model, credential, m, }) {
|
|
145
|
+
if (printed)
|
|
146
|
+
return;
|
|
147
|
+
printed = true;
|
|
148
|
+
const lines = [
|
|
149
|
+
`${chalk.cyan("Provider")}: ${chalk.green(provider)}`,
|
|
150
|
+
`${chalk.cyan("Model")}: ${chalk.green(model)}`,
|
|
151
|
+
];
|
|
152
|
+
if (provider.includes(AGENT_HUB_PROVIDER)) {
|
|
153
|
+
lines.push(`${chalk.cyan("API URL")}: ${chalk.green(credential?.url || "N/A")}`, `${chalk.cyan("API Key")}: ${chalk.green(maskApiKey(credential?.apiKey))}`);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const apiKeyEnvName = Array.isArray(m.apiKeyEnvName) ? m.apiKeyEnvName : [m.apiKeyEnvName];
|
|
157
|
+
const envKeyName = apiKeyEnvName.find((name) => name && process.env[name]);
|
|
158
|
+
if (envKeyName) {
|
|
159
|
+
lines.push(`${chalk.cyan("API Key")}: ${chalk.green(maskApiKey(process.env[envKeyName]))}`);
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
lines.push(`${chalk.cyan("API Key")}: ${chalk.yellow("Not found")}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
console.log("\n");
|
|
166
|
+
console.log(boxen(lines.join("\n"), { padding: 1, borderStyle: "classic", borderColor: "cyan" }));
|
|
167
|
+
console.log("\n");
|
|
168
|
+
}
|
|
169
|
+
export async function loadModel(model, modelOptions, options) {
|
|
170
|
+
const params = {
|
|
171
|
+
model: MODEL_NAME ?? model?.name ?? undefined,
|
|
172
|
+
temperature: model?.temperature ?? undefined,
|
|
173
|
+
topP: model?.topP ?? undefined,
|
|
174
|
+
frequencyPenalty: model?.frequencyPenalty ?? undefined,
|
|
175
|
+
presencePenalty: model?.presencePenalty ?? undefined,
|
|
176
|
+
};
|
|
177
|
+
const provider = (MODEL_PROVIDER ?? model?.provider ?? DEFAULT_MODEL_PROVIDER).replace(/-/g, "");
|
|
178
|
+
const models = availableModels();
|
|
179
|
+
const m = findModel(models, provider);
|
|
180
|
+
if (!m)
|
|
181
|
+
throw new Error(`Unsupported model: ${model?.provider} ${model?.name}`);
|
|
182
|
+
const credential = await loadCredential({ ...options, model: `${provider}:${params.model}` });
|
|
183
|
+
printChatModelInfoBox({ provider, model: params.model || "", credential, m });
|
|
184
|
+
return m.create({
|
|
185
|
+
...(credential || {}),
|
|
186
|
+
model: params.model,
|
|
187
|
+
modelOptions: { ...params, ...modelOptions },
|
|
188
|
+
});
|
|
189
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ChatModel, ChatModelOptions } from "@aigne/core/agents/chat-model.js";
|
|
2
|
+
export type Model = {
|
|
3
|
+
provider?: string | null;
|
|
4
|
+
name?: string | null;
|
|
5
|
+
temperature?: number | null;
|
|
6
|
+
topP?: number | null;
|
|
7
|
+
frequencyPenalty?: number | null;
|
|
8
|
+
presencePenalty?: number | null;
|
|
9
|
+
} | undefined;
|
|
10
|
+
type InquirerPromptFn = (prompt: {
|
|
11
|
+
type: string;
|
|
12
|
+
name: string;
|
|
13
|
+
message: string;
|
|
14
|
+
choices: {
|
|
15
|
+
name: string;
|
|
16
|
+
value: any;
|
|
17
|
+
}[];
|
|
18
|
+
default: any;
|
|
19
|
+
}) => Promise<any>;
|
|
20
|
+
export type LoadCredentialOptions = {
|
|
21
|
+
model?: string;
|
|
22
|
+
aigneHubUrl?: string;
|
|
23
|
+
inquirerPromptFn?: InquirerPromptFn;
|
|
24
|
+
};
|
|
25
|
+
export interface LoadableModel {
|
|
26
|
+
name: string | string[];
|
|
27
|
+
apiKeyEnvName?: string | string[];
|
|
28
|
+
create: (options: {
|
|
29
|
+
model?: string;
|
|
30
|
+
modelOptions?: ChatModelOptions;
|
|
31
|
+
apiKey?: string;
|
|
32
|
+
url?: string;
|
|
33
|
+
}) => ChatModel;
|
|
34
|
+
}
|
|
35
|
+
export type FetchResult = {
|
|
36
|
+
accessKeyId: string;
|
|
37
|
+
accessKeySecret: string;
|
|
38
|
+
};
|
|
39
|
+
export type BaseWrapSpinner = (_: string, waiting: () => Promise<FetchResult>) => Promise<FetchResult>;
|
|
40
|
+
export interface CreateConnectOptions {
|
|
41
|
+
connectUrl: string;
|
|
42
|
+
openPage?: (url: string) => void;
|
|
43
|
+
fetchInterval?: number;
|
|
44
|
+
retry?: number;
|
|
45
|
+
source?: string;
|
|
46
|
+
connectAction?: string;
|
|
47
|
+
appName?: string;
|
|
48
|
+
appLogo?: string;
|
|
49
|
+
wrapSpinner?: BaseWrapSpinner;
|
|
50
|
+
prettyUrl?: (url: string) => string;
|
|
51
|
+
closeOnSuccess?: boolean;
|
|
52
|
+
intervalFetchConfig?: (options: {
|
|
53
|
+
sessionId: string;
|
|
54
|
+
fetchInterval: number;
|
|
55
|
+
fetchTimeout: number;
|
|
56
|
+
}) => Promise<FetchResult>;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aigne/aigne-hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "AIGNE Hub SDK for integrating with Hub AI models",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -40,23 +40,30 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@ocap/mcrypto": "^1.21.0",
|
|
43
44
|
"@smithy/node-http-handler": "^4.1.0",
|
|
45
|
+
"boxen": "^8.0.1",
|
|
46
|
+
"chalk": "^5.4.1",
|
|
44
47
|
"https-proxy-agent": "^7.0.6",
|
|
48
|
+
"inquirer": "^12.7.0",
|
|
49
|
+
"open": "^10.2.0",
|
|
45
50
|
"openai": "^5.8.3",
|
|
51
|
+
"p-wait-for": "^5.0.2",
|
|
46
52
|
"ufo": "^1.6.1",
|
|
53
|
+
"yaml": "^2.8.0",
|
|
47
54
|
"zod": "^3.25.67",
|
|
48
|
-
"@aigne/anthropic": "^0.
|
|
49
|
-
"@aigne/bedrock": "^0.
|
|
50
|
-
"@aigne/core": "^1.
|
|
51
|
-
"@aigne/deepseek": "^0.7.
|
|
52
|
-
"@aigne/default-memory": "^1.
|
|
53
|
-
"@aigne/
|
|
54
|
-
"@aigne/open-router": "^0.7.
|
|
55
|
-
"@aigne/
|
|
56
|
-
"@aigne/openai": "^0.
|
|
57
|
-
"@aigne/
|
|
58
|
-
"@aigne/
|
|
59
|
-
"@aigne/
|
|
55
|
+
"@aigne/anthropic": "^0.11.1",
|
|
56
|
+
"@aigne/bedrock": "^0.9.1",
|
|
57
|
+
"@aigne/core": "^1.49.1",
|
|
58
|
+
"@aigne/deepseek": "^0.7.19",
|
|
59
|
+
"@aigne/default-memory": "^1.1.1",
|
|
60
|
+
"@aigne/gemini": "^0.9.1",
|
|
61
|
+
"@aigne/open-router": "^0.7.19",
|
|
62
|
+
"@aigne/ollama": "^0.7.19",
|
|
63
|
+
"@aigne/openai": "^0.11.1",
|
|
64
|
+
"@aigne/platform-helpers": "^0.6.1",
|
|
65
|
+
"@aigne/xai": "^0.7.19",
|
|
66
|
+
"@aigne/transport": "^0.13.1"
|
|
60
67
|
},
|
|
61
68
|
"devDependencies": {
|
|
62
69
|
"@types/bun": "^1.2.18",
|
|
@@ -65,8 +72,8 @@
|
|
|
65
72
|
"npm-run-all": "^4.1.5",
|
|
66
73
|
"rimraf": "^6.0.1",
|
|
67
74
|
"typescript": "^5.8.3",
|
|
68
|
-
"@aigne/openai": "^0.
|
|
69
|
-
"@aigne/test-utils": "^0.5.
|
|
75
|
+
"@aigne/openai": "^0.11.1",
|
|
76
|
+
"@aigne/test-utils": "^0.5.27"
|
|
70
77
|
},
|
|
71
78
|
"scripts": {
|
|
72
79
|
"lint": "tsc --noEmit",
|
package/lib/cjs/constants.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { ChatModel, ChatModelOptions } from "@aigne/core/agents/chat-model.js";
|
|
2
|
-
export declare const AIGNE_HUB_URL = "https://hub.aigne.io/";
|
|
3
|
-
export declare function availableModels(): LoadableModel[];
|
|
4
|
-
export interface LoadableModel {
|
|
5
|
-
name: string | string[];
|
|
6
|
-
apiKeyEnvName?: string | string[];
|
|
7
|
-
create: (options: {
|
|
8
|
-
model?: string;
|
|
9
|
-
modelOptions?: ChatModelOptions;
|
|
10
|
-
apiKey?: string;
|
|
11
|
-
url?: string;
|
|
12
|
-
}) => ChatModel;
|
|
13
|
-
}
|
|
14
|
-
export declare function findModel(models: LoadableModel[], provider: string): LoadableModel | undefined;
|
|
15
|
-
type Model = {
|
|
16
|
-
provider?: string | null;
|
|
17
|
-
name?: string | null;
|
|
18
|
-
temperature?: number | null;
|
|
19
|
-
topP?: number | null;
|
|
20
|
-
frequencyPenalty?: number | null;
|
|
21
|
-
presencePenalty?: number | null;
|
|
22
|
-
} | undefined;
|
|
23
|
-
export declare function loadModel(model?: Model, modelOptions?: ChatModelOptions, credential?: {
|
|
24
|
-
apiKey?: string;
|
|
25
|
-
url?: string;
|
|
26
|
-
}): Promise<ChatModel | undefined>;
|
|
27
|
-
export declare function getAIGNEHubMountPoint(url: string): Promise<string>;
|
|
28
|
-
export {};
|