@humain/terminal 0.1.0 → 0.2.1
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 +33 -0
- package/dist/bundle/chunks/{anthropic-messages-XULXUBIG.js → anthropic-messages-QFCEKGI3.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-JEG2XOYC.js → azure-openai-responses-G4ZMHV37.js} +1 -1
- package/dist/bundle/chunks/chunk-7T6XCLJF.js +22 -0
- package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
- package/dist/bundle/chunks/chunk-EY4DWHQU.js +2 -0
- package/dist/bundle/chunks/{chunk-RM336IEI.js → chunk-I47ZWR55.js} +486 -509
- package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
- package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
- package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
- package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
- package/dist/bundle/chunks/compat-QCV26HNS.js +2 -0
- package/dist/bundle/chunks/dist-PRPIUY3U.js +2 -0
- package/dist/bundle/chunks/esm-E7UVEYTO.js +4 -0
- package/dist/bundle/chunks/{extract-TRW5FBY6.js → extract-CTIFQZDJ.js} +15 -14
- package/dist/bundle/chunks/{google-generative-ai-OX6VSCA6.js → google-generative-ai-RSUU4MWO.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-GW76GSS2.js → google-vertex-CKQPOITO.js} +1 -1
- package/dist/bundle/chunks/host-WP3MVKS2.js +2 -0
- package/dist/bundle/chunks/{node-KOIP55YZ.js → node-NF43OPII.js} +1 -1
- package/dist/bundle/chunks/{node-XY3CBY3C.js → node-UMFM7CDZ.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-NPLWP6ZC.js → openai-codex-responses-S34V5ZSD.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-OT2GTJKC.js → openai-completions-T2IGVUEI.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-5A6CR7GI.js → openai-responses-GYZODCT3.js} +1 -1
- package/dist/bundle/chunks/readability-LAGF34IB.js +3 -0
- package/dist/bundle/chunks/turndown.cjs-HRMZVRUL.js +2 -0
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli.js +1 -5
- package/dist/core/agent-session.js +2 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +21 -8
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/subagent-env.d.ts +18 -0
- package/dist/core/tools/subagent-env.d.ts.map +1 -0
- package/dist/core/tools/subagent-env.js +43 -0
- package/dist/core/tools/subagent-env.js.map +1 -0
- package/dist/core/tools/subagent-scheduler.d.ts +31 -0
- package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
- package/dist/core/tools/subagent-scheduler.js +203 -0
- package/dist/core/tools/subagent-scheduler.js.map +1 -0
- package/dist/core/tools/subagent.d.ts +83 -3
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +214 -76
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/humain/index.d.ts.map +1 -1
- package/dist/humain/index.js +2 -0
- package/dist/humain/index.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
- package/dist/humain/mcp-adapter/vendor/README.md +125 -19
- package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
- package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
- package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
- package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
- package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
- package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
- package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
- package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
- package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
- package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
- package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
- package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
- package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
- package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
- package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
- package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
- package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
- package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
- package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
- package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
- package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
- package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
- package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
- package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
- package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
- package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
- package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
- package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
- package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
- package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
- package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
- package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
- package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
- package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
- package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
- package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
- package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
- package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
- package/dist/humain/mcp-adapter/vendor/package.json +13 -3
- package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
- package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
- package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
- package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
- package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
- package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
- package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
- package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
- package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
- package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
- package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
- package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
- package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
- package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
- package/dist/humain/node-http.d.ts +3 -0
- package/dist/humain/node-http.d.ts.map +1 -0
- package/dist/humain/node-http.js +119 -0
- package/dist/humain/node-http.js.map +1 -0
- package/dist/humain/node-provider.d.ts +4 -0
- package/dist/humain/node-provider.d.ts.map +1 -0
- package/dist/humain/node-provider.js +359 -0
- package/dist/humain/node-provider.js.map +1 -0
- package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
- package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/abortable.js +22 -0
- package/dist/humain/web-access/vendor/abortable.js.map +1 -0
- package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
- package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
- package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
- package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/credential-source.js +1 -0
- package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.js +20 -2
- package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
- package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/curator-run.js +38 -0
- package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.js +2 -0
- package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/extract.js +58 -11
- package/dist/humain/web-access/vendor/extract.js.map +1 -1
- package/dist/humain/web-access/vendor/fetch-params.js +1 -1
- package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
- package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.js +12 -5
- package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
- package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/index.js +23 -19
- package/dist/humain/web-access/vendor/index.js.map +1 -1
- package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/openai-search.js +40 -14
- package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
- package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/page-query.js +6 -2
- package/dist/humain/web-access/vendor/page-query.js.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
- package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
- package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
- package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/serpapi.js +215 -0
- package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
- package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
- package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
- package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/storage.js +9 -0
- package/dist/humain/web-access/vendor/storage.js.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.js +35 -24
- package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
- package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/utils.js +5 -3
- package/dist/humain/web-access/vendor/utils.js.map +1 -1
- package/dist/index.js +1 -51
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +16 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +7 -2
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +46 -21
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +10 -13
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/json-event.d.ts +8 -0
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +17 -0
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +3 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/environment-variables.md +8 -0
- package/docs/humain-terminal.md +6 -0
- package/docs/json.md +16 -0
- package/docs/providers.md +19 -1
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
- package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
- package/src/humain/mcp-adapter/upstream.json +6 -4
- package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
- package/src/humain/web-access/UPSTREAM.md +18 -8
- package/src/humain/web-access/upstream.json +6 -5
- package/dist/bundle/chunks/chunk-2LC3S3VV.js +0 -105
|
@@ -1,9 +1,50 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
4
|
import { homedir, platform } from "node:os";
|
|
4
|
-
import { extname, isAbsolute, join } from "node:path";
|
|
5
|
+
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
|
+
import stripJsonComments from "strip-json-comments";
|
|
5
7
|
import type { McpConfig, ServerEntry } from "./types.ts";
|
|
6
8
|
|
|
9
|
+
export function parseJsonWithComments(raw: string): unknown {
|
|
10
|
+
return JSON.parse(stripJsonComments(raw, { trailingCommas: true }));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Resolve a candidate only when its real path stays within the real root. */
|
|
14
|
+
export function resolveRealContainedPath(root: string, candidate: string, allowMissing = false): string | null {
|
|
15
|
+
const contained = resolveContainedPath(root, candidate);
|
|
16
|
+
if (!contained) return null;
|
|
17
|
+
const canonical = (path: string): string => {
|
|
18
|
+
let existing = path;
|
|
19
|
+
while (allowMissing && !existsSync(existing)) {
|
|
20
|
+
const parent = dirname(existing);
|
|
21
|
+
if (parent === existing) throw new Error("No existing path ancestor");
|
|
22
|
+
existing = parent;
|
|
23
|
+
}
|
|
24
|
+
return resolve(realpathSync(existing), relative(existing, path));
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
return resolveContainedPath(canonical(root), canonical(contained));
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveContainedPath(root: string, candidate: string): string | null {
|
|
34
|
+
const resolved = resolve(root, candidate);
|
|
35
|
+
const rel = relative(root, resolved);
|
|
36
|
+
return rel === "" || (!rel.startsWith("..") && !rel.startsWith(sep) && !isAbsolute(rel)) ? resolved : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function stableStringify(value: unknown): string {
|
|
40
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "undefined";
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
return `[${value.map(item => stableStringify(item)).join(",")}]`;
|
|
43
|
+
}
|
|
44
|
+
const object = value as Record<string, unknown>;
|
|
45
|
+
return `{${Object.keys(object).sort().map(key => `${JSON.stringify(key)}:${stableStringify(object[key])}`).join(",")}}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
7
48
|
async function execOpen(pi: ExtensionAPI, target: string, browser?: string, signal?: AbortSignal) {
|
|
8
49
|
const os = platform();
|
|
9
50
|
|
|
@@ -64,11 +105,28 @@ export async function parallelLimit<T, R>(
|
|
|
64
105
|
}
|
|
65
106
|
|
|
66
107
|
export function getConfigPathFromArgv(): string | undefined {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
108
|
+
let configPath: string | undefined;
|
|
109
|
+
for (let index = 2; index < process.argv.length; index++) {
|
|
110
|
+
const arg = process.argv[index];
|
|
111
|
+
if (arg === undefined) continue;
|
|
112
|
+
if (arg === "--") break;
|
|
113
|
+
|
|
114
|
+
if (arg === "--mcp-config") {
|
|
115
|
+
const value = process.argv[index + 1];
|
|
116
|
+
if (value !== undefined && !value.startsWith("-") && !value.startsWith("@")) {
|
|
117
|
+
configPath = value;
|
|
118
|
+
index++;
|
|
119
|
+
} else {
|
|
120
|
+
configPath = undefined;
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (arg.startsWith("--mcp-config=")) {
|
|
126
|
+
configPath = arg.slice("--mcp-config=".length);
|
|
127
|
+
}
|
|
70
128
|
}
|
|
71
|
-
return
|
|
129
|
+
return configPath;
|
|
72
130
|
}
|
|
73
131
|
|
|
74
132
|
export function interpolateEnvVars(value: string): string;
|
|
@@ -80,7 +138,7 @@ export function interpolateEnvVars(value: string, environment: NodeJS.ProcessEnv
|
|
|
80
138
|
.replace(/\{env:(\w+)\}/g, (_, name) => environment[name] ?? "");
|
|
81
139
|
}
|
|
82
140
|
|
|
83
|
-
function getMissingEnvVars(value: string, environment: NodeJS.ProcessEnv): string[] {
|
|
141
|
+
export function getMissingEnvVars(value: string, environment: NodeJS.ProcessEnv = process.env): string[] {
|
|
84
142
|
const missing = new Set<string>();
|
|
85
143
|
for (const match of value.matchAll(/\$\{(\w+)\}|\$env:(\w+)|\{env:(\w+)\}/g)) {
|
|
86
144
|
const name = match[1] ?? match[2] ?? match[3];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-http.d.ts","sourceRoot":"","sources":["../../src/humain/node-http.ts"],"names":[],"mappings":"AAAA,OAAO,EAMN,KAAK,eAAe,EAEpB,MAAM,uBAAuB,CAAC;AAU/B,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,eAAe,GAAG,eAAe,CAgHxE"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { lazyStream, } from "@earendil-works/pi-ai";
|
|
2
|
+
const PERMANENT_LIMIT_CODES = new Set([
|
|
3
|
+
"weekly_cap_exceeded",
|
|
4
|
+
"quota_exceeded",
|
|
5
|
+
"partner_credit_limit_exceeded",
|
|
6
|
+
"insufficient_quota",
|
|
7
|
+
"billing_hard_limit_reached",
|
|
8
|
+
]);
|
|
9
|
+
export function withHumainNodeHttp(api) {
|
|
10
|
+
const run = (model, context, options, simple) => lazyStream(model, async () => {
|
|
11
|
+
let httpError;
|
|
12
|
+
let requestId = null;
|
|
13
|
+
let retryAfter = null;
|
|
14
|
+
const key = options?.apiKey;
|
|
15
|
+
const redact = (value) => (key ? value.replaceAll(key, "[redacted]") : value);
|
|
16
|
+
const fetcher = options?.fetch ?? globalThis.fetch;
|
|
17
|
+
const requestOptions = {
|
|
18
|
+
...options,
|
|
19
|
+
// The Anthropic adapter must not interpret a gateway key as an OAuth token.
|
|
20
|
+
apiKey: model.api === "anthropic-messages" ? "humain-node" : key,
|
|
21
|
+
fetch: async (input, init) => {
|
|
22
|
+
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
|
|
23
|
+
if (key)
|
|
24
|
+
headers.set("Authorization", `Bearer ${key}`);
|
|
25
|
+
headers.delete("x-api-key");
|
|
26
|
+
if (model.api === "anthropic-messages") {
|
|
27
|
+
// The SDK appends /v1/messages?beta=true; Node exposes <configured base>/messages.
|
|
28
|
+
const url = `${model.baseUrl.replace(/\/+$/u, "")}/messages`;
|
|
29
|
+
input = input instanceof Request ? new Request(url, input) : url;
|
|
30
|
+
headers.delete("anthropic-beta");
|
|
31
|
+
}
|
|
32
|
+
requestId = null;
|
|
33
|
+
retryAfter = null;
|
|
34
|
+
httpError = undefined;
|
|
35
|
+
const response = await fetcher(input, { ...init, headers, redirect: "error" });
|
|
36
|
+
requestId = response.headers.get("x-request-id");
|
|
37
|
+
retryAfter = response.headers.get("retry-after");
|
|
38
|
+
if (response.ok)
|
|
39
|
+
return response;
|
|
40
|
+
const payload = await response
|
|
41
|
+
.clone()
|
|
42
|
+
.json()
|
|
43
|
+
.catch(() => undefined);
|
|
44
|
+
const error = typeof payload === "object" && payload !== null && "error" in payload ? payload.error : undefined;
|
|
45
|
+
const code = typeof error === "object" && error !== null
|
|
46
|
+
? "code" in error && typeof error.code === "string"
|
|
47
|
+
? error.code
|
|
48
|
+
: "type" in error && typeof error.type === "string"
|
|
49
|
+
? error.type
|
|
50
|
+
: undefined
|
|
51
|
+
: undefined;
|
|
52
|
+
let retryable = !PERMANENT_LIMIT_CODES.has(code ?? "") &&
|
|
53
|
+
response.headers.get("x-should-retry") !== "false" &&
|
|
54
|
+
([408, 409, 429].includes(response.status) || response.status >= 500);
|
|
55
|
+
httpError = { status: response.status, code: code && redact(code), retryable };
|
|
56
|
+
if (retryAfter) {
|
|
57
|
+
const retryAt = /^\d+(?:\.\d+)?$/u.test(retryAfter)
|
|
58
|
+
? Date.now() + Number(retryAfter) * 1000
|
|
59
|
+
: Date.parse(retryAfter);
|
|
60
|
+
if (Number.isFinite(retryAt)) {
|
|
61
|
+
const delay = retryAt - Date.now();
|
|
62
|
+
const maxDelay = options?.maxRetryDelayMs ?? 60_000;
|
|
63
|
+
// Fail instead of shortening a server delay or overflowing the JS timer.
|
|
64
|
+
if (delay > 2_147_483_647 || (maxDelay > 0 && delay > maxDelay)) {
|
|
65
|
+
retryable = false;
|
|
66
|
+
httpError.retryable = false;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
httpError.retryAt = retryAt;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!retryable) {
|
|
74
|
+
const responseHeaders = new Headers(response.headers);
|
|
75
|
+
responseHeaders.set("x-should-retry", "false");
|
|
76
|
+
return new Response(response.body, {
|
|
77
|
+
status: response.status,
|
|
78
|
+
statusText: response.statusText,
|
|
79
|
+
headers: responseHeaders,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return response;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const source = simple
|
|
86
|
+
? api.streamSimple(model, context, requestOptions)
|
|
87
|
+
: api.stream(model, context, requestOptions);
|
|
88
|
+
return (async function* () {
|
|
89
|
+
for await (const event of source) {
|
|
90
|
+
if (event.type === "error") {
|
|
91
|
+
event.error.errorMessage = redact([
|
|
92
|
+
event.error.errorMessage ?? "HUMAIN Node request failed",
|
|
93
|
+
httpError?.code && `code: ${httpError.code}`,
|
|
94
|
+
httpError?.status === 401 && "Run /login humain-node to replace the API key.",
|
|
95
|
+
httpError?.status === 404 &&
|
|
96
|
+
httpError.code === "model_not_found" &&
|
|
97
|
+
"Use /model to refresh the catalog and choose an available model (or restart with --list-models).",
|
|
98
|
+
retryAfter && `Retry-After: ${retryAfter}`,
|
|
99
|
+
requestId && `X-Request-ID: ${requestId}`,
|
|
100
|
+
]
|
|
101
|
+
.filter(Boolean)
|
|
102
|
+
.join("; "));
|
|
103
|
+
if (httpError) {
|
|
104
|
+
event.error.diagnostics = [
|
|
105
|
+
...(event.error.diagnostics ?? []),
|
|
106
|
+
{ type: "provider_http_error", timestamp: Date.now(), details: httpError },
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
yield event;
|
|
111
|
+
}
|
|
112
|
+
})();
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
stream: (model, context, options) => run(model, context, options, false),
|
|
116
|
+
streamSimple: (model, context, options) => run(model, context, options, true),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=node-http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-http.js","sourceRoot":"","sources":["../../src/humain/node-http.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Api,\n\ttype AssistantMessageEvent,\n\ttype Context,\n\tlazyStream,\n\ttype Model,\n\ttype ProviderStreams,\n\ttype SimpleStreamOptions,\n} from \"@earendil-works/pi-ai\";\n\nconst PERMANENT_LIMIT_CODES = new Set([\n\t\"weekly_cap_exceeded\",\n\t\"quota_exceeded\",\n\t\"partner_credit_limit_exceeded\",\n\t\"insufficient_quota\",\n\t\"billing_hard_limit_reached\",\n]);\n\nexport function withHumainNodeHttp(api: ProviderStreams): ProviderStreams {\n\tconst run = (model: Model<Api>, context: Context, options: SimpleStreamOptions | undefined, simple: boolean) =>\n\t\tlazyStream(model, async () => {\n\t\t\tlet httpError: Record<string, unknown> | undefined;\n\t\t\tlet requestId: string | null = null;\n\t\t\tlet retryAfter: string | null = null;\n\t\t\tconst key = options?.apiKey;\n\t\t\tconst redact = (value: string): string => (key ? value.replaceAll(key, \"[redacted]\") : value);\n\t\t\tconst fetcher = options?.fetch ?? globalThis.fetch;\n\t\t\tconst requestOptions = {\n\t\t\t\t...options,\n\t\t\t\t// The Anthropic adapter must not interpret a gateway key as an OAuth token.\n\t\t\t\tapiKey: model.api === \"anthropic-messages\" ? \"humain-node\" : key,\n\t\t\t\tfetch: async (input: Parameters<typeof fetch>[0], init?: RequestInit): Promise<Response> => {\n\t\t\t\t\tconst headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));\n\t\t\t\t\tif (key) headers.set(\"Authorization\", `Bearer ${key}`);\n\t\t\t\t\theaders.delete(\"x-api-key\");\n\t\t\t\t\tif (model.api === \"anthropic-messages\") {\n\t\t\t\t\t\t// The SDK appends /v1/messages?beta=true; Node exposes <configured base>/messages.\n\t\t\t\t\t\tconst url = `${model.baseUrl.replace(/\\/+$/u, \"\")}/messages`;\n\t\t\t\t\t\tinput = input instanceof Request ? new Request(url, input) : url;\n\t\t\t\t\t\theaders.delete(\"anthropic-beta\");\n\t\t\t\t\t}\n\t\t\t\t\trequestId = null;\n\t\t\t\t\tretryAfter = null;\n\t\t\t\t\thttpError = undefined;\n\t\t\t\t\tconst response = await fetcher(input, { ...init, headers, redirect: \"error\" });\n\t\t\t\t\trequestId = response.headers.get(\"x-request-id\");\n\t\t\t\t\tretryAfter = response.headers.get(\"retry-after\");\n\t\t\t\t\tif (response.ok) return response;\n\t\t\t\t\tconst payload: unknown = await response\n\t\t\t\t\t\t.clone()\n\t\t\t\t\t\t.json()\n\t\t\t\t\t\t.catch(() => undefined);\n\t\t\t\t\tconst error =\n\t\t\t\t\t\ttypeof payload === \"object\" && payload !== null && \"error\" in payload ? payload.error : undefined;\n\t\t\t\t\tconst code =\n\t\t\t\t\t\ttypeof error === \"object\" && error !== null\n\t\t\t\t\t\t\t? \"code\" in error && typeof error.code === \"string\"\n\t\t\t\t\t\t\t\t? error.code\n\t\t\t\t\t\t\t\t: \"type\" in error && typeof error.type === \"string\"\n\t\t\t\t\t\t\t\t\t? error.type\n\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\tlet retryable =\n\t\t\t\t\t\t!PERMANENT_LIMIT_CODES.has(code ?? \"\") &&\n\t\t\t\t\t\tresponse.headers.get(\"x-should-retry\") !== \"false\" &&\n\t\t\t\t\t\t([408, 409, 429].includes(response.status) || response.status >= 500);\n\t\t\t\t\thttpError = { status: response.status, code: code && redact(code), retryable };\n\t\t\t\t\tif (retryAfter) {\n\t\t\t\t\t\tconst retryAt = /^\\d+(?:\\.\\d+)?$/u.test(retryAfter)\n\t\t\t\t\t\t\t? Date.now() + Number(retryAfter) * 1000\n\t\t\t\t\t\t\t: Date.parse(retryAfter);\n\t\t\t\t\t\tif (Number.isFinite(retryAt)) {\n\t\t\t\t\t\t\tconst delay = retryAt - Date.now();\n\t\t\t\t\t\t\tconst maxDelay = options?.maxRetryDelayMs ?? 60_000;\n\t\t\t\t\t\t\t// Fail instead of shortening a server delay or overflowing the JS timer.\n\t\t\t\t\t\t\tif (delay > 2_147_483_647 || (maxDelay > 0 && delay > maxDelay)) {\n\t\t\t\t\t\t\t\tretryable = false;\n\t\t\t\t\t\t\t\thttpError.retryable = false;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\thttpError.retryAt = retryAt;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (!retryable) {\n\t\t\t\t\t\tconst responseHeaders = new Headers(response.headers);\n\t\t\t\t\t\tresponseHeaders.set(\"x-should-retry\", \"false\");\n\t\t\t\t\t\treturn new Response(response.body, {\n\t\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\t\t\theaders: responseHeaders,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\treturn response;\n\t\t\t\t},\n\t\t\t};\n\t\t\tconst source = simple\n\t\t\t\t? api.streamSimple(model, context, requestOptions)\n\t\t\t\t: api.stream(model, context, requestOptions);\n\t\t\treturn (async function* (): AsyncGenerator<AssistantMessageEvent> {\n\t\t\t\tfor await (const event of source) {\n\t\t\t\t\tif (event.type === \"error\") {\n\t\t\t\t\t\tevent.error.errorMessage = redact(\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\tevent.error.errorMessage ?? \"HUMAIN Node request failed\",\n\t\t\t\t\t\t\t\thttpError?.code && `code: ${httpError.code}`,\n\t\t\t\t\t\t\t\thttpError?.status === 401 && \"Run /login humain-node to replace the API key.\",\n\t\t\t\t\t\t\t\thttpError?.status === 404 &&\n\t\t\t\t\t\t\t\t\thttpError.code === \"model_not_found\" &&\n\t\t\t\t\t\t\t\t\t\"Use /model to refresh the catalog and choose an available model (or restart with --list-models).\",\n\t\t\t\t\t\t\t\tretryAfter && `Retry-After: ${retryAfter}`,\n\t\t\t\t\t\t\t\trequestId && `X-Request-ID: ${requestId}`,\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t.filter(Boolean)\n\t\t\t\t\t\t\t\t.join(\"; \"),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (httpError) {\n\t\t\t\t\t\t\tevent.error.diagnostics = [\n\t\t\t\t\t\t\t\t...(event.error.diagnostics ?? []),\n\t\t\t\t\t\t\t\t{ type: \"provider_http_error\", timestamp: Date.now(), details: httpError },\n\t\t\t\t\t\t\t];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tyield event;\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\treturn {\n\t\tstream: (model, context, options) => run(model, context, options, false),\n\t\tstreamSimple: (model, context, options) => run(model, context, options, true),\n\t};\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-provider.d.ts","sourceRoot":"","sources":["../../src/humain/node-provider.ts"],"names":[],"mappings":"AACA,OAAO,EAQN,KAAK,QAAQ,EAEb,MAAM,uBAAuB,CAAC;AAM/B,eAAO,MAAM,oBAAoB,gBAAgB,CAAC;AAwRlD,wBAAgB,wBAAwB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,QAAQ,CAgGvF"}
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { createProvider, } from "@earendil-works/pi-ai";
|
|
3
|
+
import { anthropicMessagesApi } from "@earendil-works/pi-ai/api/anthropic-messages.lazy";
|
|
4
|
+
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
5
|
+
import { openAIResponsesApi } from "@earendil-works/pi-ai/api/openai-responses.lazy";
|
|
6
|
+
import { withHumainNodeHttp } from "./node-http.js";
|
|
7
|
+
export const HUMAIN_NODE_PROVIDER = "humain-node";
|
|
8
|
+
const DEFAULT_BASE_URL = "https://api.node.humain.com/v1";
|
|
9
|
+
const API_KEY_ENV = "HUMAIN_NODE_API_KEY";
|
|
10
|
+
const BASE_URL_ENV = "HUMAIN_NODE_BASE_URL";
|
|
11
|
+
const RETRYABLE_STATUS_CODES = new Set([408, 425, 429, 500, 502, 503, 504]);
|
|
12
|
+
const PERMANENT_LIMIT_CODES = new Set([
|
|
13
|
+
"billing_hard_limit_reached",
|
|
14
|
+
"insufficient_quota",
|
|
15
|
+
"partner_credit_limit_exceeded",
|
|
16
|
+
"quota_exceeded",
|
|
17
|
+
"weekly_cap_exceeded",
|
|
18
|
+
]);
|
|
19
|
+
const REQUEST_TIMEOUT_MS = 5_000;
|
|
20
|
+
const MAX_RETRIES = 2;
|
|
21
|
+
const MAX_RETRY_DELAY_MS = 2_000;
|
|
22
|
+
const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
23
|
+
function normalizeBaseUrl(value) {
|
|
24
|
+
let url;
|
|
25
|
+
try {
|
|
26
|
+
url = new URL(value.trim());
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
throw new Error("Invalid HUMAIN Node base URL");
|
|
30
|
+
}
|
|
31
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
32
|
+
if (url.protocol !== "https:" && !(local && url.protocol === "http:")) {
|
|
33
|
+
throw new Error("HUMAIN Node base URL must use HTTPS (HTTP is allowed only for localhost)");
|
|
34
|
+
}
|
|
35
|
+
if (url.username || url.password || url.search || url.hash) {
|
|
36
|
+
throw new Error("HUMAIN Node base URL must not contain credentials, a query, or a fragment");
|
|
37
|
+
}
|
|
38
|
+
url.pathname = url.pathname.replace(/\/+$/u, "") || "/";
|
|
39
|
+
return url.toString().replace(/\/$/u, "");
|
|
40
|
+
}
|
|
41
|
+
function credentialBaseUrl(credential) {
|
|
42
|
+
const value = credential?.env?.[BASE_URL_ENV];
|
|
43
|
+
return typeof value === "string" && value.trim() ? normalizeBaseUrl(value) : undefined;
|
|
44
|
+
}
|
|
45
|
+
async function resolveBaseUrl(ctx, credential) {
|
|
46
|
+
return credentialBaseUrl(credential) ?? normalizeBaseUrl((await ctx.env(BASE_URL_ENV)) ?? DEFAULT_BASE_URL);
|
|
47
|
+
}
|
|
48
|
+
function endpointApi(value) {
|
|
49
|
+
const normalized = value
|
|
50
|
+
.trim()
|
|
51
|
+
.toLowerCase()
|
|
52
|
+
.replace(/^https?:\/\/[^/]+/u, "")
|
|
53
|
+
.replace(/\/+$/u, "");
|
|
54
|
+
switch (normalized) {
|
|
55
|
+
case "responses":
|
|
56
|
+
case "/responses":
|
|
57
|
+
case "openai-responses":
|
|
58
|
+
case "openai_responses":
|
|
59
|
+
return "openai-responses";
|
|
60
|
+
case "chat/completions":
|
|
61
|
+
case "/chat/completions":
|
|
62
|
+
case "chat-completions":
|
|
63
|
+
case "chat_completions":
|
|
64
|
+
case "openai-completions":
|
|
65
|
+
case "openai_chat_completions":
|
|
66
|
+
return "openai-completions";
|
|
67
|
+
case "messages":
|
|
68
|
+
case "/messages":
|
|
69
|
+
case "anthropic-messages":
|
|
70
|
+
case "anthropic_messages":
|
|
71
|
+
return "anthropic-messages";
|
|
72
|
+
default:
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function modelApi(id, node) {
|
|
77
|
+
if (typeof node.api_interface === "string") {
|
|
78
|
+
const primary = endpointApi(node.api_interface);
|
|
79
|
+
if (primary)
|
|
80
|
+
return primary;
|
|
81
|
+
}
|
|
82
|
+
const supported = Array.isArray(node.supported_formats)
|
|
83
|
+
? node.supported_formats.flatMap((value) => (typeof value === "string" ? (endpointApi(value) ?? []) : []))
|
|
84
|
+
: [];
|
|
85
|
+
for (const preferred of ["openai-responses", "openai-completions", "anthropic-messages"]) {
|
|
86
|
+
if (supported.includes(preferred))
|
|
87
|
+
return preferred;
|
|
88
|
+
}
|
|
89
|
+
const primary = typeof node.api_interface === "string" ? node.api_interface : "missing";
|
|
90
|
+
throw new Error(`HUMAIN Node model "${id}" has an unsupported API interface: ${primary}`);
|
|
91
|
+
}
|
|
92
|
+
function positiveInteger(value, field, id) {
|
|
93
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
94
|
+
throw new Error(`HUMAIN Node model "${id}" has invalid ${field}`);
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
function toModel(value, baseUrl) {
|
|
99
|
+
if (typeof value !== "object" || value === null || !("id" in value)) {
|
|
100
|
+
throw new Error("HUMAIN Node returned a malformed model entry");
|
|
101
|
+
}
|
|
102
|
+
const raw = value;
|
|
103
|
+
if (raw.active === false)
|
|
104
|
+
return undefined;
|
|
105
|
+
const node = typeof raw.node === "object" && raw.node !== null ? raw.node : raw;
|
|
106
|
+
const payload = { ...raw, node };
|
|
107
|
+
if (typeof payload.id !== "string" || !payload.id.trim()) {
|
|
108
|
+
throw new Error("HUMAIN Node returned a malformed model entry");
|
|
109
|
+
}
|
|
110
|
+
const id = payload.id.trim();
|
|
111
|
+
if (typeof payload.node.supports_streaming !== "boolean") {
|
|
112
|
+
throw new Error(`HUMAIN Node model "${id}" has invalid supports_streaming`);
|
|
113
|
+
}
|
|
114
|
+
if (typeof payload.node.supports_function_calling !== "boolean") {
|
|
115
|
+
throw new Error(`HUMAIN Node model "${id}" has invalid supports_function_calling`);
|
|
116
|
+
}
|
|
117
|
+
if (payload.node.supports_streaming === false || payload.node.supports_function_calling === false)
|
|
118
|
+
return undefined;
|
|
119
|
+
let api;
|
|
120
|
+
try {
|
|
121
|
+
api = modelApi(id, payload.node);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (payload.node.api_interface === "image_generations" || payload.node.api_interface === "realtime")
|
|
125
|
+
return undefined;
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
const contextWindow = payload.node.max_context_tokens;
|
|
129
|
+
const maxTokens = payload.node.max_output_tokens;
|
|
130
|
+
if (contextWindow === null || maxTokens === null || contextWindow === 0 || maxTokens === 0)
|
|
131
|
+
return undefined;
|
|
132
|
+
return {
|
|
133
|
+
id,
|
|
134
|
+
name: typeof payload.display_name === "string" && payload.display_name.trim()
|
|
135
|
+
? payload.display_name.trim()
|
|
136
|
+
: typeof payload.name === "string" && payload.name.trim()
|
|
137
|
+
? payload.name.trim()
|
|
138
|
+
: id,
|
|
139
|
+
api,
|
|
140
|
+
provider: HUMAIN_NODE_PROVIDER,
|
|
141
|
+
baseUrl,
|
|
142
|
+
reasoning: false,
|
|
143
|
+
input: payload.node.supports_images === true ? ["text", "image"] : ["text"],
|
|
144
|
+
cost: ZERO_COST,
|
|
145
|
+
contextWindow: positiveInteger(contextWindow, "max_context_tokens", id),
|
|
146
|
+
maxTokens: positiveInteger(maxTokens, "max_output_tokens", id),
|
|
147
|
+
...(api === "openai-completions"
|
|
148
|
+
? {
|
|
149
|
+
compat: {
|
|
150
|
+
supportsStore: false,
|
|
151
|
+
supportsDeveloperRole: false,
|
|
152
|
+
supportsReasoningEffort: false,
|
|
153
|
+
supportsUsageInStreaming: true,
|
|
154
|
+
supportsStrictMode: false,
|
|
155
|
+
supportsLongCacheRetention: false,
|
|
156
|
+
maxTokensField: "max_tokens",
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
: {}),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function retryDelay(response, attempt) {
|
|
163
|
+
const retryAfter = response.headers.get("retry-after");
|
|
164
|
+
if (retryAfter) {
|
|
165
|
+
const seconds = Number(retryAfter);
|
|
166
|
+
const delay = Number.isFinite(seconds) ? seconds * 1_000 : Date.parse(retryAfter) - Date.now();
|
|
167
|
+
if (Number.isFinite(delay)) {
|
|
168
|
+
const bounded = Math.max(0, delay);
|
|
169
|
+
return bounded <= MAX_RETRY_DELAY_MS ? bounded : undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return 100 * 2 ** attempt;
|
|
173
|
+
}
|
|
174
|
+
function errorDetails(response, payload) {
|
|
175
|
+
const error = payload.error;
|
|
176
|
+
const code = typeof error?.code === "string" ? error.code : undefined;
|
|
177
|
+
const type = typeof error?.type === "string" ? error.type : undefined;
|
|
178
|
+
const detail = typeof error?.message === "string" ? error.message : undefined;
|
|
179
|
+
const retryAfter = response.headers.get("retry-after");
|
|
180
|
+
const requestId = response.headers.get("x-request-id");
|
|
181
|
+
return {
|
|
182
|
+
code,
|
|
183
|
+
message: [
|
|
184
|
+
`HUMAIN Node model discovery failed: ${response.status}`,
|
|
185
|
+
code && `code=${code}`,
|
|
186
|
+
type && `type=${type}`,
|
|
187
|
+
detail,
|
|
188
|
+
retryAfter && `Retry-After: ${retryAfter}`,
|
|
189
|
+
requestId && `X-Request-ID: ${requestId}`,
|
|
190
|
+
]
|
|
191
|
+
.filter(Boolean)
|
|
192
|
+
.join("; "),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async function fetchModelsUnredacted(baseUrl, apiKey, signal) {
|
|
196
|
+
for (let attempt = 0;; attempt++) {
|
|
197
|
+
signal.throwIfAborted();
|
|
198
|
+
const attemptSignal = AbortSignal.any([signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]);
|
|
199
|
+
let response;
|
|
200
|
+
try {
|
|
201
|
+
response = await fetch(new URL(`${baseUrl}/models`), {
|
|
202
|
+
signal: attemptSignal,
|
|
203
|
+
redirect: "error",
|
|
204
|
+
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
if (signal.aborted || attempt >= MAX_RETRIES)
|
|
209
|
+
throw error;
|
|
210
|
+
await delay(100 * 2 ** attempt, undefined, { signal });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const payload = await response.json().catch(() => ({}));
|
|
214
|
+
if (!response.ok) {
|
|
215
|
+
const details = errorDetails(response, typeof payload === "object" && payload !== null ? payload : {});
|
|
216
|
+
const retryMs = retryDelay(response, attempt);
|
|
217
|
+
if (attempt < MAX_RETRIES &&
|
|
218
|
+
RETRYABLE_STATUS_CODES.has(response.status) &&
|
|
219
|
+
!PERMANENT_LIMIT_CODES.has(details.code ?? "") &&
|
|
220
|
+
retryMs !== undefined) {
|
|
221
|
+
await delay(retryMs, undefined, { signal });
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
throw new Error(details.message);
|
|
225
|
+
}
|
|
226
|
+
if (typeof payload !== "object" || payload === null) {
|
|
227
|
+
throw new Error("HUMAIN Node returned a malformed model catalog");
|
|
228
|
+
}
|
|
229
|
+
const entries = "data" in payload && Array.isArray(payload.data)
|
|
230
|
+
? payload.data
|
|
231
|
+
: "items" in payload && Array.isArray(payload.items)
|
|
232
|
+
? payload.items
|
|
233
|
+
: undefined;
|
|
234
|
+
if (!entries)
|
|
235
|
+
throw new Error("HUMAIN Node returned a malformed model catalog");
|
|
236
|
+
const ids = new Set();
|
|
237
|
+
for (const entry of entries) {
|
|
238
|
+
if (typeof entry !== "object" || entry === null || !("id" in entry) || typeof entry.id !== "string")
|
|
239
|
+
continue;
|
|
240
|
+
const id = entry.id.trim();
|
|
241
|
+
if (ids.has(id))
|
|
242
|
+
throw new Error(`HUMAIN Node returned duplicate model id "${id}"`);
|
|
243
|
+
ids.add(id);
|
|
244
|
+
}
|
|
245
|
+
return entries.flatMap((entry) => toModel(entry, baseUrl) ?? []);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function fetchModels(baseUrl, apiKey, signal) {
|
|
249
|
+
try {
|
|
250
|
+
return await fetchModelsUnredacted(baseUrl, apiKey, signal);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
if (signal.aborted)
|
|
254
|
+
throw signal.reason;
|
|
255
|
+
const message = (error instanceof Error ? error.message : String(error)).replaceAll(apiKey, "[redacted]");
|
|
256
|
+
const redacted = new Error(message);
|
|
257
|
+
if (error instanceof Error)
|
|
258
|
+
redacted.name = error.name;
|
|
259
|
+
throw redacted;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
export function createHumainNodeProvider(env = process.env) {
|
|
263
|
+
let models = [];
|
|
264
|
+
let activeScope;
|
|
265
|
+
const apiKeyAuth = {
|
|
266
|
+
name: "HUMAIN Node API key",
|
|
267
|
+
login: async (interaction) => {
|
|
268
|
+
if (!env[API_KEY_ENV]?.trim()) {
|
|
269
|
+
interaction.notify({
|
|
270
|
+
type: "info",
|
|
271
|
+
message: "Create a HUMAIN Node API key in Node Users, then paste it below.",
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
const enteredKey = (await interaction.prompt({
|
|
275
|
+
type: "secret",
|
|
276
|
+
message: "Enter HUMAIN Node API key (leave blank to use HUMAIN_NODE_API_KEY)",
|
|
277
|
+
})).trim();
|
|
278
|
+
const key = enteredKey || env[API_KEY_ENV]?.trim();
|
|
279
|
+
if (!key)
|
|
280
|
+
throw new Error("HUMAIN Node API key is required");
|
|
281
|
+
const enteredUrl = await interaction.prompt({
|
|
282
|
+
type: "text",
|
|
283
|
+
message: "HUMAIN Node base URL",
|
|
284
|
+
placeholder: env[BASE_URL_ENV] ?? DEFAULT_BASE_URL,
|
|
285
|
+
});
|
|
286
|
+
const baseUrl = normalizeBaseUrl(enteredUrl.trim() || env[BASE_URL_ENV] || DEFAULT_BASE_URL);
|
|
287
|
+
await fetchModels(baseUrl, key, interaction.signal);
|
|
288
|
+
return {
|
|
289
|
+
type: "api_key",
|
|
290
|
+
key: enteredKey.replaceAll("$", () => "$$").replace(/^!/u, () => "$!") || undefined,
|
|
291
|
+
env: { [BASE_URL_ENV]: baseUrl },
|
|
292
|
+
};
|
|
293
|
+
},
|
|
294
|
+
check: async ({ ctx, credential, signal }) => {
|
|
295
|
+
signal.throwIfAborted();
|
|
296
|
+
const key = credential?.key ?? (await ctx.env(API_KEY_ENV));
|
|
297
|
+
return key
|
|
298
|
+
? { type: "api_key", source: credential?.key ? "stored credential" : API_KEY_ENV }
|
|
299
|
+
: undefined;
|
|
300
|
+
},
|
|
301
|
+
resolve: async ({ ctx, credential, signal }) => {
|
|
302
|
+
signal.throwIfAborted();
|
|
303
|
+
const key = credential?.key ?? (await ctx.env(API_KEY_ENV));
|
|
304
|
+
if (!key)
|
|
305
|
+
return undefined;
|
|
306
|
+
const baseUrl = await resolveBaseUrl(ctx, credential);
|
|
307
|
+
return {
|
|
308
|
+
auth: { apiKey: key, baseUrl },
|
|
309
|
+
env: { ...credential?.env, [BASE_URL_ENV]: baseUrl },
|
|
310
|
+
source: credential?.key ? "stored credential" : API_KEY_ENV,
|
|
311
|
+
};
|
|
312
|
+
},
|
|
313
|
+
};
|
|
314
|
+
const base = createProvider({
|
|
315
|
+
id: HUMAIN_NODE_PROVIDER,
|
|
316
|
+
name: "HUMAIN Node",
|
|
317
|
+
baseUrl: DEFAULT_BASE_URL,
|
|
318
|
+
auth: { apiKey: apiKeyAuth },
|
|
319
|
+
models: [],
|
|
320
|
+
api: {
|
|
321
|
+
"anthropic-messages": withHumainNodeHttp(anthropicMessagesApi()),
|
|
322
|
+
"openai-completions": withHumainNodeHttp(openAICompletionsApi()),
|
|
323
|
+
"openai-responses": withHumainNodeHttp(openAIResponsesApi()),
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
return {
|
|
327
|
+
...base,
|
|
328
|
+
getModels: () => models,
|
|
329
|
+
refreshModels: async (context) => {
|
|
330
|
+
const credential = context.credential?.type === "api_key" ? context.credential : undefined;
|
|
331
|
+
const key = credential?.key;
|
|
332
|
+
const baseUrl = credential ? (credentialBaseUrl(credential) ?? DEFAULT_BASE_URL) : undefined;
|
|
333
|
+
const scope = key && baseUrl ? `${baseUrl}\0${key}` : undefined;
|
|
334
|
+
if (scope !== activeScope) {
|
|
335
|
+
if (!(await context.publish({
|
|
336
|
+
persist: null,
|
|
337
|
+
update: () => {
|
|
338
|
+
models = [];
|
|
339
|
+
activeScope = scope;
|
|
340
|
+
},
|
|
341
|
+
})))
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (!context.allowNetwork || context.signal.aborted || !key || !baseUrl)
|
|
345
|
+
return;
|
|
346
|
+
const refreshed = await fetchModels(baseUrl, key, context.signal);
|
|
347
|
+
if (context.signal.aborted)
|
|
348
|
+
return;
|
|
349
|
+
await context.publish({
|
|
350
|
+
persist: null,
|
|
351
|
+
update: () => {
|
|
352
|
+
models = refreshed;
|
|
353
|
+
activeScope = scope;
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
//# sourceMappingURL=node-provider.js.map
|