@ai21/gateway 0.5.3
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/README.md +91 -0
- package/dist/args.d.ts +74 -0
- package/dist/args.js +304 -0
- package/dist/args.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +140 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +76 -0
- package/dist/config.js +187 -0
- package/dist/config.js.map +1 -0
- package/dist/login/browser.d.ts +7 -0
- package/dist/login/browser.js +41 -0
- package/dist/login/browser.js.map +1 -0
- package/dist/login/contract.d.ts +42 -0
- package/dist/login/contract.js +60 -0
- package/dist/login/contract.js.map +1 -0
- package/dist/login/loopback.d.ts +42 -0
- package/dist/login/loopback.js +192 -0
- package/dist/login/loopback.js.map +1 -0
- package/dist/login/session.d.ts +36 -0
- package/dist/login/session.js +75 -0
- package/dist/login/session.js.map +1 -0
- package/dist/prompt.d.ts +32 -0
- package/dist/prompt.js +72 -0
- package/dist/prompt.js.map +1 -0
- package/dist/report.d.ts +50 -0
- package/dist/report.js +214 -0
- package/dist/report.js.map +1 -0
- package/dist/result.d.ts +26 -0
- package/dist/result.js +22 -0
- package/dist/result.js.map +1 -0
- package/dist/revert.d.ts +60 -0
- package/dist/revert.js +230 -0
- package/dist/revert.js.map +1 -0
- package/dist/router.d.ts +21 -0
- package/dist/router.js +271 -0
- package/dist/router.js.map +1 -0
- package/dist/snippets.d.ts +52 -0
- package/dist/snippets.js +71 -0
- package/dist/snippets.js.map +1 -0
- package/dist/verify.d.ts +96 -0
- package/dist/verify.js +309 -0
- package/dist/verify.js.map +1 -0
- package/package.json +61 -0
package/dist/router.js
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verb dispatch. Handlers return streams and an exit code rather than writing or
|
|
3
|
+
* exiting, so a command can be asserted end to end without spawning a process.
|
|
4
|
+
*/
|
|
5
|
+
import { containsControlCharacters, dashboardUrl } from "./args.js";
|
|
6
|
+
import { resolvePaths, writeConfig } from "./config.js";
|
|
7
|
+
import { promptForKey } from "./prompt.js";
|
|
8
|
+
import { EXIT_FOR, collectWarnings, report, reportRevert } from "./report.js";
|
|
9
|
+
import { revertConfig } from "./revert.js";
|
|
10
|
+
import { runLogin } from "./login/session.js";
|
|
11
|
+
import { EXIT } from "./result.js";
|
|
12
|
+
import { AI21_KEY_PLACEHOLDER, ccSettings, ccSettingsEnv } from "./snippets.js";
|
|
13
|
+
import { readVersion } from "./cli.js";
|
|
14
|
+
import { verify } from "./verify.js";
|
|
15
|
+
/* Identifies traffic this tool configured, so the gateway can tell a CLI-configured
|
|
16
|
+
* agent from a hand-pasted one without the CLI reporting anything.
|
|
17
|
+
*
|
|
18
|
+
* Read per call, not at module load: readVersion() throws on a corrupt install, and at
|
|
19
|
+
* import time that would crash every command — including `--version`, which handles
|
|
20
|
+
* the same failure cleanly on its own. */
|
|
21
|
+
function clientTag() {
|
|
22
|
+
return `ai21-gateway-cli/${readVersion()}`;
|
|
23
|
+
}
|
|
24
|
+
function snippetInputs(args) {
|
|
25
|
+
return {
|
|
26
|
+
gatewayUrl: args.urls.gateway,
|
|
27
|
+
// Only `login --print` reaches this fallback: `install` requires --agent-id,
|
|
28
|
+
// and login learns the agent from the callback. Rendering an empty value read as
|
|
29
|
+
// a bug in the preview rather than as a value the sign-in will supply.
|
|
30
|
+
agentId: args.agentId ?? AGENT_ID_PLACEHOLDER,
|
|
31
|
+
ai21Key: args.key ?? AI21_KEY_PLACEHOLDER,
|
|
32
|
+
userId: args.userId,
|
|
33
|
+
client: clientTag(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Stand-in shown instead of a supplied key. */
|
|
37
|
+
const REDACTED_KEY = "****";
|
|
38
|
+
/** Stand-in for the agent id a `login` preview cannot know yet. */
|
|
39
|
+
const AGENT_ID_PLACEHOLDER = "<agent-id-from-sign-in>";
|
|
40
|
+
/**
|
|
41
|
+
* A preview shows the shape and the endpoint, neither of which needs the key visible —
|
|
42
|
+
* and `--print` output lands in scrollback and CI logs, so a supplied key is always
|
|
43
|
+
* masked. The placeholder is left alone when no key was given: it is not a secret, and
|
|
44
|
+
* hiding it would obscure that the preview is incomplete.
|
|
45
|
+
*/
|
|
46
|
+
function previewInputs(args) {
|
|
47
|
+
const inputs = snippetInputs(args);
|
|
48
|
+
if (args.key === undefined)
|
|
49
|
+
return inputs;
|
|
50
|
+
return { ...inputs, ai21Key: REDACTED_KEY };
|
|
51
|
+
}
|
|
52
|
+
/** `--print`: render what *would* be written, without touching the filesystem. */
|
|
53
|
+
function printConfig(args) {
|
|
54
|
+
const inputs = previewInputs(args);
|
|
55
|
+
if (args.json) {
|
|
56
|
+
const payload = {
|
|
57
|
+
target: args.target,
|
|
58
|
+
urls: args.urls,
|
|
59
|
+
settings: { env: ccSettingsEnv(inputs) },
|
|
60
|
+
};
|
|
61
|
+
return { code: EXIT.OK, out: `${JSON.stringify(payload, null, 2)}\n`, err: "" };
|
|
62
|
+
}
|
|
63
|
+
return { code: EXIT.OK, out: `${ccSettings(inputs)}\n`, err: "" };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Blanks the AI21 key wherever it appears in a header value. Conflict output
|
|
67
|
+
* quotes both the existing and the new value, and both carry a live credential —
|
|
68
|
+
* which would otherwise land in a terminal scrollback or a CI log.
|
|
69
|
+
*/
|
|
70
|
+
function redactKey(value) {
|
|
71
|
+
return value.replace(/(x-ai21-key:\s*)\S+/gi, "$1****");
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Header values are `\n`-joined, so a raw one breaks out of the indented block
|
|
75
|
+
* and the two sides stop being comparable at a glance. Continuation lines are
|
|
76
|
+
* aligned under the first.
|
|
77
|
+
*/
|
|
78
|
+
function indentContinuations(value, pad) {
|
|
79
|
+
return value.split("\n").join(`\n${pad}`);
|
|
80
|
+
}
|
|
81
|
+
/** Renders the per-key breakdown under the refusal headline. */
|
|
82
|
+
function describeConflicts(conflicts) {
|
|
83
|
+
const pad = " ".repeat(" current: ".length);
|
|
84
|
+
const lines = conflicts.map((c) => ` ${c.key}\n` +
|
|
85
|
+
` current: ${indentContinuations(redactKey(c.current), pad)}\n` +
|
|
86
|
+
` new: ${indentContinuations(redactKey(c.desired), pad)}`);
|
|
87
|
+
return [
|
|
88
|
+
...lines,
|
|
89
|
+
"",
|
|
90
|
+
"Nothing was written. Re-run with --yes to overwrite; the previous file is backed up first.",
|
|
91
|
+
"",
|
|
92
|
+
].join("\n");
|
|
93
|
+
}
|
|
94
|
+
/** Human-only detail appended under the one-line outcome. */
|
|
95
|
+
function detailFor(outcome) {
|
|
96
|
+
switch (outcome.kind) {
|
|
97
|
+
case "conflict":
|
|
98
|
+
return describeConflicts(outcome.conflicts);
|
|
99
|
+
case "malformed":
|
|
100
|
+
return "Nothing was written. Fix or move the file, then re-run.\n";
|
|
101
|
+
case "failed":
|
|
102
|
+
return "The existing file was left unchanged.\n";
|
|
103
|
+
default:
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolves the key: the flag if given, otherwise a masked prompt. Returns a
|
|
109
|
+
* `RunResult` when it could not be obtained, so the caller just forwards it.
|
|
110
|
+
*/
|
|
111
|
+
async function resolveKey(args, io) {
|
|
112
|
+
if (args.key !== undefined)
|
|
113
|
+
return { key: args.key };
|
|
114
|
+
const outcome = await promptForKey(io);
|
|
115
|
+
switch (outcome.kind) {
|
|
116
|
+
case "key": {
|
|
117
|
+
// An empty line is a mistake, not a key. Refusing beats writing a config
|
|
118
|
+
// that looks fine and fails every request.
|
|
119
|
+
if (outcome.value.trim() === "") {
|
|
120
|
+
return { code: EXIT.USAGE, out: "", err: "No key entered. Nothing was written.\n" };
|
|
121
|
+
}
|
|
122
|
+
// The prompt already drops control characters, but the invariant belongs
|
|
123
|
+
// to whatever feeds a header value — asserting it here means a future
|
|
124
|
+
// input path cannot quietly become a way around it.
|
|
125
|
+
if (containsControlCharacters(outcome.value)) {
|
|
126
|
+
return {
|
|
127
|
+
code: EXIT.USAGE,
|
|
128
|
+
out: "",
|
|
129
|
+
err: "The key contains control characters. Nothing was written.\n",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return { key: outcome.value };
|
|
133
|
+
}
|
|
134
|
+
case "cancelled":
|
|
135
|
+
return { code: EXIT.USAGE, out: "", err: "Cancelled. Nothing was written.\n" };
|
|
136
|
+
case "no-tty":
|
|
137
|
+
// The alternative is blocking forever on a stdin that will never deliver
|
|
138
|
+
// a line, which is how a CLI wedges a CI job.
|
|
139
|
+
return {
|
|
140
|
+
code: EXIT.USAGE,
|
|
141
|
+
out: "",
|
|
142
|
+
err: "--key is required when stdin is not a terminal. Pass --key <key>.\n",
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Verification runs only once the config is actually on disk — there is nothing to
|
|
148
|
+
* verify after a refusal, and a request fired anyway would report on whatever
|
|
149
|
+
* config was already there.
|
|
150
|
+
*/
|
|
151
|
+
async function verifyWrite(args, ctx, outcome, inputs) {
|
|
152
|
+
if (EXIT_FOR[outcome.kind] !== EXIT.OK)
|
|
153
|
+
return undefined;
|
|
154
|
+
if (args.noVerify)
|
|
155
|
+
return { kind: "disabled" };
|
|
156
|
+
return verify({
|
|
157
|
+
urls: args.urls,
|
|
158
|
+
agentId: inputs.agentId,
|
|
159
|
+
ai21Key: inputs.ai21Key,
|
|
160
|
+
userId: args.userId,
|
|
161
|
+
anthropicKey: args.anthropicKey,
|
|
162
|
+
processEnv: ctx.processEnv,
|
|
163
|
+
// Verify from wherever the file we just wrote is the authoritative one — see
|
|
164
|
+
// VerifyContext.cwd. A --user config verified from the project directory
|
|
165
|
+
// would be shadowed by any repo-local settings.json.
|
|
166
|
+
cwd: args.target === "user" ? ctx.paths.home : ctx.paths.cwd,
|
|
167
|
+
}, ctx.verify);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Write, verify, report. Shared by `install` and `login` so the two cannot drift:
|
|
171
|
+
* whichever way the credentials were obtained, they go through one writer with one
|
|
172
|
+
* set of conflict, backup and idempotency rules.
|
|
173
|
+
*/
|
|
174
|
+
async function writeAndReport(args, ctx, inputs) {
|
|
175
|
+
const paths = resolvePaths(args.target, ctx.paths, args.client);
|
|
176
|
+
const outcome = writeConfig(paths, ccSettingsEnv(inputs), { force: args.yes });
|
|
177
|
+
const verification = await verifyWrite(args, ctx, outcome, inputs);
|
|
178
|
+
const result = report({
|
|
179
|
+
outcome,
|
|
180
|
+
warnings: collectWarnings({ urls: args.urls, processEnv: ctx.processEnv }),
|
|
181
|
+
json: args.json,
|
|
182
|
+
detail: detailFor(outcome),
|
|
183
|
+
urls: args.urls,
|
|
184
|
+
verification,
|
|
185
|
+
});
|
|
186
|
+
return { result, verification };
|
|
187
|
+
}
|
|
188
|
+
async function install(args, ctx) {
|
|
189
|
+
const resolved = await resolveKey(args, ctx.prompt);
|
|
190
|
+
if (!("key" in resolved))
|
|
191
|
+
return resolved;
|
|
192
|
+
return (await writeAndReport(args, ctx, { ...snippetInputs(args), ai21Key: resolved.key })).result;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* `login`: the browser hands us the credentials, then the ordinary writer takes
|
|
196
|
+
* over. A failed or abandoned handshake writes nothing at all.
|
|
197
|
+
*/
|
|
198
|
+
async function login(args, ctx) {
|
|
199
|
+
return handedOff(args, ctx, await runLogin(args.urls, ctx.login, { notify: ctx.notify }));
|
|
200
|
+
}
|
|
201
|
+
/** Shared by both handoff directions: write, verify, then offer the dashboard. */
|
|
202
|
+
async function handedOff(args, ctx, outcome) {
|
|
203
|
+
switch (outcome.kind) {
|
|
204
|
+
case "credentials": {
|
|
205
|
+
/* Overwrites without asking, unlike a hand-typed `install`.
|
|
206
|
+
*
|
|
207
|
+
* The conflict guard exists to catch someone clobbering a config they had
|
|
208
|
+
* forgotten about. That premise does not hold here: the reader asked the
|
|
209
|
+
* dashboard to connect this repo and ran the command it printed, so replacing
|
|
210
|
+
* whatever is there IS the request. The previous file is still backed up, and
|
|
211
|
+
* `uninstall --from-backup` still restores it.
|
|
212
|
+
*
|
|
213
|
+
* Refusing was also a dead end. Every handoff mints a fresh key, so
|
|
214
|
+
* ANTHROPIC_CUSTOM_HEADERS always differs from what is on disk and the guard
|
|
215
|
+
* always fired — and its advice, "re-run with --yes", cannot be followed: the
|
|
216
|
+
* page latches after one delivery, so the same code will never be sent again.
|
|
217
|
+
* The reader had to go back and spend another one-time key to get past it. */
|
|
218
|
+
const { result, verification } = await writeAndReport({ ...args, yes: true }, ctx, {
|
|
219
|
+
gatewayUrl: args.urls.gateway,
|
|
220
|
+
agentId: outcome.agentId,
|
|
221
|
+
ai21Key: outcome.ai21Key,
|
|
222
|
+
userId: args.userId,
|
|
223
|
+
});
|
|
224
|
+
// Only once the gateway has actually answered: sending someone to the dashboard
|
|
225
|
+
// to admire a setup that does not work would be worse than not sending them.
|
|
226
|
+
if (result.code === EXIT.OK && verification?.kind === "passed" && outcome.workspaceId !== undefined) {
|
|
227
|
+
const dashboard = dashboardUrl(args.urls, outcome.workspaceId);
|
|
228
|
+
ctx.login.openBrowser(dashboard);
|
|
229
|
+
ctx.notify(`Verified. Opening the dashboard: ${dashboard}`);
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
case "timeout":
|
|
234
|
+
return {
|
|
235
|
+
code: EXIT.LOGIN_INCOMPLETE,
|
|
236
|
+
out: "",
|
|
237
|
+
err: `Timed out waiting for the sign-in to complete. Nothing was written.\nRe-run \`ai21-gateway login\`, or install directly with --agent-id and --key.\n`,
|
|
238
|
+
};
|
|
239
|
+
case "rejected":
|
|
240
|
+
// Refusing beats writing: this is remote input that failed validation, and
|
|
241
|
+
// the reason is never the value itself.
|
|
242
|
+
return {
|
|
243
|
+
code: EXIT.LOGIN_INCOMPLETE,
|
|
244
|
+
out: "",
|
|
245
|
+
err: `Refusing the login response — ${outcome.reason}. Nothing was written.\n`,
|
|
246
|
+
};
|
|
247
|
+
case "failed":
|
|
248
|
+
return { code: EXIT.INTERNAL, out: "", err: `Login could not start: ${outcome.reason}.\n` };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** `uninstall`: hand the resolved paths to the reverter and report what it did. */
|
|
252
|
+
function uninstall(args, ctx) {
|
|
253
|
+
const outcome = revertConfig(resolvePaths(args.target, ctx.paths, args.client), {
|
|
254
|
+
fromBackup: args.fromBackup,
|
|
255
|
+
force: args.yes,
|
|
256
|
+
agentIdGuard: args.agentIdGuard,
|
|
257
|
+
});
|
|
258
|
+
return reportRevert(outcome, args.json);
|
|
259
|
+
}
|
|
260
|
+
export async function dispatch(args, ctx) {
|
|
261
|
+
if (args.verb === "uninstall")
|
|
262
|
+
return uninstall(args, ctx);
|
|
263
|
+
// --print is a preview of the resolved config, which needs no credentials and so
|
|
264
|
+
// has nothing to sign in for.
|
|
265
|
+
if (args.print)
|
|
266
|
+
return printConfig(args);
|
|
267
|
+
if (args.verb === "login")
|
|
268
|
+
return login(args, ctx);
|
|
269
|
+
return install(args, ctx);
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snippet builders duplicated from the gateway webapp, so what the CLI writes is
|
|
3
|
+
* byte-identical to what the UI tells people to copy.
|
|
4
|
+
*
|
|
5
|
+
* Copied from AI21Labs/ai21-intelligent-gateway-webapp:
|
|
6
|
+
* src/pages/docs/integrations.tsx — the builders
|
|
7
|
+
* src/pages/docs/snippetContext.tsx — the placeholder constants
|
|
8
|
+
*
|
|
9
|
+
* A webapp edit cannot fail our CI, so this drifts silently — re-copy when the
|
|
10
|
+
* gateway's snippets change. `test/fixtures/webapp-snippets.json` is what catches
|
|
11
|
+
* a divergence. Only the Claude Code path is mirrored; the LangChain snippets are
|
|
12
|
+
* now a four-way client matrix whose port is a design question, not a copy job.
|
|
13
|
+
*
|
|
14
|
+
* One deliberate divergence: the webapp always emits `x-ai21-user-id`, having a
|
|
15
|
+
* signed-in user. The CLI is unauthenticated, so that line is absent entirely
|
|
16
|
+
* unless `--user-id` supplies one — not blank, and no trailing newline.
|
|
17
|
+
*/
|
|
18
|
+
/** Stand-in for the caller's own Anthropic key, which the CLI never holds. */
|
|
19
|
+
export declare const ANTHROPIC_KEY_PLACEHOLDER = "<your-anthropic-api-key>";
|
|
20
|
+
/** Stand-in for the AI21 key in a preview, before one has been supplied. */
|
|
21
|
+
export declare const AI21_KEY_PLACEHOLDER = "<your-ai21-api-key>";
|
|
22
|
+
export interface SnippetInputs {
|
|
23
|
+
readonly gatewayUrl: string;
|
|
24
|
+
readonly agentId: string;
|
|
25
|
+
readonly ai21Key: string;
|
|
26
|
+
/** Signed-in user id for per-user attribution. Absent on a normal CLI run. */
|
|
27
|
+
readonly userId?: string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Marks the traffic as configured by this tool, e.g. `ai21-gateway-cli/0.4.0`.
|
|
30
|
+
*
|
|
31
|
+
* Set on what the CLI writes, and deliberately absent from the snippet builders the
|
|
32
|
+
* contract test pins against the webapp: a config someone pasted by hand did not
|
|
33
|
+
* come from here and must not claim to have. Lets the gateway tell the two apart
|
|
34
|
+
* without the CLI reporting anything itself.
|
|
35
|
+
*/
|
|
36
|
+
readonly client?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Claude Code's `ANTHROPIC_CUSTOM_HEADERS` value. Header order is contractual. */
|
|
39
|
+
export declare function ccCustomHeaders({ agentId, ai21Key, userId, client }: SnippetInputs): string;
|
|
40
|
+
/**
|
|
41
|
+
* The `env` block merged into `settings.json`. Three keys, matching the live
|
|
42
|
+
* builder — no `ANTHROPIC_API_KEY`, because Claude Code supplies its own
|
|
43
|
+
* Anthropic credential.
|
|
44
|
+
*/
|
|
45
|
+
export declare function ccSettingsEnv(inputs: SnippetInputs): Record<string, string>;
|
|
46
|
+
/** The full `settings.json` document, formatted as the UI renders it. */
|
|
47
|
+
export declare function ccSettings(inputs: SnippetInputs): string;
|
|
48
|
+
/**
|
|
49
|
+
* The canonical verifying request. Carries all three credentials the gateway
|
|
50
|
+
* requires — `x-ai21-key`, `x-agent-id`, and the caller's `x-api-key`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function genericCurl({ gatewayUrl, agentId, ai21Key, userId }: SnippetInputs): string;
|
package/dist/snippets.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snippet builders duplicated from the gateway webapp, so what the CLI writes is
|
|
3
|
+
* byte-identical to what the UI tells people to copy.
|
|
4
|
+
*
|
|
5
|
+
* Copied from AI21Labs/ai21-intelligent-gateway-webapp:
|
|
6
|
+
* src/pages/docs/integrations.tsx — the builders
|
|
7
|
+
* src/pages/docs/snippetContext.tsx — the placeholder constants
|
|
8
|
+
*
|
|
9
|
+
* A webapp edit cannot fail our CI, so this drifts silently — re-copy when the
|
|
10
|
+
* gateway's snippets change. `test/fixtures/webapp-snippets.json` is what catches
|
|
11
|
+
* a divergence. Only the Claude Code path is mirrored; the LangChain snippets are
|
|
12
|
+
* now a four-way client matrix whose port is a design question, not a copy job.
|
|
13
|
+
*
|
|
14
|
+
* One deliberate divergence: the webapp always emits `x-ai21-user-id`, having a
|
|
15
|
+
* signed-in user. The CLI is unauthenticated, so that line is absent entirely
|
|
16
|
+
* unless `--user-id` supplies one — not blank, and no trailing newline.
|
|
17
|
+
*/
|
|
18
|
+
/** Stand-in for the caller's own Anthropic key, which the CLI never holds. */
|
|
19
|
+
export const ANTHROPIC_KEY_PLACEHOLDER = "<your-anthropic-api-key>";
|
|
20
|
+
/** Stand-in for the AI21 key in a preview, before one has been supplied. */
|
|
21
|
+
export const AI21_KEY_PLACEHOLDER = "<your-ai21-api-key>";
|
|
22
|
+
/** Drops the lines that only exist when a user id was supplied. */
|
|
23
|
+
function joinLines(lines) {
|
|
24
|
+
return lines.filter((line) => line !== undefined).join("\n");
|
|
25
|
+
}
|
|
26
|
+
/** Claude Code's `ANTHROPIC_CUSTOM_HEADERS` value. Header order is contractual. */
|
|
27
|
+
export function ccCustomHeaders({ agentId, ai21Key, userId, client }) {
|
|
28
|
+
return joinLines([
|
|
29
|
+
`x-ai21-key: ${ai21Key}`,
|
|
30
|
+
`x-agent-id: ${agentId}`,
|
|
31
|
+
userId === undefined ? undefined : `x-ai21-user-id: ${userId}`,
|
|
32
|
+
client === undefined ? undefined : `x-ai21-client: ${client}`,
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The `env` block merged into `settings.json`. Three keys, matching the live
|
|
37
|
+
* builder — no `ANTHROPIC_API_KEY`, because Claude Code supplies its own
|
|
38
|
+
* Anthropic credential.
|
|
39
|
+
*/
|
|
40
|
+
export function ccSettingsEnv(inputs) {
|
|
41
|
+
return {
|
|
42
|
+
ANTHROPIC_BASE_URL: inputs.gatewayUrl,
|
|
43
|
+
ANTHROPIC_CUSTOM_HEADERS: ccCustomHeaders(inputs),
|
|
44
|
+
// "true", not "auto", to match what the dashboard tells people to set — see the
|
|
45
|
+
// webapp's Claude Code onboarding snippet (integrations.tsx). Same key written by
|
|
46
|
+
// both halves, so a repo configured here has to agree with the docs it followed.
|
|
47
|
+
ENABLE_TOOL_SEARCH: "true",
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** The full `settings.json` document, formatted as the UI renders it. */
|
|
51
|
+
export function ccSettings(inputs) {
|
|
52
|
+
return JSON.stringify({ env: ccSettingsEnv(inputs) }, null, 2);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The canonical verifying request. Carries all three credentials the gateway
|
|
56
|
+
* requires — `x-ai21-key`, `x-agent-id`, and the caller's `x-api-key`.
|
|
57
|
+
*/
|
|
58
|
+
export function genericCurl({ gatewayUrl, agentId, ai21Key, userId }) {
|
|
59
|
+
return joinLines([
|
|
60
|
+
`curl ${gatewayUrl}/v1/messages \\`,
|
|
61
|
+
` -H "x-ai21-key: ${ai21Key}" \\`,
|
|
62
|
+
` -H "x-api-key: ${ANTHROPIC_KEY_PLACEHOLDER}" \\`,
|
|
63
|
+
` -H "anthropic-version: 2023-06-01" \\`,
|
|
64
|
+
` -H "x-agent-id: ${agentId}" \\`,
|
|
65
|
+
userId === undefined ? undefined : ` -H "x-ai21-user-id: ${userId}" \\`,
|
|
66
|
+
` -H "content-type: application/json" \\`,
|
|
67
|
+
` -d '{"model": "claude-sonnet-4-5", "max_tokens": 64,`,
|
|
68
|
+
` "messages": [{"role": "user", "content": "Hello through the gateway"}]}'`,
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=snippets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snippets.js","sourceRoot":"","sources":["../src/snippets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,yBAAyB,GAAG,0BAA0B,CAAC;AAEpE,4EAA4E;AAC5E,MAAM,CAAC,MAAM,oBAAoB,GAAG,qBAAqB,CAAC;AAmB1D,mEAAmE;AACnE,SAAS,SAAS,CAAC,KAAsC;IACvD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC/E,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,eAAe,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAiB;IACjF,OAAO,SAAS,CAAC;QACf,eAAe,OAAO,EAAE;QACxB,eAAe,OAAO,EAAE;QACxB,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,mBAAmB,MAAM,EAAE;QAC9D,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,kBAAkB,MAAM,EAAE;KAC9D,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAqB;IACjD,OAAO;QACL,kBAAkB,EAAE,MAAM,CAAC,UAAU;QACrC,wBAAwB,EAAE,eAAe,CAAC,MAAM,CAAC;QACjD,gFAAgF;QAChF,kFAAkF;QAClF,iFAAiF;QACjF,kBAAkB,EAAE,MAAM;KAC3B,CAAC;AACJ,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,UAAU,CAAC,MAAqB;IAC9C,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAiB;IACjF,OAAO,SAAS,CAAC;QACf,QAAQ,UAAU,iBAAiB;QACnC,qBAAqB,OAAO,MAAM;QAClC,oBAAoB,yBAAyB,MAAM;QACnD,yCAAyC;QACzC,qBAAqB,OAAO,MAAM;QAClC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,yBAAyB,MAAM,MAAM;QACxE,0CAA0C;QAC1C,wDAAwD;QACxD,iFAAiF;KAClF,CAAC,CAAC;AACL,CAAC"}
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type ResolvedUrls } from "./args.js";
|
|
2
|
+
export declare const CLAUDE_COMMAND = "claude";
|
|
3
|
+
export declare const CLAUDE_PROMPT = "ai21-gateway CLI connectivity check \u2014 reply with OK and nothing else.";
|
|
4
|
+
export declare const PING_PATH = "/v1/messages";
|
|
5
|
+
/**
|
|
6
|
+
* Both tiers are bounded; the spawn gets longer because it waits on a full model
|
|
7
|
+
* round trip. 20s is measured, not guessed: a working `claude -p` returns in ~5s,
|
|
8
|
+
* and a rejected one hangs rather than failing, so this budget is what turns that
|
|
9
|
+
* hang into a message.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PING_TIMEOUT_MS = 10000;
|
|
12
|
+
export declare const CLAUDE_TIMEOUT_MS = 20000;
|
|
13
|
+
/** Mirrors the `genericCurl` snippet; a contract test holds the two together. */
|
|
14
|
+
export declare const PING_BODY: {
|
|
15
|
+
readonly model: "claude-sonnet-4-5";
|
|
16
|
+
readonly max_tokens: 64;
|
|
17
|
+
readonly messages: readonly [{
|
|
18
|
+
readonly role: "user";
|
|
19
|
+
readonly content: "Hello through the gateway";
|
|
20
|
+
}];
|
|
21
|
+
};
|
|
22
|
+
export type VerifyVia = "claude-cli" | "direct-ping";
|
|
23
|
+
/** How verification ended. Never maps to a non-zero exit code. */
|
|
24
|
+
export type VerifyOutcome = {
|
|
25
|
+
readonly kind: "disabled";
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "skipped";
|
|
28
|
+
readonly hint: string;
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: "passed";
|
|
31
|
+
readonly via: VerifyVia;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "failed";
|
|
34
|
+
readonly via: VerifyVia;
|
|
35
|
+
readonly reason: string;
|
|
36
|
+
};
|
|
37
|
+
export interface VerifyContext {
|
|
38
|
+
readonly urls: ResolvedUrls;
|
|
39
|
+
readonly agentId: string;
|
|
40
|
+
readonly ai21Key: string;
|
|
41
|
+
readonly userId?: string | undefined;
|
|
42
|
+
/** `--anthropic-key`, which outranks the environment. */
|
|
43
|
+
readonly anthropicKey?: string | undefined;
|
|
44
|
+
readonly processEnv: Record<string, string | undefined>;
|
|
45
|
+
/**
|
|
46
|
+
* Where to run `claude`: the directory in which the file we wrote is the one that
|
|
47
|
+
* wins. Home for `--user`, since a project-local settings.json outranks it.
|
|
48
|
+
*/
|
|
49
|
+
readonly cwd: string;
|
|
50
|
+
}
|
|
51
|
+
export interface CommandResult {
|
|
52
|
+
/** `null` when the process was killed or could not be started at all. */
|
|
53
|
+
readonly code: number | null;
|
|
54
|
+
readonly timedOut: boolean;
|
|
55
|
+
/** Combined stdout+stderr, used only to explain a failure. */
|
|
56
|
+
readonly output: string;
|
|
57
|
+
}
|
|
58
|
+
/** Just enough of `fetch` to ping with. `text` is optional; absent means inconclusive. */
|
|
59
|
+
export type FetchLike = (url: string, init: {
|
|
60
|
+
method: string;
|
|
61
|
+
headers: Record<string, string>;
|
|
62
|
+
body: string;
|
|
63
|
+
signal: AbortSignal;
|
|
64
|
+
}) => Promise<{
|
|
65
|
+
ok: boolean;
|
|
66
|
+
status: number;
|
|
67
|
+
text?: () => Promise<string>;
|
|
68
|
+
}>;
|
|
69
|
+
export interface PathLookup {
|
|
70
|
+
readonly platform: string;
|
|
71
|
+
readonly processEnv: Record<string, string | undefined>;
|
|
72
|
+
/** Injected so the tier-1 tests never depend on what is installed. */
|
|
73
|
+
readonly isExecutableFile: (candidate: string) => boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface VerifyDeps {
|
|
76
|
+
readonly lookup: PathLookup;
|
|
77
|
+
readonly runCommand: (executable: string, args: readonly string[], opts: {
|
|
78
|
+
readonly cwd: string;
|
|
79
|
+
readonly timeoutMs: number;
|
|
80
|
+
}) => Promise<CommandResult>;
|
|
81
|
+
readonly fetchImpl: FetchLike;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Hand-rolled rather than shelling out to `which`/`where`, which is slower and
|
|
85
|
+
* absent from stripped Windows images. `node:path`'s `join` and `delimiter` follow
|
|
86
|
+
* the *host* platform, so they cannot resolve a Windows PATH under test on macOS.
|
|
87
|
+
*/
|
|
88
|
+
export declare function findOnPath(command: string, lookup: PathLookup): string | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* Which side rejected us, without needing an Anthropic key: a deliberately invalid
|
|
91
|
+
* `x-api-key` cannot succeed, so whichever layer answers is the answer.
|
|
92
|
+
*/
|
|
93
|
+
export type CredentialVerdict = "ai21-rejected" | "ai21-accepted" | "inconclusive";
|
|
94
|
+
export declare function verify(ctx: VerifyContext, deps: VerifyDeps): Promise<VerifyOutcome>;
|
|
95
|
+
/** The real-world adapters, kept here so `cli.ts` stays a thin edge. */
|
|
96
|
+
export declare function nodeVerifyDeps(processEnv: Record<string, string | undefined>): VerifyDeps;
|