@nmzpy/pi-ember-stack 0.1.1 → 0.1.2
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/ATTRIBUTION.md +7 -2
- package/README.md +90 -69
- package/package.json +49 -50
- package/plugins/devin-auth/AGENTS.md +63 -0
- package/plugins/devin-auth/LICENSE +21 -0
- package/plugins/devin-auth/README.md +56 -0
- package/plugins/devin-auth/extensions/index.ts +141 -0
- package/plugins/devin-auth/src/cloud-direct/auth.ts +246 -0
- package/plugins/devin-auth/src/cloud-direct/catalog.ts +246 -0
- package/plugins/devin-auth/src/cloud-direct/chat.ts +1091 -0
- package/plugins/devin-auth/src/cloud-direct/index.ts +41 -0
- package/plugins/devin-auth/src/cloud-direct/metadata.ts +78 -0
- package/plugins/devin-auth/src/cloud-direct/wire.ts +202 -0
- package/plugins/devin-auth/src/context-map.ts +170 -0
- package/plugins/devin-auth/src/models.ts +236 -0
- package/plugins/devin-auth/src/oauth/login.ts +95 -0
- package/plugins/devin-auth/src/oauth/register-user.ts +174 -0
- package/plugins/devin-auth/src/oauth/types.ts +71 -0
- package/plugins/devin-auth/src/stream.ts +341 -0
- package/{src/pi-ember-stack.ts → plugins/ember/index.ts} +2 -2
- package/plugins/index.ts +138 -0
- package/{src → plugins}/subagent/extensions/model.ts +96 -96
- package/tsconfig.json +2 -1
- /package/{src → plugins/ember}/questionnaire-tool.ts +0 -0
- /package/{src → plugins}/subagent/LICENSE +0 -0
- /package/{src → plugins}/subagent/README.md +0 -0
- /package/{src → plugins}/subagent/agent-format.md +0 -0
- /package/{src → plugins}/subagent/agents/architect.md +0 -0
- /package/{src → plugins}/subagent/agents/coder.md +0 -0
- /package/{src → plugins}/subagent/agents/general-purpose.md +0 -0
- /package/{src → plugins}/subagent/agents/reviewer.md +0 -0
- /package/{src → plugins}/subagent/agents/scout.md +0 -0
- /package/{src → plugins}/subagent/agents/worker.md +0 -0
- /package/{src → plugins}/subagent/extensions/agents.ts +0 -0
- /package/{src → plugins}/subagent/extensions/index.ts +0 -0
- /package/{src → plugins}/subagent/extensions/package.json +0 -0
- /package/{src → plugins}/subagent/extensions/render.ts +0 -0
- /package/{src → plugins}/subagent/extensions/runner.ts +0 -0
- /package/{src → plugins}/subagent/extensions/service.ts +0 -0
- /package/{src → plugins}/subagent/extensions/thread-viewer.ts +0 -0
- /package/{src → plugins}/subagent/extensions/threads.ts +0 -0
package/ATTRIBUTION.md
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
# Attribution
|
|
2
2
|
|
|
3
|
-
`
|
|
3
|
+
`plugins/subagent/extensions/` and the upstream bundled agent files were vendored
|
|
4
4
|
from `@bacnh85/pi-subagent` version `0.5.0`.
|
|
5
5
|
|
|
6
6
|
The upstream package declares the MIT license. The vendored subtree retains
|
|
7
|
-
that license in `
|
|
7
|
+
that license in `plugins/subagent/LICENSE`, along with the original README, agent
|
|
8
8
|
format documentation, bundled roles, package version, and attribution.
|
|
9
9
|
Upstream project: <https://github.com/bacnh85/pi-extensions>.
|
|
10
|
+
|
|
11
|
+
`plugins/devin-auth/` was vendored from `pi-devin-auth` version `0.1.2`,
|
|
12
|
+
maintained by `nmzpy`. Its original MIT license and documentation are retained
|
|
13
|
+
in that plugin directory. Upstream project:
|
|
14
|
+
<https://github.com/nmzpy/pi-devin-auth>.
|
package/README.md
CHANGED
|
@@ -1,69 +1,90 @@
|
|
|
1
|
-
# pi-ember-stack
|
|
2
|
-
|
|
3
|
-
The Ember-owned pi package. It installs the complete Ember agent workflow as
|
|
4
|
-
one package:
|
|
5
|
-
|
|
6
|
-
- Primary modes: `/coder`, `/architect`, `/doctor`, `/orchestrator`, and
|
|
7
|
-
`/ui-doctor`.
|
|
8
|
-
- Inline `questionnaire` UI for decision-oriented questions. Agents are told to
|
|
9
|
-
prefer it when they need a user choice.
|
|
10
|
-
- A compact native `edit` renderer that shows the filename and a single
|
|
11
|
-
`+N / -N` result row.
|
|
12
|
-
- Vendored subagent support with bundled `coder` and `architect` definitions,
|
|
13
|
-
plus the upstream bundled roles.
|
|
14
|
-
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
pi
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
1
|
+
# pi-ember-stack
|
|
2
|
+
|
|
3
|
+
The Ember-owned pi package. It installs the complete Ember agent workflow as
|
|
4
|
+
one package:
|
|
5
|
+
|
|
6
|
+
- Primary modes: `/coder`, `/architect`, `/doctor`, `/orchestrator`, and
|
|
7
|
+
`/ui-doctor`.
|
|
8
|
+
- Inline `questionnaire` UI for decision-oriented questions. Agents are told to
|
|
9
|
+
prefer it when they need a user choice.
|
|
10
|
+
- A compact native `edit` renderer that shows the filename and a single
|
|
11
|
+
`+N / -N` result row.
|
|
12
|
+
- Vendored subagent support with bundled `coder` and `architect` definitions,
|
|
13
|
+
plus the upstream bundled roles.
|
|
14
|
+
- Bundled Devin auth/provider support, including OAuth, model catalog refresh,
|
|
15
|
+
and streaming transport.
|
|
16
|
+
- A self-contained `Ctrl+Space` mode-cycle shortcut and a footer showing the
|
|
17
|
+
active mode, model, and thinking variant.
|
|
18
|
+
|
|
19
|
+
## Plugin registry
|
|
20
|
+
|
|
21
|
+
The package has one top-level pi extension which dispatches to the internal
|
|
22
|
+
plugins under `plugins/`. Ember projects enable them in `.pi/ember-stack.json`:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"plugins": ["ember", "subagent", "devin-auth"]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Remove a plugin ID to disable it, or use `/stack-plugins` to toggle one from
|
|
31
|
+
the TUI. Restart pi after changing the list. The available plugins are:
|
|
32
|
+
|
|
33
|
+
- `ember`: modes, questionnaire, footer, and compact edit rendering.
|
|
34
|
+
- `subagent`: bundled subagent tool and agent definitions.
|
|
35
|
+
- `devin-auth`: Devin provider, OAuth, catalog refresh, and streaming.
|
|
36
|
+
|
|
37
|
+
## Project setup
|
|
38
|
+
|
|
39
|
+
The Ember repository contains a project-local `.pi/settings.json` entry for:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
"npm:@nmzpy/pi-ember-stack@0.1.2"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
On a new clone, start pi from the project directory. Pi will ask for a
|
|
46
|
+
one-time project trust decision before it installs the package into the
|
|
47
|
+
project-local `.pi/npm/` directory. The same decision can be approved
|
|
48
|
+
non-interactively with:
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
pi --approve
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Project trust is intentionally a user decision; a repository cannot safely
|
|
55
|
+
bypass it. After trust, normal startup is just `pi` from the Ember directory.
|
|
56
|
+
|
|
57
|
+
When a new version is intentionally released, update the pinned version in
|
|
58
|
+
the project settings and run:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
pi update --extensions
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Third-party utilities such as pi-fff and image paste remain separate package
|
|
65
|
+
entries. Devin auth is now bundled as a stack plugin, but credentials and
|
|
66
|
+
provider secrets stay in the machine-local pi configuration and are not part
|
|
67
|
+
of this repository.
|
|
68
|
+
|
|
69
|
+
## Development
|
|
70
|
+
|
|
71
|
+
The package entrypoint is `plugins/index.ts`. Ember functionality is under
|
|
72
|
+
`plugins/ember/`, subagents and bundled agents are under `plugins/subagent/`,
|
|
73
|
+
and Devin auth is under `plugins/devin-auth/`.
|
|
74
|
+
|
|
75
|
+
Run the package typecheck with:
|
|
76
|
+
|
|
77
|
+
```text
|
|
78
|
+
npm install
|
|
79
|
+
npm run typecheck
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Release
|
|
83
|
+
|
|
84
|
+
Run `./gacp.sh --release` to bump the patch version, typecheck, commit, tag,
|
|
85
|
+
push, and publish the package to npm. This only publishes the package; update
|
|
86
|
+
Ember's pinned package version separately when you want the project to install it.
|
|
87
|
+
|
|
88
|
+
The package is cross-platform: bundled paths are resolved from `import.meta.url`
|
|
89
|
+
and do not depend on a Windows home directory or the current working
|
|
90
|
+
directory.
|
package/package.json
CHANGED
|
@@ -1,50 +1,49 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@nmzpy/pi-ember-stack",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Ember's
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"keywords": [
|
|
8
|
-
"pi-package",
|
|
9
|
-
"pi-extension",
|
|
10
|
-
"ember"
|
|
11
|
-
],
|
|
12
|
-
"repository": {
|
|
13
|
-
"type": "git",
|
|
14
|
-
"url": "git+https://github.com/nmzpy/pi-ember-stack.git"
|
|
15
|
-
},
|
|
16
|
-
"bugs": {
|
|
17
|
-
"url": "https://github.com/nmzpy/pi-ember-stack/issues"
|
|
18
|
-
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"typecheck": "tsc --noEmit"
|
|
21
|
-
},
|
|
22
|
-
"files": [
|
|
23
|
-
"README.md",
|
|
24
|
-
"ATTRIBUTION.md",
|
|
25
|
-
"tsconfig.json",
|
|
26
|
-
"
|
|
27
|
-
],
|
|
28
|
-
"pi": {
|
|
29
|
-
"extensions": [
|
|
30
|
-
"./
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"@earendil-works/pi-
|
|
36
|
-
"@earendil-works/pi-agent
|
|
37
|
-
"@earendil-works/pi-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"@earendil-works/pi-
|
|
43
|
-
"@earendil-works/pi-agent
|
|
44
|
-
"@earendil-works/pi-
|
|
45
|
-
"@
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@nmzpy/pi-ember-stack",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Ember's configurable pi plugin stack with modes, questionnaire, compact edits, subagents, and Devin auth",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"pi-package",
|
|
9
|
+
"pi-extension",
|
|
10
|
+
"ember"
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/nmzpy/pi-ember-stack.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/nmzpy/pi-ember-stack/issues"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"README.md",
|
|
24
|
+
"ATTRIBUTION.md",
|
|
25
|
+
"tsconfig.json",
|
|
26
|
+
"plugins"
|
|
27
|
+
],
|
|
28
|
+
"pi": {
|
|
29
|
+
"extensions": [
|
|
30
|
+
"./plugins/index.ts"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@earendil-works/pi-ai": "*",
|
|
35
|
+
"@earendil-works/pi-agent-core": "*",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
37
|
+
"@earendil-works/pi-tui": "*",
|
|
38
|
+
"typebox": "*"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@earendil-works/pi-ai": "^0.80.6",
|
|
42
|
+
"@earendil-works/pi-agent-core": "^0.80.6",
|
|
43
|
+
"@earendil-works/pi-coding-agent": "^0.80.6",
|
|
44
|
+
"@earendil-works/pi-tui": "^0.80.6",
|
|
45
|
+
"@types/node": "^20.19.43",
|
|
46
|
+
"typebox": "^1.3.1",
|
|
47
|
+
"typescript": "^5.9.3"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# AGENTS.md — pi-devin-auth
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A [pi](https://pi.dev) coding agent extension that registers the `devin` provider (Cognition / Windsurf) with OAuth login and native streaming. Reuses the cloud-direct gRPC layer from `opencode-windsurf-auth`.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
extensions/index.ts # pi extension entry — registerProvider("devin", { oauth, streamSimple, models })
|
|
11
|
+
src/oauth/
|
|
12
|
+
login.ts # ADAPTED — pi OAuthLoginCallbacks (manual-paste flow)
|
|
13
|
+
register-user.ts # COPIED verbatim — RegisterUser RPC at register.windsurf.com
|
|
14
|
+
types.ts # COPIED verbatim — WindsurfRegion, DEFAULT_REGION
|
|
15
|
+
src/cloud-direct/ # COPIED verbatim — gRPC to server.codeium.com
|
|
16
|
+
auth.ts # GetUserJwt (short-lived JWT mint + in-memory cache)
|
|
17
|
+
chat.ts # GetChatMessage streaming (Connect-RPC + manual protobuf)
|
|
18
|
+
catalog.ts # GetCascadeModelConfigs (per-account model catalog)
|
|
19
|
+
metadata.ts # Metadata proto builder
|
|
20
|
+
wire.ts # Protobuf + Connect-streaming envelope helpers
|
|
21
|
+
index.ts # Public re-exports
|
|
22
|
+
src/context-map.ts # NEW — pi Message[]/Tool[] -> ChatHistoryItem[]/ToolDef[]
|
|
23
|
+
src/stream.ts # NEW — streamDevin: streamSimple impl (CloudChatEvent -> pi events)
|
|
24
|
+
src/models.ts # NEW — dynamic catalog -> ProviderModelConfig[] + fallback
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Build & Test
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install # install deps
|
|
31
|
+
npx tsc --noEmit # typecheck
|
|
32
|
+
npm test # run unit tests (bun test or node --test)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
pi loads extensions via jiti (no build step needed for runtime use).
|
|
36
|
+
|
|
37
|
+
## Key Design Decisions
|
|
38
|
+
|
|
39
|
+
1. **Native streamSimple** (Option B): No background proxy. `streamDevin()` calls `streamChatEvents()` directly and emits pi's `AssistantMessageEventStream` events.
|
|
40
|
+
2. **Manual-paste OAuth**: pi's `OAuthLoginCallbacks` doesn't support loopback servers. We use `redirect_uri=show-auth-token` so the Windsurf SPA renders the token for the user to paste via `callbacks.onPrompt()`.
|
|
41
|
+
3. **Non-expiring token shape**: Windsurf's `RegisterUser` returns a long-lived `api_key` with no refresh token. We set `OAuthCredentials = { refresh: "", access: apiKey, expires: now + 365 days }`. `refreshToken()` is a no-op.
|
|
42
|
+
4. **Dynamic model catalog**: Fetched from `GetCascadeModelConfigs` via `oauth.modifyModels()` after login. Static fallback for offline.
|
|
43
|
+
5. **In-memory JWT cache only**: No disk persistence for the short-lived `user_jwt`. Re-mint cost (~200ms) is negligible.
|
|
44
|
+
|
|
45
|
+
## Token Shapes
|
|
46
|
+
|
|
47
|
+
- **firebaseIdToken**: Short-lived JWT from Auth0 browser sign-in. Exchanged via RegisterUser, then discarded.
|
|
48
|
+
- **api_key**: Long-lived credential from RegisterUser. Format: `devin-session-token$<JWT>`. Used as `Metadata.api_key` in every gRPC call. Stored by pi in `~/.pi/agent/auth.json`.
|
|
49
|
+
- **user_jwt**: Short-lived (~24 min) JWT minted from `GetUserJwt`. Cached in-memory per (apiKey, host). Required alongside api_key for chat RPCs.
|
|
50
|
+
|
|
51
|
+
## Cloud-Direct gRPC
|
|
52
|
+
|
|
53
|
+
All RPCs hit `https://server.codeium.com` over HTTPS with Connect-RPC framing:
|
|
54
|
+
- `GetUserJwt` — unary, `application/proto`
|
|
55
|
+
- `GetCascadeModelConfigs` — unary, `application/proto`
|
|
56
|
+
- `GetChatMessage` — streaming, `application/connect+proto` (gzip-compressed frames)
|
|
57
|
+
|
|
58
|
+
Manual protobuf encoding (no protobuf library). Field numbers hardcoded from mitm captures of Windsurf's language_server traffic.
|
|
59
|
+
|
|
60
|
+
## Related
|
|
61
|
+
|
|
62
|
+
- [opencode-windsurf-auth](https://github.com/rsvedant/opencode-windsurf-auth) — the opencode plugin this is derived from
|
|
63
|
+
- [pi custom-provider docs](https://pi.dev/docs/latest/custom-provider)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nmzpy
|
|
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
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# pi-devin-auth
|
|
2
|
+
|
|
3
|
+
A [pi](https://pi.dev) coding agent extension that adds the **Devin** (Cognition / Windsurf) provider with browser-based OAuth login and native streaming.
|
|
4
|
+
|
|
5
|
+
## Installation in pi-ember-stack
|
|
6
|
+
|
|
7
|
+
This implementation is bundled by `@nmzpy/pi-ember-stack` and enabled through
|
|
8
|
+
the project's `.pi/ember-stack.json` plugin list. Toggle it with `/stack-plugins`
|
|
9
|
+
or remove `devin-auth` from that list, then restart pi.
|
|
10
|
+
|
|
11
|
+
### Manual / local development
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pi -e ./extensions/index.ts
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or copy `extensions/index.ts` into `~/.pi/agent/extensions/` for auto-discovery.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Login
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
/login devin
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This opens `https://windsurf.com/windsurf/signin` in your browser. After signing in, the page displays an auth token — paste it into the pi prompt. The extension exchanges it for a long-lived Devin API key via `register.windsurf.com`.
|
|
28
|
+
|
|
29
|
+
### Select a model
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
/model devin/swe-1-6
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Logout
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
/logout devin
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## How it works
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pi --login--> windsurf.com (Auth0) --token--> register.windsurf.com (RegisterUser) --api_key--> ~/.pi/agent/auth.json
|
|
45
|
+
pi --chat--> streamDevin() --> cloud-direct/streamChatEvents() --> server.codeium.com (GetChatMessage gRPC) --> pi events
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The extension reuses the battle-tested cloud-direct gRPC layer from [opencode-windsurf-auth](https://github.com/rsvedant/opencode-windsurf-auth) and wraps it in pi's native `streamSimple` + `oauth` extension API.
|
|
49
|
+
|
|
50
|
+
## Models
|
|
51
|
+
|
|
52
|
+
Models are fetched dynamically from Cognition's `GetCascadeModelConfigs` RPC after login, so the list always reflects what your account tier can access. A static fallback set is included for offline use.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi extension entry point for the Devin (Cognition) provider.
|
|
3
|
+
*
|
|
4
|
+
* Registers the `devin` provider with pi's ExtensionAPI, wiring up:
|
|
5
|
+
* - OAuth login via Windsurf's browser sign-in flow (`loginDevin`)
|
|
6
|
+
* - A no-op token refresh (Windsurf api_keys are long-lived)
|
|
7
|
+
* - Live model catalog fetch from Cognition's GetCascadeModelConfigs
|
|
8
|
+
* after login, filtered to 11 wanted model families
|
|
9
|
+
* - `/devin-refresh` command to manually re-fetch the catalog
|
|
10
|
+
* - `/devin-status` command to check auth state
|
|
11
|
+
* - `session_start` auto-fetch when already logged in
|
|
12
|
+
* - Streaming chat completions through Devin Cloud (`streamDevin`)
|
|
13
|
+
*
|
|
14
|
+
* The provider uses `streamSimple` — no background proxy. All routing
|
|
15
|
+
* and auth are handled internally via the OAuth-issued api_key.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
19
|
+
import type {
|
|
20
|
+
Api,
|
|
21
|
+
Model,
|
|
22
|
+
OAuthCredentials,
|
|
23
|
+
OAuthLoginCallbacks,
|
|
24
|
+
} from '@earendil-works/pi-ai';
|
|
25
|
+
import { streamDevin } from '../src/stream.js';
|
|
26
|
+
import { loginDevin } from '../src/oauth/login.js';
|
|
27
|
+
import { buildLiveModels, FALLBACK_MODELS, DEFAULT_HOST } from '../src/models.js';
|
|
28
|
+
import { getCachedCatalog, clearCachedCatalog } from '../src/cloud-direct/catalog.js';
|
|
29
|
+
import { DEFAULT_REGION } from '../src/oauth/types.js';
|
|
30
|
+
|
|
31
|
+
const PROVIDER_ID = 'devin';
|
|
32
|
+
const PROVIDER_NAME = 'Devin (Cognition)';
|
|
33
|
+
const OAUTH_NAME = 'Devin (Cognition / Windsurf)';
|
|
34
|
+
const API_IDENTIFIER = 'devin-cloud';
|
|
35
|
+
// pi requires baseUrl when models are defined, even with streamSimple.
|
|
36
|
+
// streamSimple ignores this — it routes internally — but the field must be present.
|
|
37
|
+
const PLACEHOLDER_BASE_URL = DEFAULT_HOST;
|
|
38
|
+
|
|
39
|
+
let _pi: ExtensionAPI | null = null;
|
|
40
|
+
|
|
41
|
+
function registerDevinProvider(pi: ExtensionAPI, models: typeof FALLBACK_MODELS): void {
|
|
42
|
+
pi.registerProvider(PROVIDER_ID, {
|
|
43
|
+
name: PROVIDER_NAME,
|
|
44
|
+
api: API_IDENTIFIER,
|
|
45
|
+
baseUrl: PLACEHOLDER_BASE_URL,
|
|
46
|
+
models,
|
|
47
|
+
oauth: {
|
|
48
|
+
name: OAUTH_NAME,
|
|
49
|
+
async login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {
|
|
50
|
+
const credentials = await loginDevin(callbacks, DEFAULT_REGION);
|
|
51
|
+
if (_pi) {
|
|
52
|
+
try {
|
|
53
|
+
clearCachedCatalog();
|
|
54
|
+
const catalog = await getCachedCatalog(
|
|
55
|
+
credentials.access,
|
|
56
|
+
DEFAULT_HOST,
|
|
57
|
+
);
|
|
58
|
+
const liveModels = buildLiveModels(catalog);
|
|
59
|
+
registerDevinProvider(_pi, liveModels);
|
|
60
|
+
} catch {
|
|
61
|
+
// keep static models if catalog fetch fails
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return credentials;
|
|
65
|
+
},
|
|
66
|
+
async refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
|
|
67
|
+
return credentials;
|
|
68
|
+
},
|
|
69
|
+
getApiKey(credentials: OAuthCredentials): string {
|
|
70
|
+
return credentials.access;
|
|
71
|
+
},
|
|
72
|
+
modifyModels(models: Model<Api>[]): Model<Api>[] {
|
|
73
|
+
return models;
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
streamSimple: streamDevin,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export default async function (pi: ExtensionAPI): Promise<void> {
|
|
81
|
+
_pi = pi;
|
|
82
|
+
|
|
83
|
+
registerDevinProvider(pi, FALLBACK_MODELS);
|
|
84
|
+
|
|
85
|
+
pi.on('session_start', async (_event, ctx) => {
|
|
86
|
+
try {
|
|
87
|
+
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(PROVIDER_ID);
|
|
88
|
+
if (apiKey && _pi) {
|
|
89
|
+
const catalog = await getCachedCatalog(apiKey, DEFAULT_HOST);
|
|
90
|
+
const liveModels = buildLiveModels(catalog);
|
|
91
|
+
registerDevinProvider(_pi, liveModels);
|
|
92
|
+
}
|
|
93
|
+
} catch {
|
|
94
|
+
// keep static models
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
pi.registerCommand('devin-refresh', {
|
|
99
|
+
description: 'Refresh Devin model catalog from Cognition',
|
|
100
|
+
handler: async (_args, ctx) => {
|
|
101
|
+
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(PROVIDER_ID);
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
ctx.ui.notify(
|
|
104
|
+
'Devin: not signed in. Run /login devin',
|
|
105
|
+
'warning',
|
|
106
|
+
);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
clearCachedCatalog();
|
|
110
|
+
try {
|
|
111
|
+
const catalog = await getCachedCatalog(apiKey, DEFAULT_HOST);
|
|
112
|
+
const liveModels = buildLiveModels(catalog);
|
|
113
|
+
registerDevinProvider(pi, liveModels);
|
|
114
|
+
ctx.ui.notify(
|
|
115
|
+
`Devin: refreshed ${liveModels.length} models.`,
|
|
116
|
+
'info',
|
|
117
|
+
);
|
|
118
|
+
} catch (e) {
|
|
119
|
+
ctx.ui.notify(
|
|
120
|
+
`Devin: refresh error - ${e instanceof Error ? e.message : String(e)}`,
|
|
121
|
+
'error',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
pi.registerCommand('devin-status', {
|
|
128
|
+
description: 'Show Devin auth status',
|
|
129
|
+
handler: async (_args, ctx) => {
|
|
130
|
+
const apiKey = await ctx.modelRegistry.getApiKeyForProvider(PROVIDER_ID);
|
|
131
|
+
ctx.ui.notify(
|
|
132
|
+
apiKey ? 'Devin: authenticated' : 'Devin: not signed in. Run /login devin',
|
|
133
|
+
apiKey ? 'info' : 'warning',
|
|
134
|
+
);
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
pi.on('session_shutdown', async () => {
|
|
139
|
+
_pi = null;
|
|
140
|
+
});
|
|
141
|
+
}
|