copilotkit 4.16.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.
Files changed (83) hide show
  1. package/README.md +195 -8
  2. package/cli-build-info.json +7 -7
  3. package/exporters/langgraph/README.md +118 -0
  4. package/exporters/langgraph/export_checkpointer.py +125 -0
  5. package/index.js +13890 -9434
  6. package/onboarding/index.json +1 -1
  7. package/onboarding/prompts/authenticate/start.md +24 -23
  8. package/onboarding/prompts/conversion/plan.md +3 -3
  9. package/onboarding/prompts/credentials/finalize-plan.md +56 -199
  10. package/onboarding/prompts/credentials/plan.md +24 -23
  11. package/onboarding/prompts/credentials/settle-credentials.md +40 -177
  12. package/onboarding/prompts/credentials/write-plan.md +47 -23
  13. package/onboarding/prompts/fallback/best-effort.md +25 -17
  14. package/onboarding/prompts/feature/a2ui/implement.md +40 -12
  15. package/onboarding/prompts/feature/a2ui/proof.md +29 -9
  16. package/onboarding/prompts/feature/a2ui/start.md +54 -12
  17. package/onboarding/prompts/feature/blocked-by-plan.md +4 -4
  18. package/onboarding/prompts/feature/channels/implement.md +41 -13
  19. package/onboarding/prompts/feature/channels/proof.md +30 -11
  20. package/onboarding/prompts/feature/channels/start.md +54 -9
  21. package/onboarding/prompts/feature/chat-suggestions/implement.md +40 -12
  22. package/onboarding/prompts/feature/chat-suggestions/proof.md +29 -9
  23. package/onboarding/prompts/feature/chat-suggestions/start.md +51 -10
  24. package/onboarding/prompts/feature/complete.md +2 -2
  25. package/onboarding/prompts/feature/learning/implement.md +66 -29
  26. package/onboarding/prompts/feature/learning/proof.md +30 -10
  27. package/onboarding/prompts/feature/learning/start.md +46 -20
  28. package/onboarding/prompts/feature/open-generative-ui/implement.md +41 -13
  29. package/onboarding/prompts/feature/open-generative-ui/proof.md +29 -9
  30. package/onboarding/prompts/feature/open-generative-ui/start.md +51 -10
  31. package/onboarding/prompts/feature/realtime-sync/implement.md +41 -13
  32. package/onboarding/prompts/feature/realtime-sync/proof.md +31 -10
  33. package/onboarding/prompts/feature/realtime-sync/start.md +51 -9
  34. package/onboarding/prompts/feature/rich-threads/implement.md +42 -14
  35. package/onboarding/prompts/feature/rich-threads/proof.md +31 -10
  36. package/onboarding/prompts/feature/rich-threads/start.md +51 -9
  37. package/onboarding/prompts/feature/stop.md +5 -5
  38. package/onboarding/prompts/feature/voice/implement.md +40 -12
  39. package/onboarding/prompts/feature/voice/proof.md +29 -9
  40. package/onboarding/prompts/feature/voice/start.md +51 -9
  41. package/onboarding/prompts/framework/ag2.md +2 -2
  42. package/onboarding/prompts/framework/agno.md +4 -4
  43. package/onboarding/prompts/framework/built-in.md +2 -2
  44. package/onboarding/prompts/framework/claude-sdk-python.md +8 -7
  45. package/onboarding/prompts/framework/claude-sdk-typescript.md +2 -2
  46. package/onboarding/prompts/framework/crewai-flows.md +15 -7
  47. package/onboarding/prompts/framework/deep-agents.md +4 -3
  48. package/onboarding/prompts/framework/google-adk.md +7 -7
  49. package/onboarding/prompts/framework/langgraph-fastapi.md +2 -2
  50. package/onboarding/prompts/framework/langgraph-python.md +2 -2
  51. package/onboarding/prompts/framework/langgraph-typescript.md +2 -2
  52. package/onboarding/prompts/framework/llamaindex.md +4 -4
  53. package/onboarding/prompts/framework/mastra.md +2 -2
  54. package/onboarding/prompts/framework/ms-agent-dotnet.md +2 -2
  55. package/onboarding/prompts/framework/ms-agent-harness-dotnet.md +2 -2
  56. package/onboarding/prompts/framework/ms-agent-python.md +6 -6
  57. package/onboarding/prompts/framework/pydantic-ai.md +2 -2
  58. package/onboarding/prompts/framework/strands-python.md +4 -4
  59. package/onboarding/prompts/framework/strands-typescript.md +4 -4
  60. package/onboarding/prompts/frontend/angular.md +3 -3
  61. package/onboarding/prompts/frontend/nextjs.md +16 -3
  62. package/onboarding/prompts/frontend/plan.md +9 -8
  63. package/onboarding/prompts/frontend/react-native.md +2 -2
  64. package/onboarding/prompts/frontend/react-spa.md +2 -2
  65. package/onboarding/prompts/frontend/vue.md +2 -2
  66. package/onboarding/prompts/implementation/build-and-validate.md +68 -30
  67. package/onboarding/prompts/proof/complete.md +24 -17
  68. package/onboarding/prompts/proof/oss-baseline.md +16 -12
  69. package/onboarding/prompts/proof/round-trip.md +39 -27
  70. package/onboarding/prompts/research/gather.md +8 -7
  71. package/onboarding/prompts/research/merge.md +3 -3
  72. package/onboarding/prompts/research/preflight.md +4 -4
  73. package/onboarding/prompts/research/route.md +6 -6
  74. package/onboarding/prompts/starter/clone.md +16 -12
  75. package/onboarding/prompts/stopped/run-failed.md +11 -11
  76. package/onboarding/prompts/subagent/create-plan.md +24 -10
  77. package/onboarding/prompts/subagent/implement-and-validate.md +25 -11
  78. package/onboarding/prompts/subagent/inspect-repository.md +21 -6
  79. package/onboarding/prompts/subagent/prove-oss-baseline.md +5 -4
  80. package/onboarding/prompts/subagent/prove-round-trip.md +77 -23
  81. package/onboarding/prompts/unsupported/no-validated-path.md +4 -4
  82. package/package.json +1 -5
  83. package/release/release-tool.js +189 -44
