@langchain/langgraph-cli 1.5.2 → 1.5.4
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/dist/cli/deploy.mjs
CHANGED
|
@@ -18,11 +18,12 @@ import { withAnalytics } from "./utils/analytics.mjs";
|
|
|
18
18
|
import { getProjectPath } from "./utils/project.mjs";
|
|
19
19
|
import { getConfig } from "../utils/config.mjs";
|
|
20
20
|
import { assembleLocalDeps, configToDocker, getBaseImage, } from "../docker/docker.mjs";
|
|
21
|
+
import { activeDockerHost } from "../docker/compose.mjs";
|
|
21
22
|
import { DOCKER_NOT_INSTALLED, DOCKER_NOT_RUNNING, isBinaryNotFound, } from "../docker/errors.mjs";
|
|
22
23
|
import { HostBackendClient, HostBackendError, } from "./utils/host-backend.mjs";
|
|
23
24
|
import { Emitter, Spinner } from "./utils/deploy-output.mjs";
|
|
24
25
|
import { createArchive, BYTES_PER_MIB } from "./utils/archive.mjs";
|
|
25
|
-
import { API_KEY_ENV_NAMES, DEFAULT_HOST_URL, DEPLOYMENT_NAME_ENV, TERMINAL_STATUSES, envWithoutDeploymentName, findDeploymentIdByName, formatDeploymentsTable, formatLogEntry, formatRevisionsTable, getDeploymentStatusUrl, hasDisallowedBuildCommandContent, isPathWithin, levelColor, normalizeImageTag, normalizeName, secretsFromEnv, smithDashboardBaseUrl, } from "./utils/deploy-helpers.mjs";
|
|
26
|
+
import { API_KEY_ENV_NAMES, DEFAULT_HOST_URL, DEPLOYMENT_NAME_ENV, TERMINAL_STATUSES, envWithoutDeploymentName, findDeploymentIdByName, formatDeploymentsTable, formatLogEntry, formatRevisionsTable, getDeploymentStatusUrl, hasDisallowedBuildCommandContent, isPathWithin, levelColor, normalizeImageTag, normalizeName, resolvePushTarget, secretsFromEnv, smithDashboardBaseUrl, } from "./utils/deploy-helpers.mjs";
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
27
28
|
// Module-level state (mirrors the Python globals `_emitter` / `_no_input`).
|
|
28
29
|
// ---------------------------------------------------------------------------
|
|
@@ -477,8 +478,8 @@ async function printDeploymentResult(client, deploymentId, lastStatus, options)
|
|
|
477
478
|
// ---------------------------------------------------------------------------
|
|
478
479
|
// Docker push auth + digest resolution
|
|
479
480
|
// ---------------------------------------------------------------------------
|
|
480
|
-
async function dockerConfigForToken(registryHost, token) {
|
|
481
|
-
const authB64 = Buffer.from(
|
|
481
|
+
async function dockerConfigForToken(registryHost, token, username) {
|
|
482
|
+
const authB64 = Buffer.from(`${username}:${token}`).toString("base64");
|
|
482
483
|
const data = { auths: { [registryHost]: { auth: authB64 } } };
|
|
483
484
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "langgraph-docker-"));
|
|
484
485
|
await fs.writeFile(path.join(dir, "config.json"), JSON.stringify(data));
|
|
@@ -660,16 +661,17 @@ async function runLocalBuild(args) {
|
|
|
660
661
|
throw new Error("Push token response missing token or registry_url");
|
|
661
662
|
}
|
|
662
663
|
step += 1;
|
|
663
|
-
let normalizedRegistry = registryUrl.replace(/\/+$/, "");
|
|
664
|
-
if (normalizedRegistry.includes("://")) {
|
|
665
|
-
normalizedRegistry = normalizedRegistry.slice(normalizedRegistry.indexOf("//") + 2);
|
|
666
|
-
}
|
|
667
664
|
const repoSeed = args.imageName || args.name || path.basename(projectDir);
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
const
|
|
672
|
-
const { dir: cfgDir, cleanup } = await dockerConfigForToken(registryHost, deploymentToken);
|
|
665
|
+
const { image: remoteImage, registryHost, username, } = resolvePushTarget(pushData, normalizeName(repoSeed), normalizeImageTag(args.tag));
|
|
666
|
+
// A config directory has no current context, so Docker would fall back to
|
|
667
|
+
// /var/run/docker.sock; keep talking to the daemon the build used.
|
|
668
|
+
const dockerHost = await activeDockerHost();
|
|
669
|
+
const { dir: cfgDir, cleanup } = await dockerConfigForToken(registryHost, deploymentToken, username);
|
|
670
|
+
const dockerArgs = [
|
|
671
|
+
"--config",
|
|
672
|
+
cfgDir,
|
|
673
|
+
...(dockerHost ? ["--host", dockerHost] : []),
|
|
674
|
+
];
|
|
673
675
|
try {
|
|
674
676
|
// -- Login --
|
|
675
677
|
emitter.step(step, `Logging into ${registryHost}`);
|
|
@@ -680,7 +682,7 @@ async function runLocalBuild(args) {
|
|
|
680
682
|
...baseOpts,
|
|
681
683
|
...stdio,
|
|
682
684
|
input: tokenInput,
|
|
683
|
-
}) `docker
|
|
685
|
+
}) `docker ${dockerArgs} login -u ${username} --password-stdin ${registryHost}`;
|
|
684
686
|
step += 1;
|
|
685
687
|
// -- Tag + push --
|
|
686
688
|
emitter.step(step, `Pushing image ${remoteImage}`);
|
|
@@ -697,7 +699,7 @@ async function runLocalBuild(args) {
|
|
|
697
699
|
await $({
|
|
698
700
|
...baseOpts,
|
|
699
701
|
...stdio,
|
|
700
|
-
}) `docker
|
|
702
|
+
}) `docker ${dockerArgs} push ${remoteImage}`;
|
|
701
703
|
pushSpinner.stop();
|
|
702
704
|
break;
|
|
703
705
|
}
|
|
@@ -153,6 +153,40 @@ export function normalizeImageTag(value) {
|
|
|
153
153
|
}
|
|
154
154
|
return tag;
|
|
155
155
|
}
|
|
156
|
+
// Artifact Registry's username for access tokens. Control planes whose registry
|
|
157
|
+
// needs another one return it as `username`.
|
|
158
|
+
const DEFAULT_REGISTRY_USERNAME = "oauth2accesstoken";
|
|
159
|
+
function stripUrlScheme(url) {
|
|
160
|
+
const reference = url.replace(/\/+$/, "");
|
|
161
|
+
return reference.includes("://")
|
|
162
|
+
? reference.slice(reference.indexOf("//") + 2)
|
|
163
|
+
: reference;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Resolve the push target from a push-token response.
|
|
167
|
+
*
|
|
168
|
+
* When the control plane returns `repository_url`, the image goes to exactly
|
|
169
|
+
* that repository: an ECR repository is a single image name, so nothing may be
|
|
170
|
+
* appended to it. Otherwise the image goes under `registry_url`.
|
|
171
|
+
*
|
|
172
|
+
* @param pushData - The push-token response; `registry_url` must be a string.
|
|
173
|
+
* @param repoName - Normalized image name used under `registry_url`.
|
|
174
|
+
* @param tag - Validated image tag.
|
|
175
|
+
* @returns The image reference, login host, and username.
|
|
176
|
+
*/
|
|
177
|
+
export function resolvePushTarget(pushData, repoName, tag) {
|
|
178
|
+
const repository = typeof pushData.repository_url === "string" && pushData.repository_url
|
|
179
|
+
? stripUrlScheme(pushData.repository_url)
|
|
180
|
+
: `${stripUrlScheme(String(pushData.registry_url))}/${repoName}`;
|
|
181
|
+
const username = typeof pushData.username === "string" && pushData.username
|
|
182
|
+
? pushData.username
|
|
183
|
+
: DEFAULT_REGISTRY_USERNAME;
|
|
184
|
+
return {
|
|
185
|
+
image: `${repository}:${tag}`,
|
|
186
|
+
registryHost: repository.split("/")[0],
|
|
187
|
+
username,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
156
190
|
/**
|
|
157
191
|
* Convert an env dict to a deployment secrets list, dropping reserved and
|
|
158
192
|
* empty-valued variables.
|
|
@@ -209,11 +209,17 @@ export class HostBackendClient {
|
|
|
209
209
|
* Request a short-lived registry push token for a local-build deployment
|
|
210
210
|
* (`POST /v2/deployments/{id}/push-token`).
|
|
211
211
|
*
|
|
212
|
+
* Tells the control plane this client logs in with the returned `username`
|
|
213
|
+
* and pushes to `repository_url`. LangSmith AWS requires it.
|
|
214
|
+
*
|
|
212
215
|
* @param deploymentId - Deployment ID.
|
|
213
|
-
* @returns A response containing the push `token` and `registry_url
|
|
216
|
+
* @returns A response containing the push `token` and `registry_url`, plus
|
|
217
|
+
* `username` and `repository_url` when the registry needs them.
|
|
214
218
|
*/
|
|
215
219
|
requestPushToken(deploymentId) {
|
|
216
|
-
return this.request("POST", `/v2/deployments/${deploymentId}/push-token
|
|
220
|
+
return this.request("POST", `/v2/deployments/${deploymentId}/push-token`, {
|
|
221
|
+
payload: { supports_registry_username: true },
|
|
222
|
+
});
|
|
217
223
|
}
|
|
218
224
|
/**
|
|
219
225
|
* Request a signed GCS upload URL for the source tarball used by remote
|
package/dist/docker/compose.mjs
CHANGED
|
@@ -185,3 +185,23 @@ export function createCompose(capabilities, options) {
|
|
|
185
185
|
}
|
|
186
186
|
return yaml.stringify(compose, { blockQuote: "literal" });
|
|
187
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* The daemon endpoint of the user's active Docker context.
|
|
190
|
+
*
|
|
191
|
+
* Undefined when `DOCKER_HOST` is set, which Docker honors with any config
|
|
192
|
+
* directory. Also undefined when the lookup fails (for example, `DOCKER_CONTEXT`
|
|
193
|
+
* names a context that doesn't exist) or the context has no Docker endpoint
|
|
194
|
+
* (Docker prints `<no value>`); that keeps Docker's own default in place.
|
|
195
|
+
*/
|
|
196
|
+
export async function activeDockerHost() {
|
|
197
|
+
if (process.env.DOCKER_HOST)
|
|
198
|
+
return undefined;
|
|
199
|
+
try {
|
|
200
|
+
const { stdout } = await $ `docker context inspect --format ${"{{.Endpoints.docker.Host}}"}`;
|
|
201
|
+
const host = String(stdout).trim();
|
|
202
|
+
return host.includes("://") ? host : undefined;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/langgraph-cli",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^18.19.0 || >=20.16.0"
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"winston-console-format": "^1.0.8",
|
|
40
40
|
"yaml": "^2.8.3",
|
|
41
41
|
"zod": "^3.25.76 || ^4",
|
|
42
|
-
"@langchain/langgraph-api": "1.5.
|
|
42
|
+
"@langchain/langgraph-api": "1.5.4",
|
|
43
43
|
"create-langgraph": "1.1.5"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|