@bastani/atomic 0.8.28 → 0.8.29-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/dist/builtin/cursor/CHANGELOG.md +27 -0
- package/dist/builtin/cursor/LICENSE +26 -0
- package/dist/builtin/cursor/README.md +22 -0
- package/dist/builtin/cursor/index.ts +9 -0
- package/dist/builtin/cursor/package.json +46 -0
- package/dist/builtin/cursor/src/auth.ts +352 -0
- package/dist/builtin/cursor/src/catalog-cache.ts +155 -0
- package/dist/builtin/cursor/src/config.ts +123 -0
- package/dist/builtin/cursor/src/conversation-state.ts +135 -0
- package/dist/builtin/cursor/src/cursor-models-raw.json +583 -0
- package/dist/builtin/cursor/src/model-mapper.ts +270 -0
- package/dist/builtin/cursor/src/models.ts +54 -0
- package/dist/builtin/cursor/src/native-loader.ts +71 -0
- package/dist/builtin/cursor/src/proto/README.md +34 -0
- package/dist/builtin/cursor/src/proto/agent_pb.ts +15294 -0
- package/dist/builtin/cursor/src/proto/protobuf-codec.ts +717 -0
- package/dist/builtin/cursor/src/provider.ts +301 -0
- package/dist/builtin/cursor/src/stream.ts +564 -0
- package/dist/builtin/cursor/src/transport.ts +791 -0
- package/dist/builtin/intercom/CHANGELOG.md +4 -0
- package/dist/builtin/intercom/package.json +2 -2
- package/dist/builtin/intercom/skills/intercom/SKILL.md +5 -5
- package/dist/builtin/mcp/CHANGELOG.md +4 -0
- package/dist/builtin/mcp/package.json +3 -3
- package/dist/builtin/subagents/CHANGELOG.md +13 -0
- package/dist/builtin/subagents/README.md +7 -3
- package/dist/builtin/subagents/agents/codebase-online-researcher.md +9 -24
- package/dist/builtin/subagents/agents/debugger.md +3 -5
- package/dist/builtin/subagents/package.json +4 -4
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +2 -1
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +2 -1
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +1 -0
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +19 -2
- package/dist/builtin/subagents/src/runs/shared/structured-output.ts +271 -10
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +12 -39
- package/dist/builtin/subagents/src/shared/types.ts +5 -3
- package/dist/builtin/subagents/src/shared/utils.ts +50 -10
- package/dist/builtin/subagents/src/slash/saved-chain-mapping.ts +77 -0
- package/dist/builtin/subagents/src/slash/slash-commands.ts +1 -55
- package/dist/builtin/web-access/CHANGELOG.md +5 -1
- package/dist/builtin/web-access/README.md +1 -1
- package/dist/builtin/web-access/github-extract.ts +1 -1
- package/dist/builtin/web-access/package.json +3 -3
- package/dist/builtin/workflows/CHANGELOG.md +26 -0
- package/dist/builtin/workflows/README.md +28 -8
- package/dist/builtin/workflows/builtin/deep-research-codebase.ts +9 -49
- package/dist/builtin/workflows/builtin/goal.ts +63 -106
- package/dist/builtin/workflows/builtin/index.d.ts +2 -0
- package/dist/builtin/workflows/builtin/open-claude-design.ts +31 -76
- package/dist/builtin/workflows/builtin/ralph.d.ts +2 -0
- package/dist/builtin/workflows/builtin/ralph.ts +227 -518
- package/dist/builtin/workflows/builtin/shared-prompts.ts +7 -0
- package/dist/builtin/workflows/package.json +2 -2
- package/dist/builtin/workflows/skills/research-codebase/SKILL.md +17 -3
- package/dist/builtin/workflows/src/extension/wiring.ts +72 -9
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +34 -0
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +13 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +86 -14
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +11 -3
- package/dist/builtin/workflows/src/shared/types.ts +8 -4
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +64 -2
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -8
- package/dist/builtin/workflows/src/tui/workflow-status.ts +2 -0
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +7 -7
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/builtin-packages.d.ts.map +1 -1
- package/dist/core/builtin-packages.js +6 -0
- package/dist/core/builtin-packages.js.map +1 -1
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +20 -0
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +17 -8
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts +11 -9
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +55 -10
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/project-trust.d.ts +1 -0
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js +3 -3
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/resource-loader.d.ts +11 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +72 -9
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +3 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +5 -5
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/structured-output.d.ts +39 -0
- package/dist/core/tools/structured-output.d.ts.map +1 -0
- package/dist/core/tools/structured-output.js +141 -0
- package/dist/core/tools/structured-output.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +36 -14
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +3 -0
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +16 -0
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +11 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +158 -11
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +39 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/custom-provider.md +1 -0
- package/docs/extensions.md +2 -2
- package/docs/models.md +2 -0
- package/docs/packages.md +3 -1
- package/docs/providers.md +15 -0
- package/docs/quickstart.md +3 -3
- package/docs/sdk.md +61 -0
- package/docs/security.md +1 -1
- package/docs/subagents.md +21 -0
- package/docs/usage.md +2 -0
- package/docs/workflows.md +28 -21
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/structured-output.ts +22 -53
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +12 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added support for local `-e <dir>` extension sources to borrow project-local Atomic resources from `<dir>/.atomic`, legacy `<dir>/.pi`, and `<dir>/.agents/skills` after resolving trust for that extension source, preserving package-manager provenance and explicit-path workflow forwarding while avoiding untrusted borrowed resources ([#1354](https://github.com/bastani-inc/atomic/issues/1354)).
|
|
8
|
+
- Added a prototype Rust/N-API Cursor HTTP/2 native transport binding through the generated `@bastani/atomic-natives` NAPI-RS package, so Atomic can use an in-process native HTTP/2 client without requiring Node.js on `PATH`.
|
|
9
|
+
- Added the experimental bundled `@bastani/cursor` provider scaffold so `/login` can offer Cursor OAuth, estimated fallback exposes `cursor/composer-2`, and Cursor model mapping/streaming hooks are available behind an isolated HTTP/2 Connect transport boundary with production-default protobuf decoding, buffered Connect frames, write-before-headers Run streaming, stable Cursor conversation ids, schema-correct Cursor MCP tool advertisement, Cursor MCP tool-call decoding with protobuf `Value` or raw UTF-8/JSON arguments and exec-id metadata, same-stream MCP tool-result resume, abort/idle cleanup for paused tool streams, Connect end-stream error classification, exact live model id fidelity without static default injection, fast/thinking catalog grouping, and usage-delta accumulation.
|
|
10
|
+
- Added the opt-in `createStructuredOutputTool({ schema, capture, output, name })` factory for terminating machine-readable final answers with schema-as-parameters validation, flat `details`, in-process capture, flat private `output.json` capture plus `output.meta.json` sidecar metadata, and prompt guidance for exact-once use without registering `structured_output` in normal agent sessions by default ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Changed the bundled builtin `ralph` workflow to run `/skill:prompt-engineer` prompt-engineering and `/skill:research-codebase` research before orchestration instead of starting with an RFC/planner stage, pass the research artifact as primary implementation context, reuse prior research session data on follow-up loops, and feed unresolved reviewer findings into later research passes ([#1371](https://github.com/bastani-inc/atomic/issues/1371)).
|
|
15
|
+
- Changed bundled `goal`, `ralph`, and `open-claude-design` decision gates to use schema-backed workflow `structured_output` stages instead of registering bespoke terminating custom tools.
|
|
16
|
+
- Changed bundled `goal` worker/reviewer prompts and `ralph` orchestrator/reviewer prompts to request end-to-end verification when practical, using browser-skilled subagents for web/frontend flows that may depend on backend/API behavior and tmux-skilled subagents for TUI or terminal-app scenarios.
|
|
17
|
+
- Bumped the bundled upstream pi runtime libraries `@earendil-works/pi-agent-core`, `@earendil-works/pi-ai`, and `@earendil-works/pi-tui` from `^0.79.1` to `^0.79.3`, bringing the latest upstream provider, model, agent-core, and TUI compatibility fixes into `@bastani/atomic`.
|
|
18
|
+
- Updated the structured-output extension example and SDK/workflow/extension docs to use the canonical factory instead of hand-rolled `terminate: true` wrappers, and documented that schema-specific calls pass fields directly rather than through `{ value: ... }` ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
|
|
19
|
+
- Enforced top-level object schemas for `structured_output` factory registration, with guidance to wrap array or primitive final values in object fields, made custom-named factory tools advertise the configured name in prompt metadata, and documented that text print mode recognizes factory-created custom structured-output tools without treating every terminating tool as printable ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
|
|
20
|
+
- Clarified SDK, extension, and workflow guidance that structured-output tools are opt-in custom tools, with workflow stages/tasks/chains/parallel items receiving `structured_output` only when they declare a `schema` and subagent children receiving it only when `outputSchema` is enabled ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Fixed the root `@bastani/atomic` package export to include a `default` condition alongside the ESM import target, improving compatibility with loaders that select default export conditions.
|
|
25
|
+
- Fixed extension custom UI focus deferral so full-screen overlays can keep keyboard focus while a parent/main-chat inline custom UI is pending, then focus that pending UI when the overlay is hidden; already-aborted custom UI calls no longer invoke factories or emit host custom-UI state changes ([#1353](https://github.com/bastani-inc/atomic/issues/1353)).
|
|
26
|
+
- Fixed text print mode to emit trailing terminating JSON from factory-created structured-output tools, including custom tool names such as `final_decision`, instead of only recognizing the canonical `structured_output` name ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
|
|
27
|
+
- Fixed terminating `structured_output` results to opt out of oversized-result persistence so large final JSON stays inline instead of being replaced by a `<persisted-output>` pointer ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
|
|
28
|
+
- Fixed cross-process structured-output file capture to preserve flat schema-valid params in `output.json` and write call metadata to an `output.meta.json` sidecar so parent readback can reject stale or non-final captures instead of accepting any schema-valid `output.json` payload by existence alone ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
|
|
29
|
+
- Fixed bundled subagent handling so explicit empty `tools: []` plus `outputSchema` grants only the schema-backed `structured_output` runtime tool instead of restoring default tools ([#1350](https://github.com/bastani-inc/atomic/issues/1350)).
|
|
30
|
+
- Fixed prerelease publishing for native Atomic artifacts by allowing the `@bastani/atomic-natives` package metadata in release-preparation verification, running native artifact builds on architecture-matched Blacksmith and macOS runners, and documenting the two-package publish flow while keeping npm provenance publishing on GitHub-hosted Ubuntu.
|
|
31
|
+
- Fixed the bundled experimental Cursor provider to honor per-request stream deadlines across open/read/resume writes, reset timed-out or aborted streams, clean up replaced paused turns safely, catch cleanup cancellation failures, tolerate non-MCP Cursor exec protocol messages without ending assistant turns, and align Run requests with Cursor's private CLI protocol by using blob/KV conversation state plus request-context tool-definition responses without the unsupported custom system-prompt field ([#1286](https://github.com/bastani-inc/atomic/issues/1286)).
|
|
32
|
+
- Fixed release archive startup for the bundled experimental Cursor provider by declaring `@bufbuild/protobuf` as an `@bastani/atomic` runtime dependency, covering Cursor in the bundled-package dependency metadata guard, and smoke-checking Cursor/protobuf assets in native archives ([#1286](https://github.com/bastani-inc/atomic/issues/1286)).
|
|
33
|
+
- Fixed bundled `ralph` skill-prompt stages to invoke bundled skills through `/skill:<name>` expansion so prompt engineering and research stages receive the intended skill instructions.
|
|
34
|
+
- Fixed concurrent bundled workflow stage resource reloads to serialize temporary subagent child environment isolation so parallel stage startup cannot leave parent process child flags accidentally cleared.
|
|
35
|
+
- Fixed bundled workflow stage sessions to keep workflow package skills (`create-spec`, `impeccable`, `prompt-engineer`, `research-codebase`, and `skill-creator`) available while disabling only the recursive workflows extension in child sessions.
|
|
36
|
+
- Fixed bundled workflow stage resource discovery so bundled subagent definitions stay available, `subagent` is active by default with the same two-hop nesting budget as main chat, and explicitly allowlisted bundled extension tools such as `subagent`, `web_search`, `fetch_content`, and `intercom` remain visible even when a workflow is launched from a subagent child process.
|
|
37
|
+
|
|
38
|
+
### Security
|
|
39
|
+
|
|
40
|
+
- Kept Cursor credentials OAuth-only with token/header and PKCE poll-secret redaction and no localhost proxy, while moving Cursor HTTP/2 to a bundled Rust/N-API native binding and no longer sending the current working directory as `previousWorkspaceUris` by default.
|
|
41
|
+
|
|
5
42
|
## [0.8.28] - 2026-06-11
|
|
6
43
|
|
|
7
44
|
### Added
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Added a prototype Rust/N-API HTTP/2 native binding and loader so the Cursor transport uses the generated `@bastani/atomic-natives` NAPI-RS package without requiring Node.js on `PATH`.
|
|
8
|
+
- Added the `@bastani/cursor` bundled provider scaffold with Cursor PKCE OAuth, token refresh, estimated/live model mapping, transport isolation, stream adapter hooks, lifecycle cleanup, and fake-transport tests.
|
|
9
|
+
- Added a safe production UUID generator path for Cursor login, refresh, and streaming, plus an injectable HTTP/2 Connect transport boundary with frame helpers and protocol-codec seams for live Cursor RPC work.
|
|
10
|
+
- Added the production-default minimal Cursor protobuf codec, buffered Connect frame decoder, HTTP/2 non-2xx/session/stream lifecycle error classification, and stricter live-discovery fallback policy.
|
|
11
|
+
- Hardened Cursor Run streaming to write the initial Connect frame before response headers, eagerly observe stream/session terminal events, encode conversation/tool context, decode Cursor `execServerMessage.mcpArgs` tool calls with protobuf `Value` arguments and field-order-independent exec ids, classify Connect end-stream errors, parse checkpoint token details without counting `max_tokens` as output, and accumulate usage deltas without zeroing missing checkpoint fields.
|
|
12
|
+
- Preserved live Cursor model id fidelity by exposing advertised fast/thinking ids instead of synthesizing absent base ids.
|
|
13
|
+
- Added a token-free live model catalog cache with atomic writes, cached startup registration, best-effort auth-first refresh discovery, and bounded first-stream rediscovery cleanup.
|
|
14
|
+
- Corrected Cursor MCP tool advertisement to encode Cursor's `McpTools` wrapper schema, stopped injecting static `composer-2` defaults into successful live or cached-live catalogs, and added stable conversation ids plus same-stream MCP tool-result resume for Cursor tool turns.
|
|
15
|
+
- Hardened Cursor's private protocol edge by accepting raw UTF-8/JSON MCP argument bytes in addition to protobuf `Value` arguments, correlating historical tool results with their originating tool calls, preserving fast/thinking model modes as separate catalog groups, treating ambiguous effort-like suffixes such as `-max` as standalone model names without sibling evidence, and cancelling paused tool streams on abort or idle timeout.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fixed Cursor tool-call continuations by background-pumping Run stream frames while Atomic executes tools, so request-context/KV/control frames are answered before the next public message is consumed and paused turns no longer appear frozen after the first tool call.
|
|
20
|
+
- Added bounded Cursor auth and transport request deadlines, cancelled paused streams when tool-result resume writes fail, and synchronized the Bun lockfile for the bundled Cursor package.
|
|
21
|
+
- Fixed Cursor stream timeout and lifecycle edge cases so per-request deadlines bound stream open/read/resume writes, timeout exits reset instead of gracefully closing streams, paused-turn abort/idle/replacement cleanup cannot leak or cancel a replacement turn, and non-MCP Cursor exec protocol messages are tolerated without ending the assistant turn ([#1286](https://github.com/bastani-inc/atomic/issues/1286)).
|
|
22
|
+
- Aligned Cursor Run request handling with the private Cursor CLI protocol by omitting the unsupported custom system-prompt field, serving conversation-state blobs through same-stream KV responses, returning MCP tool definitions from request-context responses, rejecting native Cursor execs so the model falls back to MCP tools, and pausing pending tool calls when Cursor waits for results without a terminal frame ([#1286](https://github.com/bastani-inc/atomic/issues/1286)).
|
|
23
|
+
- Fixed live Cursor models disappearing after CLI restart by rediscovering the live catalog on `session_start` from stored Cursor OAuth credentials when the cached catalog is missing or stale, so live-only models such as Composer 2.5 can be restored without requiring another login.
|
|
24
|
+
|
|
25
|
+
### Security
|
|
26
|
+
|
|
27
|
+
- Cursor credentials are handled through Atomic OAuth storage only; Authorization headers, token-like diagnostics, and Cursor PKCE poll verifier/UUID values are redacted, and HTTP/2 is handled by the bundled native transport without a localhost proxy or Node subprocess.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bastani, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
Attribution: small implementation facts and endpoint/protocol notes were adapted
|
|
16
|
+
from the MIT-licensed pi-cursor-provider project by Netanel Draiman
|
|
17
|
+
(https://github.com/ndraiman/pi-cursor-provider). This package does not copy the
|
|
18
|
+
provider wholesale and does not include its local proxy implementation.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @bastani/cursor
|
|
2
|
+
|
|
3
|
+
First-party Atomic provider for Cursor subscription models.
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
This package registers `cursor` via Atomic's bundled extension provider API. `/login` shows **Cursor** and stores credentials through Atomic OAuth storage (`~/.atomic/agent/auth.json`). The login URL and PKCE polling behavior intentionally match the MIT-licensed [`ndraiman/pi-cursor-provider`](https://github.com/ndraiman/pi-cursor-provider) reference: `callbacks.onAuth({ url: loginUrl })`, no extra login warning/instruction copy, and polling `api2.cursor.sh/auth/poll` until Cursor returns tokens.
|
|
8
|
+
|
|
9
|
+
The runtime protocol is also aligned to `ndraiman/pi-cursor-provider` at commit `82fc4e73f9ae820d87b34ac36713b18989910a36`: Atomic vendors the reference `cursor-models-raw.json` and generated `proto/agent_pb.ts`, and builds Cursor request/control messages through `@bufbuild/protobuf` descriptors instead of hand-maintained protobuf bytes. HTTP/2 itself is handled by the generated `@bastani/atomic-natives` Rust/N-API package rather than a separate local proxy.
|
|
10
|
+
|
|
11
|
+
The unavoidable Atomic-specific integration difference is the provider surface: Atomic exposes a native `cursor-agent` `streamSimple` provider instead of the reference package's localhost OpenAI-compatible proxy. The Cursor auth/model/protocol bytes should otherwise stay reference-derived.
|
|
12
|
+
|
|
13
|
+
## Limitations
|
|
14
|
+
|
|
15
|
+
- Text input only. Vision/image content is rejected.
|
|
16
|
+
- Cursor's private API may change without notice.
|
|
17
|
+
- HTTP/2 transport requires the bundled `@bastani/atomic-natives` Rust/N-API native client for the current platform.
|
|
18
|
+
- Credentials are OAuth-only. Do not pass Cursor tokens via command-line args, environment variables, logs, or local proxy processes.
|
|
19
|
+
|
|
20
|
+
## Attribution
|
|
21
|
+
|
|
22
|
+
Cursor auth, model fallback, and generated protobuf behavior are derived from the MIT-licensed `ndraiman/pi-cursor-provider` project.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default } from "./src/provider.js";
|
|
2
|
+
export { registerCursorProvider, type CursorProviderConfig, type CursorProviderHost, type CursorProviderRegistrationOptions, type CursorProviderRuntime } from "./src/provider.js";
|
|
3
|
+
export { CursorAuthError, CursorAuthService, CursorToken, createPkcePair, deriveCursorTokenExpiry, fromOAuthCredentials, redactOAuthCredentials, toOAuthCredentials } from "./src/auth.js";
|
|
4
|
+
export { CursorModelDiscoveryError, CursorModelDiscoveryService } from "./src/models.js";
|
|
5
|
+
export { FileCursorCatalogCache, getDefaultCursorCatalogCachePath, parseCursorCatalogCacheRecord, toCursorCatalogCacheRecord, type CursorCatalogCache } from "./src/catalog-cache.js";
|
|
6
|
+
export { createEstimatedCursorCatalog, insertEffortBeforeCursorSuffix, mapCursorCatalogToProviderModels, parseCursorVariant, resolveCursorModelVariant } from "./src/model-mapper.js";
|
|
7
|
+
export { CursorConversationStateStore } from "./src/conversation-state.js";
|
|
8
|
+
export { CursorStreamAdapter, createCursorStreamAdapter } from "./src/stream.js";
|
|
9
|
+
export { Http2CursorAgentTransport } from "./src/transport.js";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bastani/cursor",
|
|
3
|
+
"version": "0.8.29-alpha.3",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Experimental first-party Atomic extension for Cursor OAuth, model discovery, and streaming provider registration.",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Norin Lavaee",
|
|
8
|
+
"Alex Lavaee"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"bun": ">=1.3.14"
|
|
14
|
+
},
|
|
15
|
+
"main": "./index.ts",
|
|
16
|
+
"types": "./index.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./index.ts"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"index.ts",
|
|
22
|
+
"src/**/*.ts",
|
|
23
|
+
"src/**/*.json",
|
|
24
|
+
"src/proto/README.md",
|
|
25
|
+
"README.md",
|
|
26
|
+
"CHANGELOG.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"pi": {
|
|
30
|
+
"extensions": [
|
|
31
|
+
"./index.ts"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@bastani/atomic": "*"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@bastani/atomic": {
|
|
39
|
+
"optional": true
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@bastani/atomic-natives": "0.8.29-alpha.3",
|
|
44
|
+
"@bufbuild/protobuf": "^2.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import type { OAuthCredentials, OAuthLoginCallbacks } from "@earendil-works/pi-ai";
|
|
2
|
+
import { createHash, randomBytes as nodeRandomBytes, randomUUID } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
CURSOR_API_BASE_URL,
|
|
5
|
+
CURSOR_AUTH_POLL_PATH,
|
|
6
|
+
CURSOR_DEFAULT_TOKEN_TTL_MS,
|
|
7
|
+
CURSOR_OAUTH_EXPIRY_SKEW_MS,
|
|
8
|
+
CURSOR_REFRESH_PATH,
|
|
9
|
+
CURSOR_WEB_BASE_URL,
|
|
10
|
+
CURSOR_LOGIN_PATH,
|
|
11
|
+
parseJsonObject,
|
|
12
|
+
readStringField,
|
|
13
|
+
readNumberField,
|
|
14
|
+
sanitizeDiagnosticText,
|
|
15
|
+
} from "./config.js";
|
|
16
|
+
|
|
17
|
+
export type CursorAuthErrorCode =
|
|
18
|
+
| "LoginCancelled"
|
|
19
|
+
| "PollTimedOut"
|
|
20
|
+
| "PollRejected"
|
|
21
|
+
| "RefreshTokenExpired"
|
|
22
|
+
| "CursorApiRejected"
|
|
23
|
+
| "NetworkError";
|
|
24
|
+
|
|
25
|
+
export class CursorAuthError extends Error {
|
|
26
|
+
constructor(
|
|
27
|
+
readonly code: CursorAuthErrorCode,
|
|
28
|
+
message: string,
|
|
29
|
+
readonly status?: number,
|
|
30
|
+
) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "CursorAuthError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class CursorToken {
|
|
37
|
+
readonly kind: "access" | "refresh";
|
|
38
|
+
readonly #value: string;
|
|
39
|
+
|
|
40
|
+
constructor(kind: "access" | "refresh", value: string) {
|
|
41
|
+
this.kind = kind;
|
|
42
|
+
this.#value = value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
unwrap(): string {
|
|
46
|
+
return this.#value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
toString(): string {
|
|
50
|
+
return `[redacted cursor ${this.kind} token]`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
toJSON(): string {
|
|
54
|
+
return this.toString();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface CursorCredentialBundle {
|
|
59
|
+
readonly access: CursorToken;
|
|
60
|
+
readonly refresh: CursorToken;
|
|
61
|
+
readonly expires: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface CursorPkcePair {
|
|
65
|
+
readonly verifier: string;
|
|
66
|
+
readonly challenge: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type CursorFetch = (url: string, init?: RequestInit) => Promise<Response>;
|
|
70
|
+
export type CursorSleep = (milliseconds: number, signal?: AbortSignal) => Promise<void>;
|
|
71
|
+
export type CursorUuid = () => string;
|
|
72
|
+
export type CursorRandomBytes = (length: number) => Uint8Array;
|
|
73
|
+
export type CursorNow = () => number;
|
|
74
|
+
|
|
75
|
+
export interface CursorAuthServiceOptions {
|
|
76
|
+
readonly fetch?: CursorFetch;
|
|
77
|
+
readonly sleep?: CursorSleep;
|
|
78
|
+
readonly uuid?: CursorUuid;
|
|
79
|
+
readonly randomBytes?: CursorRandomBytes;
|
|
80
|
+
readonly now?: CursorNow;
|
|
81
|
+
readonly maxPollAttempts?: number;
|
|
82
|
+
readonly initialPollDelayMs?: number;
|
|
83
|
+
readonly maxPollDelayMs?: number;
|
|
84
|
+
readonly pollBackoffMultiplier?: number;
|
|
85
|
+
readonly fetchTimeoutMs?: number;
|
|
86
|
+
readonly apiBaseUrl?: string;
|
|
87
|
+
readonly webBaseUrl?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface CursorTokenResponse {
|
|
91
|
+
readonly accessToken: string;
|
|
92
|
+
readonly refreshToken: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const DEFAULT_MAX_POLL_ATTEMPTS = 150;
|
|
96
|
+
const DEFAULT_INITIAL_POLL_DELAY_MS = 1000;
|
|
97
|
+
const DEFAULT_MAX_POLL_DELAY_MS = 10000;
|
|
98
|
+
const DEFAULT_POLL_BACKOFF_MULTIPLIER = 1.2;
|
|
99
|
+
const DEFAULT_FETCH_TIMEOUT_MS = 30_000;
|
|
100
|
+
|
|
101
|
+
export function base64Url(bytes: Uint8Array): string {
|
|
102
|
+
return Buffer.from(bytes).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/u, "");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function createPkcePair(randomBytes: CursorRandomBytes = defaultRandomBytes): CursorPkcePair {
|
|
106
|
+
const verifier = base64Url(randomBytes(96));
|
|
107
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
108
|
+
return { verifier, challenge };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function toOAuthCredentials(bundle: CursorCredentialBundle): OAuthCredentials {
|
|
112
|
+
return {
|
|
113
|
+
access: bundle.access.unwrap(),
|
|
114
|
+
refresh: bundle.refresh.unwrap(),
|
|
115
|
+
expires: bundle.expires,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function fromOAuthCredentials(credentials: OAuthCredentials): CursorCredentialBundle {
|
|
120
|
+
return {
|
|
121
|
+
access: new CursorToken("access", credentials.access),
|
|
122
|
+
refresh: new CursorToken("refresh", credentials.refresh),
|
|
123
|
+
expires: credentials.expires,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function redactOAuthCredentials(credentials: OAuthCredentials): OAuthCredentials {
|
|
128
|
+
return {
|
|
129
|
+
access: "[redacted]",
|
|
130
|
+
refresh: "[redacted]",
|
|
131
|
+
expires: credentials.expires,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function deriveCursorTokenExpiry(accessToken: string, now: CursorNow = Date.now): number {
|
|
136
|
+
const parts = accessToken.split(".");
|
|
137
|
+
if (parts.length < 2 || !parts[1]) {
|
|
138
|
+
return now() + CURSOR_DEFAULT_TOKEN_TTL_MS;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const payload = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
143
|
+
const parsed = parseJsonObject(payload);
|
|
144
|
+
if (!parsed) {
|
|
145
|
+
return now() + CURSOR_DEFAULT_TOKEN_TTL_MS;
|
|
146
|
+
}
|
|
147
|
+
const exp = readNumberField(parsed, "exp");
|
|
148
|
+
return exp ? exp * 1000 - CURSOR_OAUTH_EXPIRY_SKEW_MS : now() + CURSOR_DEFAULT_TOKEN_TTL_MS;
|
|
149
|
+
} catch {
|
|
150
|
+
return now() + CURSOR_DEFAULT_TOKEN_TTL_MS;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export class CursorAuthService {
|
|
155
|
+
readonly #fetch: CursorFetch;
|
|
156
|
+
readonly #sleep: CursorSleep;
|
|
157
|
+
readonly #uuid: CursorUuid;
|
|
158
|
+
readonly #randomBytes: CursorRandomBytes;
|
|
159
|
+
readonly #now: CursorNow;
|
|
160
|
+
readonly #maxPollAttempts: number;
|
|
161
|
+
readonly #initialPollDelayMs: number;
|
|
162
|
+
readonly #maxPollDelayMs: number;
|
|
163
|
+
readonly #pollBackoffMultiplier: number;
|
|
164
|
+
readonly #fetchTimeoutMs: number;
|
|
165
|
+
readonly #apiBaseUrl: string;
|
|
166
|
+
readonly #webBaseUrl: string;
|
|
167
|
+
|
|
168
|
+
constructor(options: CursorAuthServiceOptions = {}) {
|
|
169
|
+
this.#fetch = options.fetch ?? fetch;
|
|
170
|
+
this.#sleep = options.sleep ?? sleep;
|
|
171
|
+
this.#uuid = options.uuid ?? randomUUID;
|
|
172
|
+
this.#randomBytes = options.randomBytes ?? defaultRandomBytes;
|
|
173
|
+
this.#now = options.now ?? Date.now;
|
|
174
|
+
this.#maxPollAttempts = options.maxPollAttempts ?? DEFAULT_MAX_POLL_ATTEMPTS;
|
|
175
|
+
this.#initialPollDelayMs = options.initialPollDelayMs ?? DEFAULT_INITIAL_POLL_DELAY_MS;
|
|
176
|
+
this.#maxPollDelayMs = options.maxPollDelayMs ?? DEFAULT_MAX_POLL_DELAY_MS;
|
|
177
|
+
this.#pollBackoffMultiplier = options.pollBackoffMultiplier ?? DEFAULT_POLL_BACKOFF_MULTIPLIER;
|
|
178
|
+
this.#fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
179
|
+
this.#apiBaseUrl = options.apiBaseUrl ?? CURSOR_API_BASE_URL;
|
|
180
|
+
this.#webBaseUrl = options.webBaseUrl ?? CURSOR_WEB_BASE_URL;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
184
|
+
const bundle = await this.loginCursor(callbacks);
|
|
185
|
+
return toOAuthCredentials(bundle);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async loginCursor(callbacks: OAuthLoginCallbacks): Promise<CursorCredentialBundle> {
|
|
189
|
+
const { verifier, challenge } = createPkcePair(this.#randomBytes);
|
|
190
|
+
const uuid = this.#uuid();
|
|
191
|
+
const loginUrl = new URL(CURSOR_LOGIN_PATH, this.#webBaseUrl);
|
|
192
|
+
loginUrl.searchParams.set("challenge", challenge);
|
|
193
|
+
loginUrl.searchParams.set("uuid", uuid);
|
|
194
|
+
loginUrl.searchParams.set("mode", "login");
|
|
195
|
+
loginUrl.searchParams.set("redirectTarget", "cli");
|
|
196
|
+
callbacks.onAuth({ url: loginUrl.toString() });
|
|
197
|
+
|
|
198
|
+
let delayMs = this.#initialPollDelayMs;
|
|
199
|
+
let consecutiveErrors = 0;
|
|
200
|
+
for (let attempt = 0; attempt < this.#maxPollAttempts; attempt += 1) {
|
|
201
|
+
await this.#sleep(delayMs, callbacks.signal);
|
|
202
|
+
if (callbacks.signal?.aborted) {
|
|
203
|
+
throw new CursorAuthError("LoginCancelled", "Cursor login was cancelled.");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const pollUrl = new URL(CURSOR_AUTH_POLL_PATH, this.#apiBaseUrl);
|
|
207
|
+
pollUrl.searchParams.set("uuid", uuid);
|
|
208
|
+
pollUrl.searchParams.set("verifier", verifier);
|
|
209
|
+
|
|
210
|
+
let response: Response;
|
|
211
|
+
try {
|
|
212
|
+
response = await this.fetchWithDeadline(pollUrl.toString(), { method: "GET" }, callbacks.signal);
|
|
213
|
+
} catch {
|
|
214
|
+
if (callbacks.signal?.aborted) throw new CursorAuthError("LoginCancelled", "Cursor login was cancelled.");
|
|
215
|
+
consecutiveErrors += 1;
|
|
216
|
+
if (consecutiveErrors >= 3) {
|
|
217
|
+
throw new CursorAuthError("NetworkError", "Cursor login polling failed after repeated network errors.");
|
|
218
|
+
}
|
|
219
|
+
delayMs = Math.min(this.#maxPollDelayMs, Math.ceil(delayMs * this.#pollBackoffMultiplier));
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (response.status === 404) {
|
|
224
|
+
consecutiveErrors = 0;
|
|
225
|
+
delayMs = Math.min(this.#maxPollDelayMs, Math.ceil(delayMs * this.#pollBackoffMultiplier));
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
consecutiveErrors += 1;
|
|
231
|
+
if (consecutiveErrors >= 3) {
|
|
232
|
+
const responseText = await response.text();
|
|
233
|
+
throw new CursorAuthError(
|
|
234
|
+
"PollRejected",
|
|
235
|
+
`Cursor login polling was rejected (${response.status}): ${sanitizeDiagnosticText(responseText, [verifier, uuid, pollUrl.toString()])}`,
|
|
236
|
+
response.status,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
delayMs = Math.min(this.#maxPollDelayMs, Math.ceil(delayMs * this.#pollBackoffMultiplier));
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const tokenResponse = parseTokenResponse(await response.text());
|
|
244
|
+
if (!tokenResponse || tokenResponse.refreshToken.length === 0) {
|
|
245
|
+
throw new CursorAuthError("PollRejected", "Cursor login response did not include usable tokens.", response.status);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
access: new CursorToken("access", tokenResponse.accessToken),
|
|
250
|
+
refresh: new CursorToken("refresh", tokenResponse.refreshToken),
|
|
251
|
+
expires: deriveCursorTokenExpiry(tokenResponse.accessToken, this.#now),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
throw new CursorAuthError("PollTimedOut", "Cursor login timed out before authorization completed.");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
|
259
|
+
const bundle = await this.refreshCursorCredentials(fromOAuthCredentials(credentials));
|
|
260
|
+
return toOAuthCredentials(bundle);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async refreshCursorCredentials(credentials: CursorCredentialBundle): Promise<CursorCredentialBundle> {
|
|
264
|
+
const refresh = credentials.refresh.unwrap();
|
|
265
|
+
let response: Response;
|
|
266
|
+
try {
|
|
267
|
+
response = await this.fetchWithDeadline(new URL(CURSOR_REFRESH_PATH, this.#apiBaseUrl).toString(), {
|
|
268
|
+
method: "POST",
|
|
269
|
+
headers: {
|
|
270
|
+
Authorization: `Bearer ${refresh}`,
|
|
271
|
+
"Content-Type": "application/json",
|
|
272
|
+
},
|
|
273
|
+
body: "{}",
|
|
274
|
+
});
|
|
275
|
+
} catch {
|
|
276
|
+
throw new CursorAuthError("NetworkError", "Cursor token refresh failed because the network request failed.");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (response.status === 401 || response.status === 403) {
|
|
280
|
+
throw new CursorAuthError("RefreshTokenExpired", "Cursor refresh token expired or was rejected; run /login again.", response.status);
|
|
281
|
+
}
|
|
282
|
+
if (!response.ok) {
|
|
283
|
+
const responseText = await response.text();
|
|
284
|
+
throw new CursorAuthError(
|
|
285
|
+
"CursorApiRejected",
|
|
286
|
+
`Cursor token refresh failed (${response.status}): ${sanitizeDiagnosticText(responseText, [refresh])}`,
|
|
287
|
+
response.status,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const tokenResponse = parseTokenResponse(await response.text());
|
|
292
|
+
if (!tokenResponse) {
|
|
293
|
+
throw new CursorAuthError("CursorApiRejected", "Cursor token refresh response did not include an access token.", response.status);
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
access: new CursorToken("access", tokenResponse.accessToken),
|
|
297
|
+
refresh: new CursorToken("refresh", tokenResponse.refreshToken || refresh),
|
|
298
|
+
expires: deriveCursorTokenExpiry(tokenResponse.accessToken, this.#now),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private async fetchWithDeadline(url: string, init: RequestInit, parentSignal?: AbortSignal): Promise<Response> {
|
|
303
|
+
const controller = new AbortController();
|
|
304
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
305
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
306
|
+
timeout = setTimeout(() => {
|
|
307
|
+
controller.abort();
|
|
308
|
+
reject(new CursorAuthError("NetworkError", "Cursor authentication request timed out."));
|
|
309
|
+
}, this.#fetchTimeoutMs);
|
|
310
|
+
timeout.unref?.();
|
|
311
|
+
});
|
|
312
|
+
const onAbort = (): void => controller.abort();
|
|
313
|
+
parentSignal?.addEventListener("abort", onAbort, { once: true });
|
|
314
|
+
try {
|
|
315
|
+
return await Promise.race([this.#fetch(url, { ...init, signal: controller.signal }), timeoutPromise]);
|
|
316
|
+
} finally {
|
|
317
|
+
if (timeout) clearTimeout(timeout);
|
|
318
|
+
parentSignal?.removeEventListener("abort", onAbort);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function parseTokenResponse(text: string): CursorTokenResponse | undefined {
|
|
324
|
+
const parsed = parseJsonObject(text);
|
|
325
|
+
if (!parsed) return undefined;
|
|
326
|
+
const accessToken = readStringField(parsed, "accessToken") ?? readStringField(parsed, "access_token");
|
|
327
|
+
const refreshToken = readStringField(parsed, "refreshToken") ?? readStringField(parsed, "refresh_token") ?? "";
|
|
328
|
+
if (!accessToken) return undefined;
|
|
329
|
+
return { accessToken, refreshToken };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function defaultRandomBytes(length: number): Uint8Array {
|
|
333
|
+
return nodeRandomBytes(length);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function sleep(milliseconds: number, signal?: AbortSignal): Promise<void> {
|
|
337
|
+
if (signal?.aborted) {
|
|
338
|
+
return Promise.reject(new CursorAuthError("LoginCancelled", "Cursor login was cancelled."));
|
|
339
|
+
}
|
|
340
|
+
return new Promise((resolve, reject) => {
|
|
341
|
+
const abort = (): void => {
|
|
342
|
+
clearTimeout(timer);
|
|
343
|
+
signal?.removeEventListener("abort", abort);
|
|
344
|
+
reject(new CursorAuthError("LoginCancelled", "Cursor login was cancelled."));
|
|
345
|
+
};
|
|
346
|
+
const timer = setTimeout(() => {
|
|
347
|
+
signal?.removeEventListener("abort", abort);
|
|
348
|
+
resolve();
|
|
349
|
+
}, milliseconds);
|
|
350
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
351
|
+
});
|
|
352
|
+
}
|