package/README.md CHANGED
@@ -280,8 +280,14 @@ and gives up after 10 minutes.
280
280
 
281
281
  Under `--json`, the first `authentication_url` record is the URL with the code
282
282
  in it, and it also carries `user_code` and `expires_at`. A sign-in that ends without a session writes a `failed`
283
- record with `reason: expired` or `reason: access_denied`. If the Ops service
284
- has no device routes, `login --json` falls back to the loopback flow.
283
+ record with `reason: expired` or `reason: access_denied`, and with
284
+ `retry: ask_developer`: a person must act before a new code can work. After 2
285
+ codes in a row expire with nobody approving them, `login --json` refuses to
286
+ request another. It writes a `failed` record with `reason: repeated_expiry` and
287
+ exits non-zero. Run `copilotkit login --json --force` when someone is ready to
288
+ approve. Any sign-in that succeeds resets the count, and the count lapses after
289
+ a day. If the Ops service has no device routes, `login --json` falls back to the
290
+ loopback flow.
285
291
 
286
292
  Approve a code only if your own terminal or coding agent showed it to you. If
287
293
  someone sent you a code, deny the request: approving it signs their CLI in as
@@ -334,7 +340,12 @@ asks which frontend they want and offers Slack and Microsoft Teams there. If the
334
340
  copied prompt came from a Slack or Teams docs page, that page is the named
335
341
  frontend.
336
342
  Its first prompt records the coding agent with `onboard identify`, including when
337
- the entry command omitted `--coding-agent`. Channel setup stage events include
343
+ the entry command omitted `--coding-agent`. The agent also passes `--model` with
344
+ the exact model id its instructions name, and leaves it out when they name none.
345
+ The event records a missing id as `unknown`, and the coding agent the environment
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
338
349
  the existing `onboarding_run_id` when the command runs inside an onboarding run;
