@langchain/langgraph-cli 1.3.1 → 1.4.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/dist/cli/cli.mjs +1 -0
- package/dist/cli/deploy.mjs +1199 -0
- package/dist/cli/utils/archive.mjs +130 -0
- package/dist/cli/utils/deploy-helpers.mjs +396 -0
- package/dist/cli/utils/deploy-ignore.mjs +163 -0
- package/dist/cli/utils/deploy-output.mjs +387 -0
- package/dist/cli/utils/host-backend.mjs +334 -0
- package/dist/docker/docker.mjs +19 -8
- package/package.json +4 -3
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the LangGraph host backend (LangSmith Deployment).
|
|
3
|
+
*
|
|
4
|
+
* Ported from the Python CLI's `host_backend.py`. Uses the global `fetch`
|
|
5
|
+
* with a small retry loop on transport-level failures.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Error raised when the host backend returns a non-success response or a
|
|
9
|
+
* request fails at the transport level.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* {@link statusCode} is populated for HTTP error responses and left
|
|
13
|
+
* `undefined` for transport-level failures (network, DNS, timeout).
|
|
14
|
+
*/
|
|
15
|
+
export class HostBackendError extends Error {
|
|
16
|
+
/** HTTP status code, when the failure came from an HTTP error response. */
|
|
17
|
+
statusCode;
|
|
18
|
+
/**
|
|
19
|
+
* @param message - Human-readable error description.
|
|
20
|
+
* @param statusCode - HTTP status code, if the error came from a response.
|
|
21
|
+
*/
|
|
22
|
+
constructor(message, statusCode) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "HostBackendError";
|
|
25
|
+
this.statusCode = statusCode;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Number of times a transport-level failure is retried before giving up. */
|
|
29
|
+
const TRANSPORT_RETRIES = 3;
|
|
30
|
+
/** Per-request timeout in milliseconds, enforced via `AbortController`. */
|
|
31
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
32
|
+
/**
|
|
33
|
+
* Minimal JSON HTTP client for the host backend deployment service.
|
|
34
|
+
*
|
|
35
|
+
* @remarks
|
|
36
|
+
* Wraps the global `fetch`, attaching the API-key (and optional tenant)
|
|
37
|
+
* headers to every request, retrying transport-level failures, and decoding
|
|
38
|
+
* JSON responses. All methods reject with a {@link HostBackendError} on
|
|
39
|
+
* failure.
|
|
40
|
+
*/
|
|
41
|
+
export class HostBackendClient {
|
|
42
|
+
/** The normalized base URL (trailing slashes stripped). */
|
|
43
|
+
baseUrl;
|
|
44
|
+
/** Headers sent with every request (API key, accept, optional tenant). */
|
|
45
|
+
headers;
|
|
46
|
+
/**
|
|
47
|
+
* @param baseUrl - Host backend base URL, e.g. `https://api.host.langchain.com`.
|
|
48
|
+
* @param apiKey - LangSmith API key sent as the `X-Api-Key` header.
|
|
49
|
+
* @param tenantId - Optional workspace/tenant ID sent as `X-Tenant-ID`.
|
|
50
|
+
* @throws {@link HostBackendError} if `baseUrl` is empty.
|
|
51
|
+
*/
|
|
52
|
+
constructor(baseUrl, apiKey, tenantId) {
|
|
53
|
+
if (!baseUrl) {
|
|
54
|
+
throw new HostBackendError("Host backend URL is required");
|
|
55
|
+
}
|
|
56
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
57
|
+
this.headers = {
|
|
58
|
+
"X-Api-Key": apiKey,
|
|
59
|
+
Accept: "application/json",
|
|
60
|
+
};
|
|
61
|
+
if (tenantId) {
|
|
62
|
+
this.headers["X-Tenant-ID"] = tenantId;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Set or override the tenant (workspace) header on this client. Used after
|
|
67
|
+
* an org-scoped API key prompts for a workspace ID, so subsequent requests
|
|
68
|
+
* are tenant-aware.
|
|
69
|
+
*
|
|
70
|
+
* @param tenantId - Workspace/tenant ID to send as `X-Tenant-ID`.
|
|
71
|
+
*/
|
|
72
|
+
setTenantId(tenantId) {
|
|
73
|
+
this.headers["X-Tenant-ID"] = tenantId;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build a fully-qualified request URL, appending query parameters.
|
|
77
|
+
*
|
|
78
|
+
* @param path - Path relative to {@link baseUrl} (must start with `/`).
|
|
79
|
+
* @param params - Optional query parameters; nullish values are omitted.
|
|
80
|
+
* @returns The absolute URL string.
|
|
81
|
+
*/
|
|
82
|
+
buildUrl(path, params) {
|
|
83
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
84
|
+
if (params) {
|
|
85
|
+
for (const [key, value] of Object.entries(params)) {
|
|
86
|
+
if (value !== undefined && value !== null) {
|
|
87
|
+
url.searchParams.set(key, String(value));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return url.toString();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Perform an HTTP request and decode the JSON response.
|
|
95
|
+
*
|
|
96
|
+
* @typeParam T - Expected shape of the decoded response body.
|
|
97
|
+
* @param method - HTTP method (e.g. `GET`, `POST`, `PATCH`, `DELETE`).
|
|
98
|
+
* @param path - Path relative to {@link baseUrl} (must start with `/`).
|
|
99
|
+
* @param options - Optional JSON body and query parameters.
|
|
100
|
+
* @returns The decoded JSON body, or `undefined` for empty responses.
|
|
101
|
+
* @throws {@link HostBackendError} on HTTP error responses, transport
|
|
102
|
+
* failures (after retries), or undecodable JSON bodies.
|
|
103
|
+
*/
|
|
104
|
+
async request(method, path, options = {}) {
|
|
105
|
+
const url = this.buildUrl(path, options.params);
|
|
106
|
+
const init = {
|
|
107
|
+
method,
|
|
108
|
+
headers: { ...this.headers },
|
|
109
|
+
};
|
|
110
|
+
if (options.payload !== undefined) {
|
|
111
|
+
init.body = JSON.stringify(options.payload);
|
|
112
|
+
init.headers["Content-Type"] =
|
|
113
|
+
"application/json";
|
|
114
|
+
}
|
|
115
|
+
let lastError;
|
|
116
|
+
for (let attempt = 0; attempt <= TRANSPORT_RETRIES; attempt += 1) {
|
|
117
|
+
const controller = new AbortController();
|
|
118
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
119
|
+
let response;
|
|
120
|
+
try {
|
|
121
|
+
response = await fetch(url, { ...init, signal: controller.signal });
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
// Transport-level error (network, DNS, timeout). Retry a few times.
|
|
125
|
+
lastError = error;
|
|
126
|
+
if (attempt < TRANSPORT_RETRIES)
|
|
127
|
+
continue;
|
|
128
|
+
throw new HostBackendError(error instanceof Error ? error.message : String(error));
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
}
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
const detail = (await response.text().catch(() => "")) || String(response.status);
|
|
135
|
+
throw new HostBackendError(`${method} ${path} failed with status ${response.status}: ${detail}`, response.status);
|
|
136
|
+
}
|
|
137
|
+
const text = await response.text();
|
|
138
|
+
if (!text)
|
|
139
|
+
return undefined;
|
|
140
|
+
try {
|
|
141
|
+
return JSON.parse(text);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
throw new HostBackendError(`Failed to decode response from ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
throw new HostBackendError(lastError instanceof Error ? lastError.message : String(lastError));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Create a new deployment (`POST /v2/deployments`).
|
|
151
|
+
*
|
|
152
|
+
* @param args - Deployment creation parameters.
|
|
153
|
+
* @param args.name - Deployment name.
|
|
154
|
+
* @param args.deploymentType - Deployment type, e.g. `dev` or `prod`.
|
|
155
|
+
* @param args.source - Source kind, e.g. `internal_docker` or `internal_source`.
|
|
156
|
+
* @param args.configPath - Config path inside the source archive. Only sent
|
|
157
|
+
* for `internal_source` deployments.
|
|
158
|
+
* @param args.secrets - Optional secrets to attach to the deployment.
|
|
159
|
+
* @returns The created deployment object (expected to contain an `id`).
|
|
160
|
+
*/
|
|
161
|
+
createDeployment(args) {
|
|
162
|
+
const payload = {
|
|
163
|
+
name: args.name,
|
|
164
|
+
source: args.source,
|
|
165
|
+
source_config: { deployment_type: args.deploymentType },
|
|
166
|
+
source_revision_config: {},
|
|
167
|
+
};
|
|
168
|
+
if (args.source === "internal_source" && args.configPath) {
|
|
169
|
+
payload.source_revision_config["langgraph_config_path"] = args.configPath;
|
|
170
|
+
}
|
|
171
|
+
if (args.secrets != null) {
|
|
172
|
+
payload.secrets = args.secrets;
|
|
173
|
+
}
|
|
174
|
+
return this.request("POST", "/v2/deployments", { payload });
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* List deployments (`GET /v2/deployments`).
|
|
178
|
+
*
|
|
179
|
+
* @param nameContains - Optional substring filter on deployment names.
|
|
180
|
+
* @returns A paginated response object with a `resources` array.
|
|
181
|
+
*/
|
|
182
|
+
listDeployments(nameContains = "") {
|
|
183
|
+
return this.request("GET", "/v2/deployments", {
|
|
184
|
+
params: { name_contains: nameContains },
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Fetch a single deployment (`GET /v2/deployments/{id}`).
|
|
189
|
+
*
|
|
190
|
+
* @param deploymentId - Deployment ID.
|
|
191
|
+
* @returns The deployment object.
|
|
192
|
+
*/
|
|
193
|
+
getDeployment(deploymentId) {
|
|
194
|
+
return this.request("GET", `/v2/deployments/${deploymentId}`);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Delete a deployment (`DELETE /v2/deployments/{id}`).
|
|
198
|
+
*
|
|
199
|
+
* @param deploymentId - Deployment ID.
|
|
200
|
+
*/
|
|
201
|
+
deleteDeployment(deploymentId) {
|
|
202
|
+
return this.request("DELETE", `/v2/deployments/${deploymentId}`);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Request a short-lived registry push token for a local-build deployment
|
|
206
|
+
* (`POST /v2/deployments/{id}/push-token`).
|
|
207
|
+
*
|
|
208
|
+
* @param deploymentId - Deployment ID.
|
|
209
|
+
* @returns A response containing the push `token` and `registry_url`.
|
|
210
|
+
*/
|
|
211
|
+
requestPushToken(deploymentId) {
|
|
212
|
+
return this.request("POST", `/v2/deployments/${deploymentId}/push-token`);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Request a signed GCS upload URL for the source tarball used by remote
|
|
216
|
+
* builds (`POST /v2/deployments/{id}/upload-url`).
|
|
217
|
+
*
|
|
218
|
+
* @param deploymentId - Deployment ID.
|
|
219
|
+
* @returns A response containing the `upload_url` and `object_path`.
|
|
220
|
+
*/
|
|
221
|
+
requestUploadUrl(deploymentId) {
|
|
222
|
+
return this.request("POST", `/v2/deployments/${deploymentId}/upload-url`);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Update a deployment to a new pre-built image, creating a new revision
|
|
226
|
+
* (`PATCH /v2/deployments/{id}` with `revision_source: internal_docker`).
|
|
227
|
+
*
|
|
228
|
+
* @param deploymentId - Deployment ID.
|
|
229
|
+
* @param imageUri - Fully-qualified image reference (ideally a digest).
|
|
230
|
+
* @param args - Optional secrets.
|
|
231
|
+
* @param args.secrets - Secrets to set on the deployment.
|
|
232
|
+
* @returns The updated deployment object.
|
|
233
|
+
*/
|
|
234
|
+
updateDeployment(deploymentId, imageUri, args = {}) {
|
|
235
|
+
const payload = {
|
|
236
|
+
revision_source: "internal_docker",
|
|
237
|
+
source_revision_config: { image_uri: imageUri },
|
|
238
|
+
};
|
|
239
|
+
if (args.secrets != null) {
|
|
240
|
+
payload.secrets = args.secrets;
|
|
241
|
+
}
|
|
242
|
+
return this.request("PATCH", `/v2/deployments/${deploymentId}`, {
|
|
243
|
+
payload,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Trigger a remote build revision from an uploaded source tarball
|
|
248
|
+
* (`PATCH /v2/deployments/{id}` with `revision_source: internal_source`).
|
|
249
|
+
*
|
|
250
|
+
* @param deploymentId - Deployment ID.
|
|
251
|
+
* @param args - Remote-build parameters.
|
|
252
|
+
* @param args.sourceTarballPath - GCS object path of the uploaded tarball.
|
|
253
|
+
* @param args.configPath - Path to `langgraph.json` inside the tarball.
|
|
254
|
+
* @param args.secrets - Optional secrets to set on the deployment.
|
|
255
|
+
* @param args.installCommand - Optional custom install command.
|
|
256
|
+
* @param args.buildCommand - Optional custom build command.
|
|
257
|
+
* @returns The updated deployment object.
|
|
258
|
+
*/
|
|
259
|
+
updateDeploymentInternalSource(deploymentId, args) {
|
|
260
|
+
const payload = {
|
|
261
|
+
revision_source: "internal_source",
|
|
262
|
+
source_revision_config: {
|
|
263
|
+
source_tarball_path: args.sourceTarballPath,
|
|
264
|
+
langgraph_config_path: args.configPath,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
const sourceConfig = {};
|
|
268
|
+
if (args.installCommand != null) {
|
|
269
|
+
sourceConfig.install_command = args.installCommand;
|
|
270
|
+
}
|
|
271
|
+
if (args.buildCommand != null) {
|
|
272
|
+
sourceConfig.build_command = args.buildCommand;
|
|
273
|
+
}
|
|
274
|
+
if (Object.keys(sourceConfig).length) {
|
|
275
|
+
payload.source_config = sourceConfig;
|
|
276
|
+
}
|
|
277
|
+
if (args.secrets != null) {
|
|
278
|
+
payload.secrets = args.secrets;
|
|
279
|
+
}
|
|
280
|
+
return this.request("PATCH", `/v2/deployments/${deploymentId}`, {
|
|
281
|
+
payload,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* List a deployment's revisions, newest first
|
|
286
|
+
* (`GET /v2/deployments/{id}/revisions`).
|
|
287
|
+
*
|
|
288
|
+
* @param deploymentId - Deployment ID.
|
|
289
|
+
* @param limit - Maximum number of revisions to return (default `1`).
|
|
290
|
+
* @returns A response object with a `resources` array of revisions.
|
|
291
|
+
*/
|
|
292
|
+
listRevisions(deploymentId, limit = 1) {
|
|
293
|
+
return this.request("GET", `/v2/deployments/${deploymentId}/revisions`, {
|
|
294
|
+
params: { limit },
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Fetch a single revision (`GET /v2/deployments/{id}/revisions/{revId}`).
|
|
299
|
+
*
|
|
300
|
+
* @param deploymentId - Deployment ID.
|
|
301
|
+
* @param revisionId - Revision ID.
|
|
302
|
+
* @returns The revision object (expected to contain a `status`).
|
|
303
|
+
*/
|
|
304
|
+
getRevision(deploymentId, revisionId) {
|
|
305
|
+
return this.request("GET", `/v2/deployments/${deploymentId}/revisions/${revisionId}`);
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Fetch build logs for a revision
|
|
309
|
+
* (`POST /v1/projects/{projectId}/revisions/{revId}/build_logs`).
|
|
310
|
+
*
|
|
311
|
+
* @param projectId - Project/deployment ID.
|
|
312
|
+
* @param revisionId - Revision ID.
|
|
313
|
+
* @param payload - Query body (e.g. `order`, `limit`, `offset`).
|
|
314
|
+
* @returns A response object with a `logs` array and optional `next_offset`.
|
|
315
|
+
*/
|
|
316
|
+
getBuildLogs(projectId, revisionId, payload) {
|
|
317
|
+
return this.request("POST", `/v1/projects/${projectId}/revisions/${revisionId}/build_logs`, { payload });
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Fetch agent-server runtime (deploy) logs. Targets a specific revision when
|
|
321
|
+
* `revisionId` is provided, otherwise the deployment as a whole.
|
|
322
|
+
*
|
|
323
|
+
* @param projectId - Project/deployment ID.
|
|
324
|
+
* @param payload - Query body (e.g. `order`, `limit`, `level`, `query`).
|
|
325
|
+
* @param revisionId - Optional revision ID to scope the logs.
|
|
326
|
+
* @returns A response object with a `logs` array.
|
|
327
|
+
*/
|
|
328
|
+
getDeployLogs(projectId, payload, revisionId) {
|
|
329
|
+
const path = revisionId
|
|
330
|
+
? `/v1/projects/${projectId}/revisions/${revisionId}/deploy_logs`
|
|
331
|
+
: `/v1/projects/${projectId}/deploy_logs`;
|
|
332
|
+
return this.request("POST", path, { payload });
|
|
333
|
+
}
|
|
334
|
+
}
|
package/dist/docker/docker.mjs
CHANGED
|
@@ -263,7 +263,12 @@ export async function configToDocker(configPath, config, localDeps, options) {
|
|
|
263
263
|
testFile("bun.lockb"),
|
|
264
264
|
]);
|
|
265
265
|
let installCmd = "npm i";
|
|
266
|
-
if (
|
|
266
|
+
if (options?.installCommand) {
|
|
267
|
+
// Explicit override (JS projects only); takes precedence over lockfile
|
|
268
|
+
// auto-detection. Mirrors the Python CLI's `--install-command`.
|
|
269
|
+
installCmd = options.installCommand;
|
|
270
|
+
}
|
|
271
|
+
else if (yarn) {
|
|
267
272
|
installCmd = "yarn install";
|
|
268
273
|
}
|
|
269
274
|
else if (pnpm) {
|
|
@@ -275,6 +280,9 @@ export async function configToDocker(configPath, config, localDeps, options) {
|
|
|
275
280
|
else if (bun) {
|
|
276
281
|
installCmd = "bun i";
|
|
277
282
|
}
|
|
283
|
+
const buildStep = options?.buildCommand
|
|
284
|
+
? `RUN ${options.buildCommand}`
|
|
285
|
+
: `RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts`;
|
|
278
286
|
const lines = [
|
|
279
287
|
`FROM ${getBaseImage(config)}`,
|
|
280
288
|
config.dockerfile_lines,
|
|
@@ -292,12 +300,7 @@ export async function configToDocker(configPath, config, localDeps, options) {
|
|
|
292
300
|
!!config.auth && `ENV LANGGRAPH_AUTH='${JSON.stringify(config.auth)}'`,
|
|
293
301
|
!!config.http && `ENV LANGGRAPH_HTTP='${JSON.stringify(config.http)}'`,
|
|
294
302
|
!!localDeps.workingDir && `WORKDIR ${localDeps.workingDir}`,
|
|
295
|
-
"node_version" in config
|
|
296
|
-
? [
|
|
297
|
-
`RUN ${installCmd}`,
|
|
298
|
-
`RUN (test ! -f /api/langgraph_api/js/build.mts && echo "Prebuild script not found, skipping") || tsx /api/langgraph_api/js/build.mts`,
|
|
299
|
-
]
|
|
300
|
-
: undefined,
|
|
303
|
+
"node_version" in config ? [`RUN ${installCmd}`, buildStep] : undefined,
|
|
301
304
|
];
|
|
302
305
|
if (options?.watch && (localDeps.workingDir || localDeps.reloadDir)) {
|
|
303
306
|
// TODO: hacky, should add as entrypoint to the langgraph-api base image
|
|
@@ -355,7 +358,15 @@ export async function configToCompose(configPath, config, options) {
|
|
|
355
358
|
const extendEnvIgnore = new Set();
|
|
356
359
|
if (typeof config.env === "string") {
|
|
357
360
|
// try to parse out the env file
|
|
358
|
-
const
|
|
361
|
+
const projectRoot = path.dirname(configPath);
|
|
362
|
+
const envPath = path.resolve(projectRoot, config.env);
|
|
363
|
+
// Keep the config-supplied env path contained within the project so a
|
|
364
|
+
// malicious langgraph.json can't use `../` or an absolute path to read
|
|
365
|
+
// files outside the project (and surface them to docker compose).
|
|
366
|
+
const rel = path.relative(projectRoot, envPath);
|
|
367
|
+
if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
368
|
+
throw new Error(`env file '${config.env}' specified in langgraph.json is outside the project directory.`);
|
|
369
|
+
}
|
|
359
370
|
try {
|
|
360
371
|
const envFileKeys = (await fs.readFile(envPath, "utf-8"))
|
|
361
372
|
.split("\n")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/langgraph-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^18.19.0 || >=20.16.0"
|
|
@@ -29,16 +29,17 @@
|
|
|
29
29
|
"execa": "^9.5.2",
|
|
30
30
|
"exit-hook": "^4.0.0",
|
|
31
31
|
"extract-zip": "^2.0.1",
|
|
32
|
+
"ignore": "^7.0.5",
|
|
32
33
|
"langsmith": "^0.7.4",
|
|
33
34
|
"open": "^10.1.0",
|
|
34
35
|
"package-manager-detector": "^1.3.0",
|
|
35
36
|
"stacktrace-parser": "^0.1.10",
|
|
36
|
-
"tar": "^7.
|
|
37
|
+
"tar": "^7.5.16",
|
|
37
38
|
"winston": "^3.17.0",
|
|
38
39
|
"winston-console-format": "^1.0.8",
|
|
39
40
|
"yaml": "^2.8.3",
|
|
40
41
|
"zod": "^3.25.76 || ^4",
|
|
41
|
-
"@langchain/langgraph-api": "1.
|
|
42
|
+
"@langchain/langgraph-api": "1.4.0",
|
|
42
43
|
"create-langgraph": "1.1.5"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|