@nmzpy/pi-ember-stack 0.2.1 → 0.2.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/README.md +83 -83
- package/package.json +63 -59
- package/plugins/devin-auth/extensions/index.ts +23 -0
- package/plugins/devin-auth/src/cloud-direct/auth.ts +246 -246
- package/plugins/devin-auth/src/cloud-direct/catalog.ts +246 -246
- package/plugins/devin-auth/src/cloud-direct/chat.ts +1096 -1091
- package/plugins/devin-auth/src/cloud-direct/index.ts +41 -41
- package/plugins/devin-auth/src/cloud-direct/metadata.ts +78 -78
- package/plugins/devin-auth/src/cloud-direct/wire.ts +202 -202
- package/plugins/devin-auth/src/oauth/register-user.ts +174 -174
- package/plugins/devin-auth/src/oauth/types.ts +71 -71
- package/plugins/devin-auth/src/stream.ts +1 -1
- package/plugins/pi-compact-tools/index.ts +19 -1
- package/plugins/pi-compact-tools/renderer.ts +231 -61
- package/plugins/pi-custom-agents/index.ts +310 -102
- package/plugins/pi-custom-agents/questionnaire-tool.ts +14 -5
- package/plugins/pi-custom-agents/subagent/extensions/agents.ts +18 -1
- package/plugins/pi-custom-agents/subagent/extensions/index.ts +116 -224
- package/plugins/pi-custom-agents/subagent/extensions/model.ts +96 -96
- package/plugins/pi-custom-agents/subagent/extensions/render.ts +205 -1
- package/plugins/pi-custom-agents/subagent/extensions/runner.ts +241 -177
- package/plugins/pi-custom-agents/subagent/extensions/test/render.test.ts +145 -0
- package/plugins/pi-ember-fff/index.ts +275 -17
- package/plugins/pi-ember-fff/query.ts +170 -10
- package/plugins/pi-ember-fff/test/query.test.ts +157 -1
- package/plugins/pi-ember-fff/test/renderer.test.ts +367 -16
- package/plugins/pi-ember-tps/index.ts +27 -5
- package/plugins/pi-ember-ui/ember.json +3 -0
- package/plugins/pi-ember-ui/index.ts +223 -36
- package/plugins/pi-ember-ui/mode-colors.ts +36 -8
|
@@ -1,174 +1,174 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Exchange a Firebase ID token for a long-lived Windsurf API key.
|
|
3
|
-
*
|
|
4
|
-
* This calls the same Connect-RPC endpoint the Windsurf desktop extension uses
|
|
5
|
-
* after the browser sign-in completes:
|
|
6
|
-
*
|
|
7
|
-
* POST https://register.windsurf.com/exa.seat_management_pb.SeatManagementService/RegisterUser
|
|
8
|
-
* Content-Type: application/json
|
|
9
|
-
* Body: { "firebase_id_token": "<jwt>" }
|
|
10
|
-
*
|
|
11
|
-
* Connect-RPC happily accepts plain JSON over HTTPS (no gRPC framing required),
|
|
12
|
-
* so we skip @connectrpc/connect entirely and use `fetch`. The response shape
|
|
13
|
-
* matches `exa.seat_management_pb.RegisterUserResponse`:
|
|
14
|
-
*
|
|
15
|
-
* { api_key, name, api_server_url, redirect_url, team_options[] }
|
|
16
|
-
*
|
|
17
|
-
* Endpoint verified live (returns `{code:"unauthenticated",message:"invalid token ..."}`
|
|
18
|
-
* for a fake token, 200 with the response body for a valid one).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import type { OAuthLoginResult, WindsurfRegion } from './types.js';
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Polyfill for `AbortSignal.any` — composes multiple signals so the result
|
|
25
|
-
* aborts when ANY input aborts. Built-in in Node ≥20.3 / Bun ≥1.0; we
|
|
26
|
-
* implement the fallback ourselves so the timeout/caller-signal merge
|
|
27
|
-
* works on every runtime our `engines` field permits (Node 18+).
|
|
28
|
-
*/
|
|
29
|
-
function anySignal(signals: AbortSignal[]): AbortSignal {
|
|
30
|
-
const builtin = (AbortSignal as unknown as { any?: (s: AbortSignal[]) => AbortSignal }).any;
|
|
31
|
-
if (typeof builtin === 'function') return builtin(signals);
|
|
32
|
-
const controller = new AbortController();
|
|
33
|
-
const onAbort = (reason: unknown): void => {
|
|
34
|
-
if (!controller.signal.aborted) controller.abort(reason);
|
|
35
|
-
};
|
|
36
|
-
for (const s of signals) {
|
|
37
|
-
if (s.aborted) {
|
|
38
|
-
onAbort(s.reason);
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
s.addEventListener('abort', () => onAbort(s.reason), { once: true });
|
|
42
|
-
}
|
|
43
|
-
return controller.signal;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface RegisterUserResponseJson {
|
|
47
|
-
api_key?: string;
|
|
48
|
-
name?: string;
|
|
49
|
-
api_server_url?: string;
|
|
50
|
-
redirect_url?: string;
|
|
51
|
-
team_options?: unknown[];
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
interface ConnectErrorJson {
|
|
55
|
-
code?: string;
|
|
56
|
-
message?: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export class WindsurfRegistrationError extends Error {
|
|
60
|
-
readonly status: number;
|
|
61
|
-
readonly connectCode?: string;
|
|
62
|
-
readonly traceId?: string;
|
|
63
|
-
|
|
64
|
-
constructor(message: string, status: number, connectCode?: string, traceId?: string) {
|
|
65
|
-
super(message);
|
|
66
|
-
this.name = 'WindsurfRegistrationError';
|
|
67
|
-
this.status = status;
|
|
68
|
-
this.connectCode = connectCode;
|
|
69
|
-
this.traceId = traceId;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const TRACE_ID_RE = /\(trace ID: ([0-9a-f]+)\)/i;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Exchange the Firebase ID token for a Windsurf API key.
|
|
77
|
-
*
|
|
78
|
-
* `firebaseIdToken` is the `access_token` (or `firebase_id_token`) value the
|
|
79
|
-
* Windsurf sign-in page returns in the OAuth callback URL — we treat it as
|
|
80
|
-
* opaque.
|
|
81
|
-
*/
|
|
82
|
-
export async function registerUser(
|
|
83
|
-
firebaseIdToken: string,
|
|
84
|
-
region: WindsurfRegion,
|
|
85
|
-
abortSignal?: AbortSignal,
|
|
86
|
-
): Promise<OAuthLoginResult> {
|
|
87
|
-
if (!firebaseIdToken) {
|
|
88
|
-
throw new WindsurfRegistrationError('Empty firebase_id_token', 0, 'invalid_argument');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const url = `${region.registerApiServerUrl.replace(/\/$/, '')}/exa.seat_management_pb.SeatManagementService/RegisterUser`;
|
|
92
|
-
|
|
93
|
-
// 30s internal timeout — RegisterUser responds in ~200ms in steady state.
|
|
94
|
-
// CLI users on flaky networks need bounded waits or the sign-in command
|
|
95
|
-
// hangs forever. Compose with the caller's signal via a small polyfill
|
|
96
|
-
// (`anySignal`) because Node 18 / older Bun lack AbortSignal.any; the
|
|
97
|
-
// previous fallback `combinedSignal = abortSignal` would drop the
|
|
98
|
-
// timeout entirely on those runtimes.
|
|
99
|
-
const timeoutSignal = AbortSignal.timeout(30_000);
|
|
100
|
-
const combinedSignal: AbortSignal = abortSignal
|
|
101
|
-
? anySignal([abortSignal, timeoutSignal])
|
|
102
|
-
: timeoutSignal;
|
|
103
|
-
|
|
104
|
-
const response = await fetch(url, {
|
|
105
|
-
method: 'POST',
|
|
106
|
-
headers: {
|
|
107
|
-
'Content-Type': 'application/json',
|
|
108
|
-
// Connect protocol version header — not strictly required for JSON, but
|
|
109
|
-
// matches what the official Connect clients send and avoids accidental
|
|
110
|
-
// routing into a non-Connect HTTP handler.
|
|
111
|
-
'Connect-Protocol-Version': '1',
|
|
112
|
-
},
|
|
113
|
-
body: JSON.stringify({ firebase_id_token: firebaseIdToken }),
|
|
114
|
-
signal: combinedSignal,
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const text = await response.text();
|
|
118
|
-
|
|
119
|
-
if (!response.ok) {
|
|
120
|
-
let connectCode: string | undefined;
|
|
121
|
-
let message = text || `RegisterUser failed with HTTP ${response.status}`;
|
|
122
|
-
try {
|
|
123
|
-
const errJson = JSON.parse(text) as ConnectErrorJson;
|
|
124
|
-
connectCode = errJson.code;
|
|
125
|
-
if (errJson.message) message = errJson.message;
|
|
126
|
-
} catch {
|
|
127
|
-
// non-JSON error body — keep raw text in `message`
|
|
128
|
-
}
|
|
129
|
-
const traceMatch = message.match(TRACE_ID_RE);
|
|
130
|
-
throw new WindsurfRegistrationError(message, response.status, connectCode, traceMatch?.[1]);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
let parsed: RegisterUserResponseJson;
|
|
134
|
-
try {
|
|
135
|
-
parsed = JSON.parse(text) as RegisterUserResponseJson;
|
|
136
|
-
} catch {
|
|
137
|
-
throw new WindsurfRegistrationError(
|
|
138
|
-
`RegisterUser returned 200 but body is not JSON: ${text.slice(0, 200)}`,
|
|
139
|
-
response.status,
|
|
140
|
-
'internal',
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const apiKey = parsed.api_key;
|
|
145
|
-
const name = parsed.name;
|
|
146
|
-
// Empty `api_server_url` is normal for single-tenant accounts — the desktop
|
|
147
|
-
// extension's `getApiServerUrl` helper falls back to the configured default
|
|
148
|
-
// when this is empty/missing. We mirror that behavior here.
|
|
149
|
-
const apiServerUrl = parsed.api_server_url && parsed.api_server_url.length > 0
|
|
150
|
-
? parsed.api_server_url
|
|
151
|
-
: 'https://server.codeium.com';
|
|
152
|
-
|
|
153
|
-
if (!apiKey) {
|
|
154
|
-
throw new WindsurfRegistrationError(
|
|
155
|
-
'RegisterUser returned 200 but api_key was empty',
|
|
156
|
-
response.status,
|
|
157
|
-
'malformed_response',
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
if (!name) {
|
|
161
|
-
throw new WindsurfRegistrationError(
|
|
162
|
-
'RegisterUser returned 200 but name was empty',
|
|
163
|
-
response.status,
|
|
164
|
-
'malformed_response',
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
apiKey,
|
|
170
|
-
name,
|
|
171
|
-
apiServerUrl,
|
|
172
|
-
redirectUrl: parsed.redirect_url,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Exchange a Firebase ID token for a long-lived Windsurf API key.
|
|
3
|
+
*
|
|
4
|
+
* This calls the same Connect-RPC endpoint the Windsurf desktop extension uses
|
|
5
|
+
* after the browser sign-in completes:
|
|
6
|
+
*
|
|
7
|
+
* POST https://register.windsurf.com/exa.seat_management_pb.SeatManagementService/RegisterUser
|
|
8
|
+
* Content-Type: application/json
|
|
9
|
+
* Body: { "firebase_id_token": "<jwt>" }
|
|
10
|
+
*
|
|
11
|
+
* Connect-RPC happily accepts plain JSON over HTTPS (no gRPC framing required),
|
|
12
|
+
* so we skip @connectrpc/connect entirely and use `fetch`. The response shape
|
|
13
|
+
* matches `exa.seat_management_pb.RegisterUserResponse`:
|
|
14
|
+
*
|
|
15
|
+
* { api_key, name, api_server_url, redirect_url, team_options[] }
|
|
16
|
+
*
|
|
17
|
+
* Endpoint verified live (returns `{code:"unauthenticated",message:"invalid token ..."}`
|
|
18
|
+
* for a fake token, 200 with the response body for a valid one).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { OAuthLoginResult, WindsurfRegion } from './types.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Polyfill for `AbortSignal.any` — composes multiple signals so the result
|
|
25
|
+
* aborts when ANY input aborts. Built-in in Node ≥20.3 / Bun ≥1.0; we
|
|
26
|
+
* implement the fallback ourselves so the timeout/caller-signal merge
|
|
27
|
+
* works on every runtime our `engines` field permits (Node 18+).
|
|
28
|
+
*/
|
|
29
|
+
function anySignal(signals: AbortSignal[]): AbortSignal {
|
|
30
|
+
const builtin = (AbortSignal as unknown as { any?: (s: AbortSignal[]) => AbortSignal }).any;
|
|
31
|
+
if (typeof builtin === 'function') return builtin(signals);
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const onAbort = (reason: unknown): void => {
|
|
34
|
+
if (!controller.signal.aborted) controller.abort(reason);
|
|
35
|
+
};
|
|
36
|
+
for (const s of signals) {
|
|
37
|
+
if (s.aborted) {
|
|
38
|
+
onAbort(s.reason);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
s.addEventListener('abort', () => onAbort(s.reason), { once: true });
|
|
42
|
+
}
|
|
43
|
+
return controller.signal;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface RegisterUserResponseJson {
|
|
47
|
+
api_key?: string;
|
|
48
|
+
name?: string;
|
|
49
|
+
api_server_url?: string;
|
|
50
|
+
redirect_url?: string;
|
|
51
|
+
team_options?: unknown[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ConnectErrorJson {
|
|
55
|
+
code?: string;
|
|
56
|
+
message?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class WindsurfRegistrationError extends Error {
|
|
60
|
+
readonly status: number;
|
|
61
|
+
readonly connectCode?: string;
|
|
62
|
+
readonly traceId?: string;
|
|
63
|
+
|
|
64
|
+
constructor(message: string, status: number, connectCode?: string, traceId?: string) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.name = 'WindsurfRegistrationError';
|
|
67
|
+
this.status = status;
|
|
68
|
+
this.connectCode = connectCode;
|
|
69
|
+
this.traceId = traceId;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const TRACE_ID_RE = /\(trace ID: ([0-9a-f]+)\)/i;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Exchange the Firebase ID token for a Windsurf API key.
|
|
77
|
+
*
|
|
78
|
+
* `firebaseIdToken` is the `access_token` (or `firebase_id_token`) value the
|
|
79
|
+
* Windsurf sign-in page returns in the OAuth callback URL — we treat it as
|
|
80
|
+
* opaque.
|
|
81
|
+
*/
|
|
82
|
+
export async function registerUser(
|
|
83
|
+
firebaseIdToken: string,
|
|
84
|
+
region: WindsurfRegion,
|
|
85
|
+
abortSignal?: AbortSignal,
|
|
86
|
+
): Promise<OAuthLoginResult> {
|
|
87
|
+
if (!firebaseIdToken) {
|
|
88
|
+
throw new WindsurfRegistrationError('Empty firebase_id_token', 0, 'invalid_argument');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const url = `${region.registerApiServerUrl.replace(/\/$/, '')}/exa.seat_management_pb.SeatManagementService/RegisterUser`;
|
|
92
|
+
|
|
93
|
+
// 30s internal timeout — RegisterUser responds in ~200ms in steady state.
|
|
94
|
+
// CLI users on flaky networks need bounded waits or the sign-in command
|
|
95
|
+
// hangs forever. Compose with the caller's signal via a small polyfill
|
|
96
|
+
// (`anySignal`) because Node 18 / older Bun lack AbortSignal.any; the
|
|
97
|
+
// previous fallback `combinedSignal = abortSignal` would drop the
|
|
98
|
+
// timeout entirely on those runtimes.
|
|
99
|
+
const timeoutSignal = AbortSignal.timeout(30_000);
|
|
100
|
+
const combinedSignal: AbortSignal = abortSignal
|
|
101
|
+
? anySignal([abortSignal, timeoutSignal])
|
|
102
|
+
: timeoutSignal;
|
|
103
|
+
|
|
104
|
+
const response = await fetch(url, {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
// Connect protocol version header — not strictly required for JSON, but
|
|
109
|
+
// matches what the official Connect clients send and avoids accidental
|
|
110
|
+
// routing into a non-Connect HTTP handler.
|
|
111
|
+
'Connect-Protocol-Version': '1',
|
|
112
|
+
},
|
|
113
|
+
body: JSON.stringify({ firebase_id_token: firebaseIdToken }),
|
|
114
|
+
signal: combinedSignal,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const text = await response.text();
|
|
118
|
+
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
let connectCode: string | undefined;
|
|
121
|
+
let message = text || `RegisterUser failed with HTTP ${response.status}`;
|
|
122
|
+
try {
|
|
123
|
+
const errJson = JSON.parse(text) as ConnectErrorJson;
|
|
124
|
+
connectCode = errJson.code;
|
|
125
|
+
if (errJson.message) message = errJson.message;
|
|
126
|
+
} catch {
|
|
127
|
+
// non-JSON error body — keep raw text in `message`
|
|
128
|
+
}
|
|
129
|
+
const traceMatch = message.match(TRACE_ID_RE);
|
|
130
|
+
throw new WindsurfRegistrationError(message, response.status, connectCode, traceMatch?.[1]);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let parsed: RegisterUserResponseJson;
|
|
134
|
+
try {
|
|
135
|
+
parsed = JSON.parse(text) as RegisterUserResponseJson;
|
|
136
|
+
} catch {
|
|
137
|
+
throw new WindsurfRegistrationError(
|
|
138
|
+
`RegisterUser returned 200 but body is not JSON: ${text.slice(0, 200)}`,
|
|
139
|
+
response.status,
|
|
140
|
+
'internal',
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const apiKey = parsed.api_key;
|
|
145
|
+
const name = parsed.name;
|
|
146
|
+
// Empty `api_server_url` is normal for single-tenant accounts — the desktop
|
|
147
|
+
// extension's `getApiServerUrl` helper falls back to the configured default
|
|
148
|
+
// when this is empty/missing. We mirror that behavior here.
|
|
149
|
+
const apiServerUrl = parsed.api_server_url && parsed.api_server_url.length > 0
|
|
150
|
+
? parsed.api_server_url
|
|
151
|
+
: 'https://server.codeium.com';
|
|
152
|
+
|
|
153
|
+
if (!apiKey) {
|
|
154
|
+
throw new WindsurfRegistrationError(
|
|
155
|
+
'RegisterUser returned 200 but api_key was empty',
|
|
156
|
+
response.status,
|
|
157
|
+
'malformed_response',
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (!name) {
|
|
161
|
+
throw new WindsurfRegistrationError(
|
|
162
|
+
'RegisterUser returned 200 but name was empty',
|
|
163
|
+
response.status,
|
|
164
|
+
'malformed_response',
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
apiKey,
|
|
170
|
+
name,
|
|
171
|
+
apiServerUrl,
|
|
172
|
+
redirectUrl: parsed.redirect_url,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared types for the OAuth login flow + persisted credentials.
|
|
3
|
-
*
|
|
4
|
-
* Two distinct token shapes appear in this codebase:
|
|
5
|
-
*
|
|
6
|
-
* - `firebaseIdToken` — the short-lived JWT minted by Auth0 / Firebase Auth
|
|
7
|
-
* during browser sign-in. Lives in the OAuth callback URL fragment/query.
|
|
8
|
-
* Treated as opaque and discarded once exchanged.
|
|
9
|
-
*
|
|
10
|
-
* - `apiKey` — the long-lived credential returned by
|
|
11
|
-
* `SeatManagementService.RegisterUser`. Used inside every Cascade RPC's
|
|
12
|
-
* `Metadata.api_key` field. Format is provider-defined:
|
|
13
|
-
* * Cognition era: `devin-session-token$<JWT>`
|
|
14
|
-
* * Codeium classic: bare UUID v4
|
|
15
|
-
* * Older Windsurf: `sk-ws-01-<...>` / `cog_<...>`
|
|
16
|
-
* The plugin treats it as an opaque string — only the cloud cares about format.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
export interface OAuthLoginResult {
|
|
20
|
-
/** The opaque API key used as `Metadata.api_key` in every Cascade RPC. */
|
|
21
|
-
apiKey: string;
|
|
22
|
-
/** Human-readable account name (`Satvik Kapoor`). */
|
|
23
|
-
name: string;
|
|
24
|
-
/**
|
|
25
|
-
* Cloud API server (`https://server.codeium.com`, `https://eu.windsurf.com/_route/api_server`,
|
|
26
|
-
* `https://windsurf.fedstart.com/_route/api_server`). Driven by the user's
|
|
27
|
-
* tenant — language_server needs this as `--api_server_url`.
|
|
28
|
-
*/
|
|
29
|
-
apiServerUrl: string;
|
|
30
|
-
/** Optional cleanup redirect URL returned by RegisterUser. Informational. */
|
|
31
|
-
redirectUrl?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface PersistedCredentials extends OAuthLoginResult {
|
|
35
|
-
/** ISO timestamp the credentials were minted at — purely informational. */
|
|
36
|
-
issuedAt: string;
|
|
37
|
-
/** Optional tag tracking the OAuth client id used (so a future client rotation can invalidate). */
|
|
38
|
-
oauthClientId: string;
|
|
39
|
-
/**
|
|
40
|
-
* True when these credentials were written as part of the
|
|
41
|
-
* `opencode auth login` → authorize() flow (so opencode's auth.json is the
|
|
42
|
-
* authoritative copy and `opencode auth logout windsurf` should mirror-clear
|
|
43
|
-
* this file). False / absent for credentials written by our standalone
|
|
44
|
-
* `opencode-windsurf-auth login` CLI; those survive opencode auth state
|
|
45
|
-
* changes.
|
|
46
|
-
*/
|
|
47
|
-
syncedViaOpencodeAuth?: boolean;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface WindsurfRegion {
|
|
51
|
-
/** Where to send users for browser sign-in. */
|
|
52
|
-
website: string;
|
|
53
|
-
/** Where to POST RegisterUser. */
|
|
54
|
-
registerApiServerUrl: string;
|
|
55
|
-
/** Auth0 client id passed in the OAuth URL. */
|
|
56
|
-
oauthClientId: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* The single tenant (free / personal) configuration. EU, FedStart, and arbitrary
|
|
61
|
-
* portal URLs override `website` + `registerApiServerUrl` at runtime when the
|
|
62
|
-
* user passes `--portal-url` to the login command.
|
|
63
|
-
*/
|
|
64
|
-
export const DEFAULT_REGION: WindsurfRegion = {
|
|
65
|
-
website: 'https://windsurf.com',
|
|
66
|
-
registerApiServerUrl: 'https://register.windsurf.com',
|
|
67
|
-
// From /Applications/Windsurf.app/.../extension.js — the public Windsurf
|
|
68
|
-
// Auth0 client. If Windsurf rotates this, sign-in will start failing until
|
|
69
|
-
// we re-extract it.
|
|
70
|
-
oauthClientId: '3GUryQ7ldAeKEuD2obYnppsnmj58eP5u',
|
|
71
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the OAuth login flow + persisted credentials.
|
|
3
|
+
*
|
|
4
|
+
* Two distinct token shapes appear in this codebase:
|
|
5
|
+
*
|
|
6
|
+
* - `firebaseIdToken` — the short-lived JWT minted by Auth0 / Firebase Auth
|
|
7
|
+
* during browser sign-in. Lives in the OAuth callback URL fragment/query.
|
|
8
|
+
* Treated as opaque and discarded once exchanged.
|
|
9
|
+
*
|
|
10
|
+
* - `apiKey` — the long-lived credential returned by
|
|
11
|
+
* `SeatManagementService.RegisterUser`. Used inside every Cascade RPC's
|
|
12
|
+
* `Metadata.api_key` field. Format is provider-defined:
|
|
13
|
+
* * Cognition era: `devin-session-token$<JWT>`
|
|
14
|
+
* * Codeium classic: bare UUID v4
|
|
15
|
+
* * Older Windsurf: `sk-ws-01-<...>` / `cog_<...>`
|
|
16
|
+
* The plugin treats it as an opaque string — only the cloud cares about format.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface OAuthLoginResult {
|
|
20
|
+
/** The opaque API key used as `Metadata.api_key` in every Cascade RPC. */
|
|
21
|
+
apiKey: string;
|
|
22
|
+
/** Human-readable account name (`Satvik Kapoor`). */
|
|
23
|
+
name: string;
|
|
24
|
+
/**
|
|
25
|
+
* Cloud API server (`https://server.codeium.com`, `https://eu.windsurf.com/_route/api_server`,
|
|
26
|
+
* `https://windsurf.fedstart.com/_route/api_server`). Driven by the user's
|
|
27
|
+
* tenant — language_server needs this as `--api_server_url`.
|
|
28
|
+
*/
|
|
29
|
+
apiServerUrl: string;
|
|
30
|
+
/** Optional cleanup redirect URL returned by RegisterUser. Informational. */
|
|
31
|
+
redirectUrl?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface PersistedCredentials extends OAuthLoginResult {
|
|
35
|
+
/** ISO timestamp the credentials were minted at — purely informational. */
|
|
36
|
+
issuedAt: string;
|
|
37
|
+
/** Optional tag tracking the OAuth client id used (so a future client rotation can invalidate). */
|
|
38
|
+
oauthClientId: string;
|
|
39
|
+
/**
|
|
40
|
+
* True when these credentials were written as part of the
|
|
41
|
+
* `opencode auth login` → authorize() flow (so opencode's auth.json is the
|
|
42
|
+
* authoritative copy and `opencode auth logout windsurf` should mirror-clear
|
|
43
|
+
* this file). False / absent for credentials written by our standalone
|
|
44
|
+
* `opencode-windsurf-auth login` CLI; those survive opencode auth state
|
|
45
|
+
* changes.
|
|
46
|
+
*/
|
|
47
|
+
syncedViaOpencodeAuth?: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface WindsurfRegion {
|
|
51
|
+
/** Where to send users for browser sign-in. */
|
|
52
|
+
website: string;
|
|
53
|
+
/** Where to POST RegisterUser. */
|
|
54
|
+
registerApiServerUrl: string;
|
|
55
|
+
/** Auth0 client id passed in the OAuth URL. */
|
|
56
|
+
oauthClientId: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The single tenant (free / personal) configuration. EU, FedStart, and arbitrary
|
|
61
|
+
* portal URLs override `website` + `registerApiServerUrl` at runtime when the
|
|
62
|
+
* user passes `--portal-url` to the login command.
|
|
63
|
+
*/
|
|
64
|
+
export const DEFAULT_REGION: WindsurfRegion = {
|
|
65
|
+
website: 'https://windsurf.com',
|
|
66
|
+
registerApiServerUrl: 'https://register.windsurf.com',
|
|
67
|
+
// From /Applications/Windsurf.app/.../extension.js — the public Windsurf
|
|
68
|
+
// Auth0 client. If Windsurf rotates this, sign-in will start failing until
|
|
69
|
+
// we re-extract it.
|
|
70
|
+
oauthClientId: '3GUryQ7ldAeKEuD2obYnppsnmj58eP5u',
|
|
71
|
+
};
|
|
@@ -265,7 +265,7 @@ export function streamDevin(
|
|
|
265
265
|
output.usage.cacheRead = ev.cachedInputTokens ?? 0;
|
|
266
266
|
output.usage.cacheWrite = ev.cacheCreationInputTokens ?? 0;
|
|
267
267
|
output.usage.totalTokens =
|
|
268
|
-
|
|
268
|
+
output.usage.input + output.usage.output + output.usage.cacheRead + output.usage.cacheWrite;
|
|
269
269
|
// calculateCost mutates output.usage.cost in place.
|
|
270
270
|
calculateCost(model, output.usage);
|
|
271
271
|
break;
|
|
@@ -14,6 +14,7 @@ import { type Component } from "@earendil-works/pi-tui";
|
|
|
14
14
|
import {
|
|
15
15
|
CompactRenderer,
|
|
16
16
|
GROUPABLE_TOOLS,
|
|
17
|
+
bashGrepInfo,
|
|
17
18
|
type ToolRenderContext,
|
|
18
19
|
type ToolRenderResultOptions,
|
|
19
20
|
} from "./renderer.ts";
|
|
@@ -73,6 +74,8 @@ function registerCompactTool(
|
|
|
73
74
|
|
|
74
75
|
let sharedRenderer: CompactRenderer | null = null;
|
|
75
76
|
|
|
77
|
+
export { bashGrepInfo };
|
|
78
|
+
|
|
76
79
|
export function getSharedRenderer(): CompactRenderer {
|
|
77
80
|
if (!sharedRenderer) sharedRenderer = new CompactRenderer();
|
|
78
81
|
return sharedRenderer;
|
|
@@ -81,11 +84,26 @@ export function getSharedRenderer(): CompactRenderer {
|
|
|
81
84
|
export default function piCompactToolsPlugin(pi: ExtensionAPI): void {
|
|
82
85
|
const renderer = getSharedRenderer();
|
|
83
86
|
pi.on("turn_start", () => renderer.beginTurn());
|
|
87
|
+
pi.on("message_update", (event: any) => {
|
|
88
|
+
// When the model streams visible text (not just thinking tokens),
|
|
89
|
+
// mark the turn as having visible output so the next discovery
|
|
90
|
+
// call starts a fresh group instead of appending to the previous
|
|
91
|
+
// turn's group. Thinking-only turns keep grouping coherent.
|
|
92
|
+
const ev = event?.assistantMessageEvent;
|
|
93
|
+
if (ev && (ev.type === "text_start" || ev.type === "text_delta")) {
|
|
94
|
+
renderer.noteVisibleText();
|
|
95
|
+
}
|
|
96
|
+
});
|
|
84
97
|
pi.on("tool_call", (event: any) => {
|
|
85
98
|
if (GROUPABLE_TOOLS.has(event.toolName) || TOOL_FACTORIES[event.toolName]) {
|
|
86
|
-
renderer.
|
|
99
|
+
renderer.registerCall(event.toolName, event.toolCallId, event.input);
|
|
87
100
|
}
|
|
88
101
|
});
|
|
102
|
+
// Reset the shared renderer on session replacement so stale call rows
|
|
103
|
+
// from the previous session do not leak into the new one. The renderer
|
|
104
|
+
// is module-level (shared across sessions because jiti caches the
|
|
105
|
+
// module), so it must be explicitly cleared.
|
|
106
|
+
pi.on("session_start", () => renderer.resetForSession());
|
|
89
107
|
for (const [name, factory] of Object.entries(TOOL_FACTORIES)) {
|
|
90
108
|
registerCompactTool(pi, name, factory, renderer);
|
|
91
109
|
}
|