339
350
  standalone commands omit it. These events use the CLI's existing telemetry consent gate.
340
351
 
@@ -486,11 +497,37 @@ are supported. Configure source identity and access with the
486
497
  [`MASTRA_IMPORT_*` variables](../../libs/import-mastra/README.md); destination
487
498
  Intelligence credentials use the usual import options.
488
499
 
489
- Local access requires Node 22.13+. Use the same source namespace when switching
500
+ Local access requires Node 22.13+ and npm. The CLI does not ship the LibSQL
501
+ driver, so the first local import installs `@mastra/libsql@1.14.3` and
502
+ `@mastra/core@1.48.0` into `~/.copilotkit/cache/mastra-driver/`
503
+ (`$XDG_CACHE_HOME/copilotkit/mastra-driver/` when that variable is an absolute
504
+ path) and reuses them after that. The install overrides the npm settings
505
+ `legacy-peer-deps` and `omit=optional`, which would otherwise leave out a
506
+ required peer or the native binary. If that install fails, the error prints the
507
+ `npm install` command to run by hand. Server mode installs nothing.
508
+
509
+ Use the same source namespace when switching
490
510
  between local and remote access so repeat imports deduplicate. Import while the
491
511
  source is idle. This reads existing history and pending data; it does not execute
492
512
  models, tools or native resume operations.
493
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
+
494
531
  ## Managed Channels
495
532
 
496
533
  `copilotkit channels` connects a hosted Intelligence project to Slack or
@@ -885,6 +922,20 @@ exist yet. After the credential step creates it, run
885
922
  Later changes still fail the audit. The same rule applies to
886
923
  `.copilotkit/project.json`. Missing source files remain protected.
887
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
+
888
939
  ## Version Control
889
940
 
890
941
  Outside a repository, `copilotkit init` runs `git init` in the new app and makes
@@ -894,7 +945,9 @@ before you start editing.
894
945
  Inside a repository you already have, it does neither. A second `git init` would
895
946
  make the new app a nested repository: commits made in it would go to the inner
896
947
  repository, while the outer one saw only an untracked directory. The CLI names
