copilotkit 4.17.0 → 4.18.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/README.md +127 -5
- package/cli-build-info.json +7 -7
- package/exporters/langgraph/README.md +118 -0
- package/exporters/langgraph/export_checkpointer.py +125 -0
- package/index.js +12227 -9022
- package/onboarding/index.json +1 -1
- package/onboarding/prompts/authenticate/start.md +18 -19
- package/onboarding/prompts/conversion/plan.md +3 -3
- package/onboarding/prompts/credentials/finalize-plan.md +57 -200
- package/onboarding/prompts/credentials/plan.md +24 -23
- package/onboarding/prompts/credentials/settle-credentials.md +40 -213
- package/onboarding/prompts/credentials/write-plan.md +17 -8
- package/onboarding/prompts/fallback/best-effort.md +12 -12
- package/onboarding/prompts/feature/a2ui/implement.md +7 -7
- package/onboarding/prompts/feature/a2ui/proof.md +7 -7
- package/onboarding/prompts/feature/a2ui/start.md +53 -9
- package/onboarding/prompts/feature/channels/implement.md +8 -8
- package/onboarding/prompts/feature/channels/proof.md +7 -7
- package/onboarding/prompts/feature/channels/start.md +50 -7
- package/onboarding/prompts/feature/chat-suggestions/implement.md +7 -7
- package/onboarding/prompts/feature/chat-suggestions/proof.md +7 -7
- package/onboarding/prompts/feature/chat-suggestions/start.md +50 -7
- package/onboarding/prompts/feature/complete.md +2 -2
- package/onboarding/prompts/feature/learning/implement.md +24 -19
- package/onboarding/prompts/feature/learning/proof.md +8 -8
- package/onboarding/prompts/feature/learning/start.md +43 -20
- package/onboarding/prompts/feature/open-generative-ui/implement.md +7 -7
- package/onboarding/prompts/feature/open-generative-ui/proof.md +7 -7
- package/onboarding/prompts/feature/open-generative-ui/start.md +50 -7
- package/onboarding/prompts/feature/realtime-sync/implement.md +8 -8
- package/onboarding/prompts/feature/realtime-sync/proof.md +7 -7
- package/onboarding/prompts/feature/realtime-sync/start.md +49 -6
- package/onboarding/prompts/feature/rich-threads/implement.md +9 -9
- package/onboarding/prompts/feature/rich-threads/proof.md +7 -7
- package/onboarding/prompts/feature/rich-threads/start.md +49 -6
- package/onboarding/prompts/feature/stop.md +5 -5
- package/onboarding/prompts/feature/voice/implement.md +7 -7
- package/onboarding/prompts/feature/voice/proof.md +7 -7
- package/onboarding/prompts/feature/voice/start.md +50 -7
- package/onboarding/prompts/framework/ag2.md +2 -2
- package/onboarding/prompts/framework/agno.md +2 -2
- package/onboarding/prompts/framework/built-in.md +2 -2
- package/onboarding/prompts/framework/claude-sdk-python.md +2 -2
- package/onboarding/prompts/framework/claude-sdk-typescript.md +2 -2
- package/onboarding/prompts/framework/crewai-flows.md +2 -2
- package/onboarding/prompts/framework/deep-agents.md +2 -2
- package/onboarding/prompts/framework/google-adk.md +2 -2
- package/onboarding/prompts/framework/langgraph-fastapi.md +2 -2
- package/onboarding/prompts/framework/langgraph-python.md +2 -2
- package/onboarding/prompts/framework/langgraph-typescript.md +2 -2
- package/onboarding/prompts/framework/llamaindex.md +2 -2
- package/onboarding/prompts/framework/mastra.md +2 -2
- package/onboarding/prompts/framework/ms-agent-dotnet.md +2 -2
- package/onboarding/prompts/framework/ms-agent-harness-dotnet.md +2 -2
- package/onboarding/prompts/framework/ms-agent-python.md +2 -2
- package/onboarding/prompts/framework/pydantic-ai.md +2 -2
- package/onboarding/prompts/framework/strands-python.md +2 -2
- package/onboarding/prompts/framework/strands-typescript.md +2 -2
- package/onboarding/prompts/frontend/angular.md +3 -3
- package/onboarding/prompts/frontend/nextjs.md +3 -3
- package/onboarding/prompts/frontend/plan.md +9 -8
- package/onboarding/prompts/frontend/react-native.md +2 -2
- package/onboarding/prompts/frontend/react-spa.md +2 -2
- package/onboarding/prompts/frontend/vue.md +2 -2
- package/onboarding/prompts/implementation/build-and-validate.md +19 -20
- package/onboarding/prompts/proof/complete.md +11 -11
- package/onboarding/prompts/proof/oss-baseline.md +5 -5
- package/onboarding/prompts/proof/round-trip.md +16 -15
- package/onboarding/prompts/research/gather.md +6 -6
- package/onboarding/prompts/research/merge.md +3 -3
- package/onboarding/prompts/research/preflight.md +4 -4
- package/onboarding/prompts/research/route.md +5 -5
- package/onboarding/prompts/starter/clone.md +8 -7
- package/onboarding/prompts/stopped/run-failed.md +4 -4
- package/onboarding/prompts/subagent/create-plan.md +10 -1
- package/onboarding/prompts/subagent/inspect-repository.md +9 -2
- package/onboarding/prompts/subagent/prove-oss-baseline.md +1 -1
- package/onboarding/prompts/subagent/prove-round-trip.md +47 -17
- package/onboarding/prompts/unsupported/no-validated-path.md +4 -4
- package/package.json +1 -1
- package/release/release-tool.js +19 -5
package/README.md
CHANGED
|
@@ -343,7 +343,9 @@ Its first prompt records the coding agent with `onboard identify`, including whe
|
|
|
343
343
|
the entry command omitted `--coding-agent`. The agent also passes `--model` with
|
|
344
344
|
the exact model id its instructions name, and leaves it out when they name none.
|
|
345
345
|
The event records a missing id as `unknown`, and the coding agent the environment
|
|
346
|
-
shows as `detected_coding_agent`.
|
|
346
|
+
shows as `detected_coding_agent`. `cli.onboarding.started` also carries
|
|
347
|
+
`detected_coding_agent`, because a run that stops before `onboard identify` sends
|
|
348
|
+
no identify event. Channel setup stage events include
|
|
347
349
|
the existing `onboarding_run_id` when the command runs inside an onboarding run;
|
|
348
350
|
standalone commands omit it. These events use the CLI's existing telemetry consent gate.
|
|
349
351
|
|
|
@@ -509,6 +511,23 @@ between local and remote access so repeat imports deduplicate. Import while the
|
|
|
509
511
|
source is idle. This reads existing history and pending data; it does not execute
|
|
510
512
|
models, tools or native resume operations.
|
|
511
513
|
|
|
514
|
+
## Import Strands Python History
|
|
515
|
+
|
|
516
|
+
Run `copilotkit import --source strands-python --dry-run` to preview local
|
|
517
|
+
Strands Python sessions. Both `FileSessionManager` records and
|
|
518
|
+
`SnapshotSessionManager` snapshots are supported. Set the source root, stable
|
|
519
|
+
source identity and explicit format through the
|
|
520
|
+
[`STRANDS_PY_IMPORT_*` variables](../../libs/import-strands-python/README.md).
|
|
521
|
+
Omit `--dry-run` to use the normal agent mapping and upload flow.
|
|
522
|
+
|
|
523
|
+
Import an idle store. Extraction reads native files without creating an agent,
|
|
524
|
+
calling a model or tool, or restoring a session. Snapshot imports use the latest
|
|
525
|
+
saved view; they do not merge older snapshots into an invented transcript.
|
|
526
|
+
Unavailable event timestamps remain absent. Native state and pending interaction
|
|
527
|
+
identity are retained; routing a continuation to the original agent is separate.
|
|
528
|
+
Use `ag-ui-strands>=0.4.1` when generating new sources that include frontend-tool
|
|
529
|
+
results. Import cannot repair placeholders already saved by older adapters.
|
|
530
|
+
|
|
512
531
|
## Managed Channels
|
|
513
532
|
|
|
514
533
|
`copilotkit channels` connects a hosted Intelligence project to Slack or
|
|
@@ -903,6 +922,20 @@ exist yet. After the credential step creates it, run
|
|
|
903
922
|
Later changes still fail the audit. The same rule applies to
|
|
904
923
|
`.copilotkit/project.json`. Missing source files remain protected.
|
|
905
924
|
|
|
925
|
+
Inside a git repository, the first `onboard protect` records the files that git
|
|
926
|
+
lists as changed or untracked. Git can restore committed files, so the baseline
|
|
927
|
+
leaves them out. Outside a repository, the baseline records every file under the
|
|
928
|
+
run's folder, except in `node_modules`, `dist`, `build`, `out`, `coverage`,
|
|
929
|
+
`.next`, `venv` and `__pycache__`. The capture report says which of the two it
|
|
930
|
+
used.
|
|
931
|
+
|
|
932
|
+
A git repository at your home directory, or above it, does not count as the
|
|
933
|
+
repository of a project below it. It is usually a dotfiles or accidental
|
|
934
|
+
repository, and `git status` there reads your whole home directory. A run that
|
|
935
|
+
starts in such a project takes the file baseline instead, and the report names
|
|
936
|
+
the repository it did not use. A run that starts in the home directory itself
|
|
937
|
+
still uses its repository.
|
|
938
|
+
|
|
906
939
|
## Version Control
|
|
907
940
|
|
|
908
941
|
Outside a repository, `copilotkit init` runs `git init` in the new app and makes
|
|
@@ -912,7 +945,9 @@ before you start editing.
|
|
|
912
945
|
Inside a repository you already have, it does neither. A second `git init` would
|
|
913
946
|
make the new app a nested repository: commits made in it would go to the inner
|
|
914
947
|
repository, while the outer one saw only an untracked directory. The CLI names
|
|
915
|
-
the repository it found and leaves the new app for you to commit.
|
|
948
|
+
the repository it found and leaves the new app for you to commit. A repository
|
|
949
|
+
at your home directory, or above it, does not count here, so an app created below
|
|
950
|
+
it gets its own `git init`.
|
|
916
951
|
|
|
917
952
|
The project binding is recorded at that repository's root, so every directory in
|
|
918
953
|
it resolves to the same project, and the run says where it went. If the root
|
|
@@ -1057,9 +1092,10 @@ checks.
|
|
|
1057
1092
|
`verify` works the port out from the project rather than assuming one. In order:
|
|
1058
1093
|
|
|
1059
1094
|
1. `--runtime-url`, when you pass it.
|
|
1060
|
-
2. `"runtimeUrl"` in `.copilotkit/project.json` — an absolute URL
|
|
1061
|
-
|
|
1062
|
-
|
|
1095
|
+
2. `"runtimeUrl"` in `.copilotkit/project.json` — an absolute URL for a
|
|
1096
|
+
project whose runtime is somewhere none of the below can find it. Add it by
|
|
1097
|
+
hand, or let `project select --runtime-url <url>` or
|
|
1098
|
+
`onboard runtime-url --url <url>` write it. The CLI never removes one.
|
|
1063
1099
|
3. `COPILOTKIT_RUNTIME_URL` in the project's `.env` / `.env.local`, when it is
|
|
1064
1100
|
absolute.
|
|
1065
1101
|
4. The port the app's own dev configuration declares: `PORT` in its env files,
|
|
@@ -1235,6 +1271,28 @@ started, and a starter that serves both halves from one script loses both when
|
|
|
1235
1271
|
either one goes down. Restart what you started, with the command you started it
|
|
1236
1272
|
with, and leave the rest running.
|
|
1237
1273
|
|
|
1274
|
+
### `verify` probes port 3000 while the app runs on another port
|
|
1275
|
+
|
|
1276
|
+
A dev server whose port is taken moves to the next free one without asking. A
|
|
1277
|
+
project that `init --create` made records no runtime URL, so `verify` assumes
|
|
1278
|
+
port 3000. Record the URL the server reports once it is up:
|
|
1279
|
+
|
|
1280
|
+
```bash
|
|
1281
|
+
copilotkit onboard runtime-url --url http://localhost:3001/api/copilotkit
|
|
1282
|
+
```
|
|
1283
|
+
|
|
1284
|
+
It writes `runtimeUrl` into the `.copilotkit/project.json` that governs the
|
|
1285
|
+
current directory and changes nothing else. It mints no key and does not touch
|
|
1286
|
+
`.env`, unlike `project select --runtime-url`, which selects the project again.
|
|
1287
|
+
It needs no bound run. The URL must name the mount path: a bare
|
|
1288
|
+
`http://localhost:3001` is refused, because `verify` would probe `/`. With no
|
|
1289
|
+
project record, it writes nothing and exits non-zero.
|
|
1290
|
+
|
|
1291
|
+
An onboarding run protects the record once it selects the project. When the
|
|
1292
|
+
bound run protects it, the command moves that baseline with its own write, so
|
|
1293
|
+
the next `onboard audit` still passes. It refuses a record that changed since
|
|
1294
|
+
the baseline, so a write it did not make is never absorbed.
|
|
1295
|
+
|
|
1238
1296
|
### A model key has no credits or is rejected
|
|
1239
1297
|
|
|
1240
1298
|
A key with no credits passes every static check. The first model call then
|
|
@@ -1264,6 +1322,43 @@ SDK at another endpoint, or when the shell exports a different value for the
|
|
|
1264
1322
|
key. It never prints the key, and it never prints the vendor's error message,
|
|
1265
1323
|
because a vendor can echo part of the key in it.
|
|
1266
1324
|
|
|
1325
|
+
### An onboarding run stops while it selects the project
|
|
1326
|
+
|
|
1327
|
+
The onboarding graph settles the project, its key, and the Learning Container
|
|
1328
|
+
with one step. Run it from the app directory whose `.env` receives the key:
|
|
1329
|
+
|
|
1330
|
+
```bash
|
|
1331
|
+
copilotkit onboard credentials --json
|
|
1332
|
+
copilotkit onboard credentials --create billing-portal \
|
|
1333
|
+
--runtime-url http://localhost:3000/api/copilotkit --json
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
With no project flag, the step reuses a project that this directory already
|
|
1337
|
+
binds with a key. Otherwise it asks for the project name. With `--create` or
|
|
1338
|
+
`--project`, it runs `project select`, retries once when no key was written, and
|
|
1339
|
+
checks the record and the key. Then it reads the default Learning Container.
|
|
1340
|
+
|
|
1341
|
+
Read `status`:
|
|
1342
|
+
|
|
1343
|
+
- `ready`: the project, key, and container are settled.
|
|
1344
|
+
- `needs-developer`: `question` names the project name or the app directory
|
|
1345
|
+
that the step needs.
|
|
1346
|
+
- `stopped`: `stopReason` names the check, the exact file or code, and the
|
|
1347
|
+
friction category for the stop report.
|
|
1348
|
+
|
|
1349
|
+
When the project is settled, the same step checks the model credential:
|
|
1350
|
+
|
|
1351
|
+
```bash
|
|
1352
|
+
copilotkit onboard credentials --model-key OPENAI_API_KEY --json
|
|
1353
|
+
```
|
|
1354
|
+
|
|
1355
|
+
It asks the vendor about each key, as `onboard model-key` does. When every key
|
|
1356
|
+
settles, it protects the credential paths for the run's audit. A missing or
|
|
1357
|
+
refused key is a `needs-developer` answer.
|
|
1358
|
+
|
|
1359
|
+
It needs a bound run and exits zero for every answer. A `stopped` answer files
|
|
1360
|
+
the stop report itself. It never prints a key.
|
|
1361
|
+
|
|
1267
1362
|
### A command, subcommand, or flag that should exist is rejected
|
|
1268
1363
|
|
|
1269
1364
|
`npx` keys its cache on the spec string, so `copilotkit@latest` can keep serving
|
|
@@ -1409,3 +1504,30 @@ The destination must not already exist. The CLI validates every bundle before it
|
|
|
1409
1504
|
If a download or validation fails, the CLI leaves no partial destination.
|
|
1410
1505
|
Container IDs must be unique and belong to the selected project. Select up to 50 containers.
|
|
1411
1506
|
Multiple containers use one product API batch request. If any container fails, the whole request fails.
|
|
1507
|
+
|
|
1508
|
+
## Import standalone LangGraph history
|
|
1509
|
+
|
|
1510
|
+
Use `copilotkit import --source langgraph --langgraph-snapshot native.jsonl`
|
|
1511
|
+
for a Python FastAPI application that does not expose LangGraph Server APIs.
|
|
1512
|
+
Export saved checkpoints with the helper shipped at
|
|
1513
|
+
`exporters/langgraph/export_checkpointer.py` in this CLI package (with its guide
|
|
1514
|
+
at `exporters/langgraph/README.md`). Supply explicit
|
|
1515
|
+
native thread/graph selections when graphs share storage. See the
|
|
1516
|
+
[LangGraph exporter guide](../../libs/import-langgraph/README.md) for the native
|
|
1517
|
+
contract, async export example, discovery rules, source limitations and validation.
|
|
1518
|
+
The existing API credentials, agent map, dry-run and replace options apply.
|
|
1519
|
+
The snapshot retains native IDs and checkpoint provenance; replay does not prove
|
|
1520
|
+
continuation on the original native session.
|
|
1521
|
+
|
|
1522
|
+
## Replay an application’s custom A2UI catalog
|
|
1523
|
+
|
|
1524
|
+
When native `render_a2ui` calls omit `catalogId`, supply the original application catalog explicitly:
|
|
1525
|
+
|
|
1526
|
+
```sh
|
|
1527
|
+
copilotkit import --source langgraph --langgraph-snapshot native.jsonl \
|
|
1528
|
+
--agent-map agent-map.json --a2ui-default-catalog-id copilotkit://app-dashboard-catalog -y
|
|
1529
|
+
```
|
|
1530
|
+
|
|
1531
|
+
The destination frontend must register that catalog. This option works with all import sources and applies to every selected conversation; import applications with different defaults separately. Explicit native `catalogId` values and native A2UI operation payloads take precedence. Native tool arguments/results are preserved; the setting only controls reconstructed UI. Without the option, existing replay behavior is unchanged. Invalid empty/whitespace identifiers fail before upload. No catalog is inferred from message prose.
|
|
1532
|
+
|
|
1533
|
+
API callers can set `replayConfig: { defaultCatalogId: "copilotkit://app-dashboard-catalog" }` on each normalized conversation. An unchanged source identity is still deduplicated: changing this option does not update an existing import. Use the existing `--replace` workflow to regenerate its imported history, after accounting for any destination changes.
|
package/cli-build-info.json
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"package": {
|
|
4
4
|
"name": "copilotkit",
|
|
5
|
-
"version": "4.
|
|
5
|
+
"version": "4.18.0"
|
|
6
6
|
},
|
|
7
7
|
"intelligence": {
|
|
8
|
-
"commit": "
|
|
8
|
+
"commit": "3a9266b5fa673a4974ad204d131ed63d5696dc00"
|
|
9
9
|
},
|
|
10
10
|
"copilotKit": {
|
|
11
|
-
"submittedInput": "
|
|
12
|
-
"commit": "
|
|
11
|
+
"submittedInput": "70d1f3c0a832",
|
|
12
|
+
"commit": "70d1f3c0a832d6274e2fe710e24102f6429f14f4"
|
|
13
13
|
},
|
|
14
14
|
"channel": "production",
|
|
15
15
|
"triggeringActor": "BenTaylorDev",
|
|
16
16
|
"workflow": {
|
|
17
|
-
"runId": "
|
|
18
|
-
"runUrl": "https://github.com/CopilotKit/Intelligence/actions/runs/
|
|
17
|
+
"runId": "36480955998",
|
|
18
|
+
"runUrl": "https://github.com/CopilotKit/Intelligence/actions/runs/36480955998"
|
|
19
19
|
},
|
|
20
20
|
"validationResult": "passed",
|
|
21
21
|
"ag2": {
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"revision": "main",
|
|
25
25
|
"pinned": false
|
|
26
26
|
},
|
|
27
|
-
"builtAt": "2026-09-
|
|
27
|
+
"builtAt": "2026-09-28T20:43:08Z"
|
|
28
28
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# LangGraph import
|
|
2
|
+
|
|
3
|
+
`@cpki/import-langgraph` normalizes saved LangGraph Server history and standalone
|
|
4
|
+
Python graph checkpoint exports for the shared Intelligence import pipeline.
|
|
5
|
+
|
|
6
|
+
## Standalone FastAPI applications
|
|
7
|
+
|
|
8
|
+
FastAPI and AG-UI do not imply a LangGraph Server thread API. Use the compiled
|
|
9
|
+
graph and its configured checkpointer in the application's own environment.
|
|
10
|
+
The exporter does not open SQL tables, invoke graph nodes, answer interrupts,
|
|
11
|
+
or write checkpoints. It requires public `aget_state_history`, `aget_state`,
|
|
12
|
+
`get_output_jsonschema`, and optionally the saver's `alist` API.
|
|
13
|
+
|
|
14
|
+
Copy `python/export_checkpointer.py` into your application tooling (also shipped
|
|
15
|
+
in the CLI package under `exporters/langgraph/`). Run this with the application's
|
|
16
|
+
existing dependency versions and matching compiled graph:
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import json
|
|
20
|
+
from export_checkpointer import export_thread
|
|
21
|
+
|
|
22
|
+
# graph already has the application's durable checkpointer attached.
|
|
23
|
+
# Obtain this selection from your application's thread/graph registry.
|
|
24
|
+
configs = [{"configurable": {"thread_id": "native-thread-id", "checkpoint_ns": ""}}]
|
|
25
|
+
with open("native.jsonl", "x", encoding="utf-8") as output:
|
|
26
|
+
for config in configs:
|
|
27
|
+
record = await export_thread(
|
|
28
|
+
graph, config, source_id="production-chat-store", graph_id="chat"
|
|
29
|
+
)
|
|
30
|
+
output.write(json.dumps(record, allow_nan=False) + "\n")
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This snippet belongs inside your application's async export routine with the
|
|
34
|
+
checkpointer open. Use an idle source or a consistent read-only database copy.
|
|
35
|
+
The exporter detects a changed head checkpoint and asks you to retry. It cannot
|
|
36
|
+
provide a transaction spanning arbitrary custom storage implementations. Never
|
|
37
|
+
run startup code that initializes or mutates the retained production store merely
|
|
38
|
+
to export it. Preserve the same source label when exporting a copy of that store.
|
|
39
|
+
|
|
40
|
+
If one graph owns the entire selected namespace, discover configs with
|
|
41
|
+
`async for config in discover_threads(saver, graph_owns_namespace=True)`.
|
|
42
|
+
`checkpoint_ns` defaults to the root namespace `""`; select another namespace
|
|
43
|
+
explicitly with a compiled graph that supports reading it. Do not infer ownership
|
|
44
|
+
when multiple graphs share the same saver and namespace. Supply explicit configs
|
|
45
|
+
from the application's registry for each graph instead. Missing graph ownership
|
|
46
|
+
cannot be recovered from the generic saver contract. Stores without discovery
|
|
47
|
+
support can still export known thread IDs.
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
copilotkit import --source langgraph --langgraph-snapshot native.jsonl \
|
|
51
|
+
--agent-map agent-map.json --unrecoverable imported-unknown --dry-run -y
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`agent-map.json` maps the declared graph key to your configured Intelligence agent,
|
|
55
|
+
for example `{"chat":"beautiful-chat"}`. Remove `--dry-run` after inspecting the
|
|
56
|
+
preview. Existing API credentials and `--replace` behavior apply. Snapshot mode
|
|
57
|
+
makes no LangGraph Server requests. Invalid records, conflicting graph identity,
|
|
58
|
+
duplicate conversation identities, and histories that do not follow a newest-first
|
|
59
|
+
parent checkpoint chain fail enumeration before opening a batch.
|
|
60
|
+
Treat the file as sensitive conversation data; it may include user messages,
|
|
61
|
+
attachments, graph state and interrupt values. No database credentials or runtime
|
|
62
|
+
configuration are exported by the helper.
|
|
63
|
+
|
|
64
|
+
## Fidelity and identity
|
|
65
|
+
|
|
66
|
+
The current checkpoint's messages and output-schema-projected state are
|
|
67
|
+
canonical. Available text, media, tool calls/results, frontend tool requests and
|
|
68
|
+
native task interrupts reuse the existing converter and replay pipeline. The
|
|
69
|
+
exporter uses the head read to apply completed pending writes. It preserves
|
|
70
|
+
native task/interrupt IDs and checkpoint/parent selectors as observed provenance.
|
|
71
|
+
Exports follow the current head’s ancestor chain; sibling branches are excluded.
|
|
72
|
+
Those selectors never authorize a rewind, fork or outbound route.
|
|
73
|
+
|
|
74
|
+
The opaque dedup key is a JSON tuple of format, source ID, graph ID, namespace and
|
|
75
|
+
native thread ID. It is stable across checkpoint revisions and distinct across
|
|
76
|
+
stores/namespaces. It is not an executable thread ID. `nativeSource.threadId`
|
|
77
|
+
retains the exact native ID. `sourceId` is a stable non-secret store/deployment
|
|
78
|
+
label; `graphId` selects interpretation and does not prove physical isolation.
|
|
79
|
+
Continuation to the original session is separate from replay/import support.
|
|
80
|
+
|
|
81
|
+
Checkpointers do not supply Server run records. Imported transcripts therefore
|
|
82
|
+
use a synthetic run with unknown status unless a native failure is recorded;
|
|
83
|
+
metadata explicitly marks run boundaries unavailable. Middleware-only UI or HTML
|
|
84
|
+
never saved by the source cannot be reconstructed. Unknown non-JSON native values
|
|
85
|
+
and integers outside the JavaScript safe range fail export instead of silently
|
|
86
|
+
changing their values. Task exceptions are exported as their Python `repr`
|
|
87
|
+
(type and message), matching persisted LangGraph task errors.
|
|
88
|
+
|
|
89
|
+
## Tests
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
python -m pip install -r libs/import-langgraph/python/requirements-test.txt
|
|
93
|
+
pnpm nx run @cpki/import-langgraph:test-native
|
|
94
|
+
pnpm nx test @cpki/import-langgraph
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Install the workspace Node dependencies with `pnpm install --frozen-lockfile`
|
|
98
|
+
before running the native tests. They emit JSON Schema from the actual TypeScript
|
|
99
|
+
importer schema and validate every fresh export against it.
|
|
100
|
+
|
|
101
|
+
The SQLite test runs a local graph with a genuine interrupt, checks checkpoint
|
|
102
|
+
and pending-write rows before/after export, reopens the database and compares
|
|
103
|
+
snapshot equality and database bytes. It makes no model/provider calls. The
|
|
104
|
+
captured synthetic native snapshot in `fixtures/langgraph/checkpointer-native.jsonl`
|
|
105
|
+
is consumed by TypeScript contract tests.
|
|
106
|
+
|
|
107
|
+
## Replay an application’s custom A2UI catalog
|
|
108
|
+
|
|
109
|
+
When native `render_a2ui` calls omit `catalogId`, supply the original application catalog explicitly:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
copilotkit import --source langgraph --langgraph-snapshot native.jsonl \
|
|
113
|
+
--agent-map agent-map.json --a2ui-default-catalog-id copilotkit://app-dashboard-catalog -y
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The destination frontend must register that catalog. This option works with all import sources and applies to every selected conversation; import applications with different defaults separately. Explicit native `catalogId` values and native A2UI operation payloads take precedence. Native tool arguments/results are preserved; the setting only controls reconstructed UI. Without the option, existing replay behavior is unchanged. Invalid empty/whitespace identifiers fail before upload. No catalog is inferred from message prose.
|
|
117
|
+
|
|
118
|
+
API callers can set `replayConfig: { defaultCatalogId: "copilotkit://app-dashboard-catalog" }` on each normalized conversation. An unchanged source identity is still deduplicated: changing this option does not update an existing import. Use the existing `--replace` workflow to regenerate its imported history, after accounting for any destination changes.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Read-only LangGraph export helpers. Run inside the application's Python environment.
|
|
2
|
+
|
|
3
|
+
The caller owns checkpointer lifecycle, credentials and graph/thread selection.
|
|
4
|
+
This module never invokes a graph or opens a database directly.
|
|
5
|
+
"""
|
|
6
|
+
from dataclasses import asdict, is_dataclass
|
|
7
|
+
from math import isfinite
|
|
8
|
+
|
|
9
|
+
from langchain_core.messages import BaseMessage
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def json_value(value):
|
|
13
|
+
"""Convert native messages/tasks into JSON without lossy string fallbacks."""
|
|
14
|
+
if value is None or isinstance(value, (str, bool)):
|
|
15
|
+
return value
|
|
16
|
+
if isinstance(value, int):
|
|
17
|
+
if abs(value) > 9007199254740991:
|
|
18
|
+
raise ValueError('Native integer exceeds the JavaScript safe integer range')
|
|
19
|
+
return value
|
|
20
|
+
if isinstance(value, float) and isfinite(value):
|
|
21
|
+
return value
|
|
22
|
+
if isinstance(value, Exception):
|
|
23
|
+
# Match LangGraph's persisted task-error representation (type + message).
|
|
24
|
+
return repr(value)
|
|
25
|
+
if isinstance(value, BaseMessage):
|
|
26
|
+
return json_value(value.model_dump(mode='json'))
|
|
27
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
28
|
+
return json_value(asdict(value))
|
|
29
|
+
if hasattr(value, '_asdict'):
|
|
30
|
+
return json_value(value._asdict())
|
|
31
|
+
if isinstance(value, dict):
|
|
32
|
+
if any(not isinstance(key, str) for key in value):
|
|
33
|
+
raise ValueError('Native data contains non-string JSON object keys')
|
|
34
|
+
return {key: json_value(item) for key, item in value.items()}
|
|
35
|
+
if isinstance(value, (list, tuple)):
|
|
36
|
+
return [json_value(item) for item in value]
|
|
37
|
+
raise ValueError(f'Native data is not losslessly JSON serializable: {type(value).__name__}')
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def checkpoint_ref(config):
|
|
41
|
+
"""Keep checkpoint selectors only; never export source credentials/configuration."""
|
|
42
|
+
if config is None:
|
|
43
|
+
return None
|
|
44
|
+
selected = config['configurable']
|
|
45
|
+
return {key: selected[key] for key in ('thread_id', 'checkpoint_ns', 'checkpoint_id', 'checkpoint_map') if key in selected}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def discover_threads(checkpointer, *, graph_owns_namespace, checkpoint_ns=''):
|
|
49
|
+
"""Discover IDs via public saver API in a namespace owned by ONE graph.
|
|
50
|
+
|
|
51
|
+
Do not use on a store shared by graphs without a separate ownership registry.
|
|
52
|
+
Instead supply explicit per-graph thread configs from that registry.
|
|
53
|
+
"""
|
|
54
|
+
if graph_owns_namespace is not True:
|
|
55
|
+
raise ValueError('Discovery requires explicit single-graph namespace ownership; supply selected configs for shared stores')
|
|
56
|
+
seen = set()
|
|
57
|
+
async for item in checkpointer.alist(None):
|
|
58
|
+
selected = item.config['configurable']
|
|
59
|
+
if selected.get('checkpoint_ns', '') != checkpoint_ns:
|
|
60
|
+
continue
|
|
61
|
+
thread_id = selected['thread_id']
|
|
62
|
+
if thread_id not in seen:
|
|
63
|
+
seen.add(thread_id)
|
|
64
|
+
yield {'configurable': {'thread_id': thread_id, 'checkpoint_ns': checkpoint_ns}}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def export_thread(graph, config, *, source_id, graph_id):
|
|
68
|
+
"""Export one selected native thread through public graph history reads.
|
|
69
|
+
|
|
70
|
+
Use an idle source or a consistent read-only copy and the matching compiled
|
|
71
|
+
graph. History is the current head's ancestry, newest-first, including pending
|
|
72
|
+
task writes/interrupts. Sibling branches are excluded.
|
|
73
|
+
A checkpoint revision in config is rejected: this exports the current head,
|
|
74
|
+
not a branch rewind. JSON output is data, never continuation authorization.
|
|
75
|
+
"""
|
|
76
|
+
if not source_id or not graph_id:
|
|
77
|
+
raise ValueError('source_id and graph_id must be nonempty stable labels')
|
|
78
|
+
selected = config['configurable']
|
|
79
|
+
if not selected.get('thread_id') or selected.get('checkpoint_id'):
|
|
80
|
+
raise ValueError('Select a native thread ID without a checkpoint revision')
|
|
81
|
+
namespace = selected.get('checkpoint_ns', '')
|
|
82
|
+
history = []
|
|
83
|
+
async for state in graph.aget_state_history(config):
|
|
84
|
+
if state.metadata and state.metadata.get('graph_id', graph_id) != graph_id:
|
|
85
|
+
raise ValueError('Recorded graph ownership conflicts with selected graph')
|
|
86
|
+
history.append({
|
|
87
|
+
'values': json_value(state.values),
|
|
88
|
+
'next': list(state.next),
|
|
89
|
+
'tasks': json_value(state.tasks),
|
|
90
|
+
'checkpoint': checkpoint_ref(state.config),
|
|
91
|
+
'parent_checkpoint': checkpoint_ref(state.parent_config),
|
|
92
|
+
'metadata': json_value(state.metadata),
|
|
93
|
+
'created_at': state.created_at,
|
|
94
|
+
})
|
|
95
|
+
if not history:
|
|
96
|
+
raise ValueError('No saved checkpoints for the selected native thread')
|
|
97
|
+
# Detect a concurrent source advance instead of claiming a consistent export.
|
|
98
|
+
head = await graph.aget_state(config)
|
|
99
|
+
if checkpoint_ref(head.config) != history[0]['checkpoint']:
|
|
100
|
+
raise ValueError('Native checkpoint advanced during export; retry on an idle source')
|
|
101
|
+
# aget_state applies completed pending writes to the authoritative head.
|
|
102
|
+
history[0]['values'] = json_value(head.values)
|
|
103
|
+
history[0]['next'] = list(head.next)
|
|
104
|
+
history[0]['tasks'] = json_value(head.tasks)
|
|
105
|
+
# Public history can contain sibling branches after a fork. Export only the
|
|
106
|
+
# current head's ancestor chain so adjacency has the same meaning on import.
|
|
107
|
+
revisions = {entry['checkpoint']['checkpoint_id']: entry for entry in history}
|
|
108
|
+
lineage = []
|
|
109
|
+
seen = set()
|
|
110
|
+
entry = history[0]
|
|
111
|
+
while entry is not None:
|
|
112
|
+
revision = entry['checkpoint']['checkpoint_id']
|
|
113
|
+
if revision in seen:
|
|
114
|
+
raise ValueError('Native checkpoint ancestry contains a cycle')
|
|
115
|
+
seen.add(revision)
|
|
116
|
+
lineage.append(entry)
|
|
117
|
+
parent = entry['parent_checkpoint']
|
|
118
|
+
entry = revisions.get(parent['checkpoint_id']) if parent else None
|
|
119
|
+
return {
|
|
120
|
+
'format': 'copilotkit.langgraph.checkpointer.v1',
|
|
121
|
+
'sourceId': source_id, 'graphId': graph_id,
|
|
122
|
+
'config': {'thread_id': selected['thread_id'], 'checkpoint_ns': namespace},
|
|
123
|
+
'outputSchema': json_value(graph.get_output_jsonschema()),
|
|
124
|
+
'history': lineage,
|
|
125
|
+
}
|