897
- 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`.
898
951
 
899
952
  The project binding is recorded at that repository's root, so every directory in
900
953
  it resolves to the same project, and the run says where it went. If the root
@@ -1016,14 +1069,33 @@ exits zero only when `/info` is valid, declares the agent named by `--agent`,
1016
1069
  reports no Intelligence entitlement, and `--round-trip` passes. Both `--round-trip` and
1017
1070
  `--agent` are required for an OSS pass.
1018
1071
 
1072
+ Without `--expect-runtime`, `verify` checks an app as open source on its own
1073
+ when nothing points at a hosted setup: no `.copilotkit/project.json`, no
1074
+ Intelligence key, and a runtime that answers `/info` without an Intelligence
1075
+ client. The report says so at the top, names the runtime URL it checked, and
1076
+ says when that URL was only assumed. `--json` reports
1077
+ `"expectRuntime": "oss"` with `"expectRuntimeSource": "detected"` (otherwise
1078
+ `"flag"` or `"default"`). The round trip still has to pass. It runs against the
1079
+ agent `--agent` names, with or without `--round-trip`. Without `--agent`, when
1080
+ the runtime declares exactly one agent, `verify` runs it and names it. With no
1081
+ agent or several, the run fails and names `--round-trip --agent <id>`. A runtime
1082
+ mounted `mode: "single-route"` cannot pass this way without a key, because
1083
+ `verify` reads a single-route answer from the Intelligence platform and an
1084
+ open-source runtime records nothing there. Mount it multi-route (the default)
1085
+ and point `--runtime-url` at that endpoint.
1086
+ Pass `--expect-runtime intelligence` to check the hosted setup instead. A
1087
+ recorded project, a key, or a runtime that does not answer keeps the hosted
1088
+ checks.
1089
+
1019
1090
  ### Which runtime URL gets probed
1020
1091
 
1021
1092
  `verify` works the port out from the project rather than assuming one. In order:
1022
1093
 
1023
1094
  1. `--runtime-url`, when you pass it.
1024
- 2. `"runtimeUrl"` in `.copilotkit/project.json` — an absolute URL you add by
1025
- hand for a project whose runtime is somewhere none of the below can find it.
1026
- The CLI never writes this field and never removes one you wrote.
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.
1027
1099
  3. `COPILOTKIT_RUNTIME_URL` in the project's `.env` / `.env.local`, when it is
1028
1100
  absolute.
1029
1101
  4. The port the app's own dev configuration declares: `PORT` in its env files,
@@ -1199,6 +1271,94 @@ started, and a starter that serves both halves from one script loses both when
1199
1271
  either one goes down. Restart what you started, with the command you started it
1200
1272
  with, and leave the rest running.
1201
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
+
1296
+ ### A model key has no credits or is rejected
1297
+
1298
+ A key with no credits passes every static check. The first model call then
1299
+ fails inside the agent, and the round-trip proof reports only a timeout.
1300
+
1301
+ Ask the vendor about one key before the build:
1302
+
1303
+ ```bash
1304
+ copilotkit onboard model-key --key OPENAI_API_KEY
1305
+ copilotkit onboard model-key --key ANTHROPIC_API_KEY --env-file agent/.env --json
1306
+ ```
1307
+
1308
+ The command reads the key from the env file (`.env` by default) and sends one
1309
+ request with a one-token answer to that vendor. The call costs a fraction of a
1310
+ cent. It knows `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, and `GOOGLE_API_KEY`.
1311
+
1312
+ - `pass`: the vendor served a model call with the key.
1313
+ - `fail`: the vendor refused the key. The cause is `model_quota` (no credits) or
1314
+ `model_auth` (a rejected key).
1315
+ - `missing`: the env file or the key is absent, or the value is a placeholder.
1316
+ - `unsupported` or `undetermined`: the check did not settle it. A rate limit, a
1317
+ vendor outage, or a probe model the key may not use proves nothing about the
1318
+ key, so none of them is a `fail`.
1319
+
1320
+ It does not send the key when a base URL such as `OPENAI_BASE_URL` points the
1321
+ SDK at another endpoint, or when the shell exports a different value for the
1322
+ key. It never prints the key, and it never prints the vendor's error message,
1323
+ because a vendor can echo part of the key in it.
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
+
1202
1362
  ### A command, subcommand, or flag that should exist is rejected
1203
1363
 
1204
1364
  `npx` keys its cache on the spec string, so `copilotkit@latest` can keep serving
@@ -1344,3 +1504,30 @@ The destination must not already exist. The CLI validates every bundle before it
1344
1504
  If a download or validation fails, the CLI leaves no partial destination.
1345
1505
  Container IDs must be unique and belong to the selected project. Select up to 50 containers.
1346
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.
@@ -2,20 +2,20 @@
2
2
  "schemaVersion": 1,
3
3
  "package": {
4
4
  "name": "copilotkit",
5
- "version": "4.16.0"
5
+ "version": "4.18.0"
6
6
  },
7
7
  "intelligence": {
8
- "commit": "9b0c17ef9b25db5614653a69190290fadaf9fe59"
8
+ "commit": "3a9266b5fa673a4974ad204d131ed63d5696dc00"
9
9
  },
10
10
  "copilotKit": {
11
- "submittedInput": "a933af0b132e",
12
- "commit": "a933af0b132e61d2d04be7ce0a8f55743af21ccc"
11
+ "submittedInput": "70d1f3c0a832",
12
+ "commit": "70d1f3c0a832d6274e2fe710e24102f6429f14f4"
13
13
  },
14
14
  "channel": "production",
15
15
  "triggeringActor": "BenTaylorDev",
16
16
  "workflow": {
17
- "runId": "36051066331",
18
- "runUrl": "https://github.com/CopilotKit/Intelligence/actions/runs/36051066331"
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-24T19:54:09Z"
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
+ }