@tt-a1i/openpi 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +116 -46
- package/SETUP.md +29 -7
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +155 -0
- package/extensions/ai-providers/LICENSE.upstream +23 -0
- package/extensions/ai-providers/README.md +59 -0
- package/extensions/ai-providers/antigravity/credentials.ts +52 -0
- package/extensions/ai-providers/antigravity/discovery.ts +130 -0
- package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
- package/extensions/ai-providers/antigravity/models.ts +84 -0
- package/extensions/ai-providers/antigravity/oauth.ts +700 -0
- package/extensions/ai-providers/antigravity/provider.ts +1116 -0
- package/extensions/ai-providers/antigravity/routing.ts +340 -0
- package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
- package/extensions/ai-providers/cursor/constants.ts +5 -0
- package/extensions/ai-providers/cursor/credentials.ts +14 -0
- package/extensions/ai-providers/cursor/discovery.ts +291 -0
- package/extensions/ai-providers/cursor/input-images.ts +106 -0
- package/extensions/ai-providers/cursor/models.ts +45 -0
- package/extensions/ai-providers/cursor/oauth.ts +263 -0
- package/extensions/ai-providers/cursor/proto.ts +1064 -0
- package/extensions/ai-providers/cursor/protobuf.ts +1171 -0
- package/extensions/ai-providers/cursor/provider.ts +1175 -0
- package/extensions/ai-providers/cursor/proxy.ts +213 -0
- package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
- package/extensions/ai-providers/index.ts +86 -0
- package/extensions/ai-providers/oauth-adapter.ts +81 -0
- package/extensions/ai-providers/usage.ts +10 -0
- package/extensions/background-terminals/index.ts +38 -3
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +484 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +13 -5
- package/extensions/background-terminals/src/result-delivery.ts +47 -24
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/index.ts +68 -27
- package/extensions/cron/schedule.ts +12 -2
- package/extensions/file-mutation-display/render.ts +17 -257
- package/extensions/file-search/src/binaries.ts +57 -41
- package/extensions/git-read/index.ts +1 -3
- package/extensions/model-info/cache-diagnostics.ts +220 -0
- package/extensions/model-info/index.ts +65 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +82 -6
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +153 -86
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +138 -130
- package/extensions/shared/activity-status.ts +30 -0
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/capability-intent.ts +1 -1
- package/extensions/shared/child-session.ts +457 -21
- package/extensions/shared/completion-inbox.ts +193 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/setup-config.ts +83 -34
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/structured-output.ts +154 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +72 -15
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +29 -2
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +313 -62
- package/extensions/subagents/navigation.ts +34 -5
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +450 -70
- package/extensions/subagents/src/domain.ts +21 -1
- package/extensions/subagents/src/manager.ts +39 -2
- package/extensions/subagents/src/prompt.ts +49 -7
- package/extensions/subagents/src/result-artifact.ts +36 -0
- package/extensions/subagents/src/result-delivery.ts +39 -14
- package/extensions/subagents/src/runtime.ts +15 -1
- package/extensions/subagents/src/ui/takeover.ts +73 -257
- package/extensions/subagents/src/ui/transcript.ts +38 -535
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/ui-customization/footer.ts +16 -45
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +42 -6
- package/extensions/web/index.ts +257 -0
- package/extensions/workflows/acceptance.ts +43 -19
- package/extensions/workflows/artifacts.ts +137 -47
- package/extensions/workflows/completion-projection.ts +459 -0
- package/extensions/workflows/coordinator.ts +8 -10
- package/extensions/workflows/dashboard.ts +175 -228
- package/extensions/workflows/handoff.ts +70 -16
- package/extensions/workflows/index.ts +501 -198
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +79 -5
- package/extensions/workflows/navigation.ts +32 -8
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +70 -16
- package/extensions/workflows/replay-safety.ts +42 -21
- package/extensions/workflows/result-delivery.ts +214 -76
- package/extensions/workflows/retention.ts +599 -0
- package/extensions/workflows/runner.ts +389 -345
- package/extensions/workflows/sandbox-child.cjs +25 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +34 -14
- package/skills/subagents/REFERENCE.md +190 -0
- package/skills/subagents/SKILL.md +2 -1
- package/skills/workflows/REFERENCE.md +6 -4
- package/skills/workflows/SKILL.md +1 -1
- package/web/adapter/pi-adapter.ts +664 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/pi-coding-agent-entry.ts +162 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +1069 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +467 -0
- package/web/runtime/pi-runtime.ts +1206 -0
- package/web/runtime/types.ts +102 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1700 -0
- package/web/ui/index.html +142 -0
- package/web/ui/styles.css +680 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -303
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import * as http2 from "node:http2";
|
|
2
|
+
import * as net from "node:net";
|
|
3
|
+
import * as tls from "node:tls";
|
|
4
|
+
|
|
5
|
+
export interface CursorHttp2ConnectOptions {
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function isLocalOrMetadataHost(hostname: string): boolean {
|
|
11
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
12
|
+
if (
|
|
13
|
+
host === "localhost" ||
|
|
14
|
+
host.endsWith(".localhost") ||
|
|
15
|
+
host === "metadata.google.internal"
|
|
16
|
+
) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (host === "::" || host === "::1" || /^f[cd]/.test(host)) return true;
|
|
20
|
+
const ipv4 = /^(\d{1,3})\.(\d{1,3})\./.exec(host);
|
|
21
|
+
if (!ipv4) return false;
|
|
22
|
+
const first = Number(ipv4[1]);
|
|
23
|
+
const second = Number(ipv4[2]);
|
|
24
|
+
return (
|
|
25
|
+
first === 0 ||
|
|
26
|
+
first === 10 ||
|
|
27
|
+
first === 127 ||
|
|
28
|
+
(first === 169 && second === 254) ||
|
|
29
|
+
(first === 172 && second >= 16 && second <= 31) ||
|
|
30
|
+
(first === 192 && second === 168)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function shouldBypassProxy(target: URL): boolean {
|
|
35
|
+
if (isLocalOrMetadataHost(target.hostname)) return true;
|
|
36
|
+
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
|
|
37
|
+
if (!noProxy) return false;
|
|
38
|
+
const targetHost = target.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
39
|
+
const targetPort =
|
|
40
|
+
target.port || (target.protocol === "https:" ? "443" : "80");
|
|
41
|
+
for (const rawRule of noProxy.split(/[,\s]+/)) {
|
|
42
|
+
let rule = rawRule.trim().toLowerCase();
|
|
43
|
+
if (!rule) continue;
|
|
44
|
+
if (rule === "*") return true;
|
|
45
|
+
let rulePort: string | undefined;
|
|
46
|
+
const portMatch = /^(\[[^\]]+\]|[^:]+):(\d+)$/.exec(rule);
|
|
47
|
+
if (portMatch) {
|
|
48
|
+
rule = portMatch[1]!;
|
|
49
|
+
rulePort = portMatch[2];
|
|
50
|
+
}
|
|
51
|
+
if (rulePort && rulePort !== targetPort) continue;
|
|
52
|
+
rule = rule.replace(/^\[|\]$/g, "").replace(/^\./, "");
|
|
53
|
+
if (targetHost === rule || targetHost.endsWith(`.${rule}`)) return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Resolve Cursor's provider override first, then the standard proxy variables. */
|
|
59
|
+
export function resolveCursorProxy(target: URL): string | undefined {
|
|
60
|
+
if (shouldBypassProxy(target)) return undefined;
|
|
61
|
+
const protocolProxy =
|
|
62
|
+
target.protocol === "https:"
|
|
63
|
+
? process.env.HTTPS_PROXY || process.env.https_proxy
|
|
64
|
+
: process.env.HTTP_PROXY || process.env.http_proxy;
|
|
65
|
+
return [
|
|
66
|
+
process.env.PI_PROXY_CURSOR,
|
|
67
|
+
process.env.PI_PROXY,
|
|
68
|
+
protocolProxy,
|
|
69
|
+
process.env.ALL_PROXY || process.env.all_proxy,
|
|
70
|
+
]
|
|
71
|
+
.map((value) => value?.trim())
|
|
72
|
+
.find((value): value is string => Boolean(value));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function connectProxyTunnel(
|
|
76
|
+
proxyUrl: URL,
|
|
77
|
+
targetUrl: URL,
|
|
78
|
+
options: CursorHttp2ConnectOptions,
|
|
79
|
+
): Promise<net.Socket> {
|
|
80
|
+
if (!["http:", "https:"].includes(proxyUrl.protocol)) {
|
|
81
|
+
return Promise.reject(
|
|
82
|
+
new Error(`Unsupported Cursor proxy protocol: ${proxyUrl.protocol}`),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (options.signal?.aborted) {
|
|
86
|
+
return Promise.reject(new Error("Cursor proxy tunnel aborted"));
|
|
87
|
+
}
|
|
88
|
+
const proxyTls = proxyUrl.protocol === "https:";
|
|
89
|
+
const proxyPort = Number(proxyUrl.port || (proxyTls ? 443 : 80));
|
|
90
|
+
const targetPort = Number(
|
|
91
|
+
targetUrl.port || (targetUrl.protocol === "https:" ? 443 : 80),
|
|
92
|
+
);
|
|
93
|
+
const targetAuthority = `${targetUrl.hostname}:${targetPort}`;
|
|
94
|
+
let proxyAuthorization: string | undefined;
|
|
95
|
+
if (proxyUrl.username || proxyUrl.password) {
|
|
96
|
+
try {
|
|
97
|
+
const credentials = `${decodeURIComponent(proxyUrl.username)}:${decodeURIComponent(proxyUrl.password)}`;
|
|
98
|
+
proxyAuthorization = Buffer.from(credentials).toString("base64");
|
|
99
|
+
} catch (cause) {
|
|
100
|
+
return Promise.reject(
|
|
101
|
+
new Error("Cursor proxy credentials contain invalid percent-encoding", {
|
|
102
|
+
cause,
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const { promise, resolve, reject } = Promise.withResolvers<net.Socket>();
|
|
108
|
+
let rawSocket: net.Socket | undefined;
|
|
109
|
+
let targetSocket: net.Socket | undefined;
|
|
110
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
111
|
+
let response = Buffer.alloc(0);
|
|
112
|
+
let settled = false;
|
|
113
|
+
|
|
114
|
+
const cleanup = () => {
|
|
115
|
+
if (timer) clearTimeout(timer);
|
|
116
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
117
|
+
rawSocket?.removeListener("error", onError);
|
|
118
|
+
rawSocket?.removeListener(proxyTls ? "secureConnect" : "connect", onReady);
|
|
119
|
+
rawSocket?.removeListener("data", onData);
|
|
120
|
+
targetSocket?.removeListener("error", onError);
|
|
121
|
+
targetSocket?.removeListener("secureConnect", onTargetReady);
|
|
122
|
+
};
|
|
123
|
+
const fail = (error: Error) => {
|
|
124
|
+
if (settled) return;
|
|
125
|
+
settled = true;
|
|
126
|
+
cleanup();
|
|
127
|
+
targetSocket?.destroy();
|
|
128
|
+
rawSocket?.destroy();
|
|
129
|
+
reject(error);
|
|
130
|
+
};
|
|
131
|
+
const succeed = (socket: net.Socket) => {
|
|
132
|
+
if (settled) return;
|
|
133
|
+
settled = true;
|
|
134
|
+
cleanup();
|
|
135
|
+
resolve(socket);
|
|
136
|
+
};
|
|
137
|
+
const onAbort = () => fail(new Error("Cursor proxy tunnel aborted"));
|
|
138
|
+
const onError = (error: Error) => fail(error);
|
|
139
|
+
const onTargetReady = () => {
|
|
140
|
+
if (targetSocket) succeed(targetSocket);
|
|
141
|
+
};
|
|
142
|
+
const onData = (chunk: Buffer) => {
|
|
143
|
+
if (!rawSocket) return;
|
|
144
|
+
response = Buffer.concat([response, chunk]);
|
|
145
|
+
if (response.length > 64 * 1024) {
|
|
146
|
+
fail(new Error("Cursor proxy response headers exceed 64 KiB"));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const headerEnd = response.indexOf("\r\n\r\n");
|
|
150
|
+
if (headerEnd === -1) return;
|
|
151
|
+
const statusLine = response
|
|
152
|
+
.subarray(0, headerEnd)
|
|
153
|
+
.toString("latin1")
|
|
154
|
+
.split("\r\n")[0];
|
|
155
|
+
if (!/^HTTP\/1\.[01] 200\b/.test(statusLine ?? "")) {
|
|
156
|
+
fail(
|
|
157
|
+
new Error(
|
|
158
|
+
`Cursor proxy tunnel failed: ${statusLine || "invalid response"}`,
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
rawSocket.removeListener("data", onData);
|
|
164
|
+
if (targetUrl.protocol !== "https:") {
|
|
165
|
+
succeed(rawSocket);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
targetSocket = tls.connect({
|
|
169
|
+
socket: rawSocket,
|
|
170
|
+
servername: targetUrl.hostname,
|
|
171
|
+
ALPNProtocols: ["h2"],
|
|
172
|
+
});
|
|
173
|
+
targetSocket.once("error", onError);
|
|
174
|
+
targetSocket.once("secureConnect", onTargetReady);
|
|
175
|
+
};
|
|
176
|
+
const onReady = () => {
|
|
177
|
+
if (!rawSocket) return;
|
|
178
|
+
let request = `CONNECT ${targetAuthority} HTTP/1.1\r\nHost: ${targetAuthority}\r\n`;
|
|
179
|
+
if (proxyAuthorization) {
|
|
180
|
+
request += `Proxy-Authorization: Basic ${proxyAuthorization}\r\n`;
|
|
181
|
+
}
|
|
182
|
+
rawSocket.on("data", onData);
|
|
183
|
+
rawSocket.write(`${request}\r\n`);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
187
|
+
if (options.timeoutMs !== undefined && options.timeoutMs > 0) {
|
|
188
|
+
const timeoutMs = Math.floor(options.timeoutMs);
|
|
189
|
+
timer = setTimeout(
|
|
190
|
+
() =>
|
|
191
|
+
fail(new Error(`Cursor proxy tunnel timed out after ${timeoutMs}ms`)),
|
|
192
|
+
timeoutMs,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
rawSocket = proxyTls
|
|
196
|
+
? tls.connect({ host: proxyUrl.hostname, port: proxyPort })
|
|
197
|
+
: net.connect({ host: proxyUrl.hostname, port: proxyPort });
|
|
198
|
+
rawSocket.once("error", onError);
|
|
199
|
+
rawSocket.once(proxyTls ? "secureConnect" : "connect", onReady);
|
|
200
|
+
return promise;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Open Cursor's HTTP/2 session directly or through an HTTP CONNECT proxy. */
|
|
204
|
+
export async function connectCursorHttp2(
|
|
205
|
+
baseUrl: string,
|
|
206
|
+
options: CursorHttp2ConnectOptions = {},
|
|
207
|
+
): Promise<http2.ClientHttp2Session> {
|
|
208
|
+
const target = new URL(baseUrl);
|
|
209
|
+
const proxy = resolveCursorProxy(target);
|
|
210
|
+
if (!proxy) return http2.connect(target);
|
|
211
|
+
const socket = await connectProxyTunnel(new URL(proxy), target, options);
|
|
212
|
+
return http2.connect(target, { createConnection: () => socket });
|
|
213
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Node >=22 provides Promise.withResolvers; the repo's ES2022 lib needs a shim. */
|
|
2
|
+
declare global {
|
|
3
|
+
interface PromiseConstructor {
|
|
4
|
+
withResolvers<T>(): {
|
|
5
|
+
promise: Promise<T>;
|
|
6
|
+
resolve: (value: T | PromiseLike<T>) => void;
|
|
7
|
+
reject: (reason?: unknown) => void;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-providers — OAuth-backed model providers for pi.
|
|
3
|
+
*
|
|
4
|
+
* Adds OAuth-backed Google Antigravity and Cursor model providers. Both are
|
|
5
|
+
* inert until the user logs in and selects one of their models. Cursor uses
|
|
6
|
+
* AgentService/Run in deliberately chat-only mode: Cursor-native coding tools
|
|
7
|
+
* are not exposed or executed by this extension.
|
|
8
|
+
*
|
|
9
|
+
* Wire protocol: Cloud Code Assist `v1internal:streamGenerateContent` over
|
|
10
|
+
* SSE (see antigravity/provider.ts). Reference implementation: oh-my-pi's
|
|
11
|
+
* google-gemini-cli provider (shared google-gemini-cli/google-antigravity).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createProvider, type ProviderStreams } from "@earendil-works/pi-ai";
|
|
15
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
import { createOAuthAuth } from "./oauth-adapter.ts";
|
|
17
|
+
import { encodeApiKey } from "./antigravity/credentials.ts";
|
|
18
|
+
import { fetchAntigravityModels } from "./antigravity/discovery.ts";
|
|
19
|
+
import {
|
|
20
|
+
ANTIGRAVITY_API_URL,
|
|
21
|
+
ANTIGRAVITY_MODELS,
|
|
22
|
+
} from "./antigravity/models.ts";
|
|
23
|
+
import {
|
|
24
|
+
loginAntigravity,
|
|
25
|
+
refreshAntigravityToken,
|
|
26
|
+
} from "./antigravity/oauth.ts";
|
|
27
|
+
import { streamAntigravity } from "./antigravity/provider.ts";
|
|
28
|
+
import { getCursorApiKey } from "./cursor/credentials.ts";
|
|
29
|
+
import { fetchCursorModels } from "./cursor/discovery.ts";
|
|
30
|
+
import { transformCursorImageInput } from "./cursor/input-images.ts";
|
|
31
|
+
import { CURSOR_MODELS } from "./cursor/models.ts";
|
|
32
|
+
import { loginCursor, refreshCursorToken } from "./cursor/oauth.ts";
|
|
33
|
+
import { streamCursor } from "./cursor/provider.ts";
|
|
34
|
+
|
|
35
|
+
function providerStreams(
|
|
36
|
+
streamSimple: ProviderStreams["streamSimple"],
|
|
37
|
+
): ProviderStreams {
|
|
38
|
+
return {
|
|
39
|
+
stream: (model, context, options) => streamSimple(model, context, options),
|
|
40
|
+
streamSimple,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default function authProviders(pi: ExtensionAPI) {
|
|
45
|
+
pi.on("input", transformCursorImageInput);
|
|
46
|
+
|
|
47
|
+
pi.registerProvider(
|
|
48
|
+
createProvider({
|
|
49
|
+
id: "google-antigravity",
|
|
50
|
+
name: "Google Antigravity",
|
|
51
|
+
baseUrl: ANTIGRAVITY_API_URL,
|
|
52
|
+
api: providerStreams(streamAntigravity),
|
|
53
|
+
auth: {
|
|
54
|
+
oauth: createOAuthAuth({
|
|
55
|
+
name: "Google (Antigravity)",
|
|
56
|
+
isSubscription: true,
|
|
57
|
+
login: loginAntigravity,
|
|
58
|
+
refreshToken: refreshAntigravityToken,
|
|
59
|
+
getApiKey: encodeApiKey,
|
|
60
|
+
}),
|
|
61
|
+
},
|
|
62
|
+
models: ANTIGRAVITY_MODELS,
|
|
63
|
+
fetchModels: fetchAntigravityModels,
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
pi.registerProvider(
|
|
68
|
+
createProvider({
|
|
69
|
+
id: "cursor",
|
|
70
|
+
name: "Cursor",
|
|
71
|
+
baseUrl: "https://api2.cursor.sh",
|
|
72
|
+
api: providerStreams(streamCursor),
|
|
73
|
+
auth: {
|
|
74
|
+
oauth: createOAuthAuth({
|
|
75
|
+
name: "Cursor",
|
|
76
|
+
isSubscription: true,
|
|
77
|
+
login: loginCursor,
|
|
78
|
+
refreshToken: refreshCursorToken,
|
|
79
|
+
getApiKey: getCursorApiKey,
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
models: CURSOR_MODELS,
|
|
83
|
+
fetchModels: fetchCursorModels,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModelAuth,
|
|
3
|
+
OAuthAuth,
|
|
4
|
+
OAuthCredential,
|
|
5
|
+
OAuthCredentials,
|
|
6
|
+
OAuthLoginCallbacks,
|
|
7
|
+
ProviderAuthInteraction,
|
|
8
|
+
} from "@earendil-works/pi-ai";
|
|
9
|
+
|
|
10
|
+
interface LegacyOAuthImplementation {
|
|
11
|
+
name: string;
|
|
12
|
+
isSubscription?: boolean;
|
|
13
|
+
login(callbacks: CancellableOAuthLoginCallbacks): Promise<OAuthCredentials>;
|
|
14
|
+
refreshToken(
|
|
15
|
+
credential: OAuthCredentials,
|
|
16
|
+
signal: AbortSignal,
|
|
17
|
+
): Promise<OAuthCredentials>;
|
|
18
|
+
getApiKey(credential: OAuthCredentials): string | Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CancellableOAuthLoginCallbacks = Omit<
|
|
22
|
+
OAuthLoginCallbacks,
|
|
23
|
+
"onManualCodeInput"
|
|
24
|
+
> & {
|
|
25
|
+
onManualCodeInput?(signal?: AbortSignal): Promise<string>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function legacyCallbacks(
|
|
29
|
+
interaction: ProviderAuthInteraction,
|
|
30
|
+
): CancellableOAuthLoginCallbacks {
|
|
31
|
+
return {
|
|
32
|
+
signal: interaction.signal,
|
|
33
|
+
onAuth: (info) => interaction.notify({ type: "auth_url", ...info }),
|
|
34
|
+
onDeviceCode: (info) =>
|
|
35
|
+
interaction.notify({ type: "device_code", ...info }),
|
|
36
|
+
onProgress: (message) => interaction.notify({ type: "progress", message }),
|
|
37
|
+
onPrompt: (prompt) =>
|
|
38
|
+
interaction.prompt({
|
|
39
|
+
type: "text",
|
|
40
|
+
message: prompt.message,
|
|
41
|
+
placeholder: prompt.placeholder,
|
|
42
|
+
}),
|
|
43
|
+
onManualCodeInput: (signal) =>
|
|
44
|
+
interaction.prompt({
|
|
45
|
+
type: "manual_code",
|
|
46
|
+
message: "Paste the authorization callback URL or code",
|
|
47
|
+
signal,
|
|
48
|
+
}),
|
|
49
|
+
onSelect: (prompt) =>
|
|
50
|
+
interaction.prompt({
|
|
51
|
+
type: "select",
|
|
52
|
+
message: prompt.message,
|
|
53
|
+
options: prompt.options,
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function canonicalCredential(credentials: OAuthCredentials): OAuthCredential {
|
|
59
|
+
return { ...credentials, type: "oauth" };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Adapt pi's retained extension OAuth callbacks to the native Provider API. */
|
|
63
|
+
export function createOAuthAuth(
|
|
64
|
+
implementation: LegacyOAuthImplementation,
|
|
65
|
+
): OAuthAuth {
|
|
66
|
+
return {
|
|
67
|
+
name: implementation.name,
|
|
68
|
+
isSubscription: implementation.isSubscription,
|
|
69
|
+
login: async (interaction) =>
|
|
70
|
+
canonicalCredential(
|
|
71
|
+
await implementation.login(legacyCallbacks(interaction)),
|
|
72
|
+
),
|
|
73
|
+
refresh: async (credential, signal) =>
|
|
74
|
+
canonicalCredential(
|
|
75
|
+
await implementation.refreshToken(credential, signal),
|
|
76
|
+
),
|
|
77
|
+
toAuth: async (credential): Promise<ModelAuth> => ({
|
|
78
|
+
apiKey: await implementation.getApiKey(credential),
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -32,6 +32,11 @@ import {
|
|
|
32
32
|
OPENPI_TOOL_SURFACE,
|
|
33
33
|
patchOwnedTools,
|
|
34
34
|
} from "../shared/tool-surface.ts";
|
|
35
|
+
import { completionOwnerFor } from "../shared/completion-inbox.ts";
|
|
36
|
+
import {
|
|
37
|
+
projectBackgroundTerminalCapability,
|
|
38
|
+
registerWebCapability,
|
|
39
|
+
} from "../shared/web-observer-registry.ts";
|
|
35
40
|
import type { TerminalSnapshot } from "./src/domain.ts";
|
|
36
41
|
import {
|
|
37
42
|
MAX_RUNNING,
|
|
@@ -95,11 +100,18 @@ interface WatchToolDetails {
|
|
|
95
100
|
export default function (pi: ExtensionAPI) {
|
|
96
101
|
let runtime: TerminalRuntime | undefined;
|
|
97
102
|
let managerPromise: Promise<TerminalManagerShape> | undefined;
|
|
103
|
+
let managerForHooks: TerminalManagerShape | undefined;
|
|
104
|
+
let unregisterWebCapability: (() => void) | undefined;
|
|
98
105
|
let sessionContext: ExtensionContext | undefined;
|
|
99
106
|
let ui: ExtensionUIContext | undefined;
|
|
100
107
|
let unsubStatus: (() => void) | undefined;
|
|
101
108
|
let startReservations = 0;
|
|
102
|
-
const resultDelivery = createDeferredResultDelivery<TerminalSnapshot>(
|
|
109
|
+
const resultDelivery = createDeferredResultDelivery<TerminalSnapshot>({
|
|
110
|
+
owner: () =>
|
|
111
|
+
sessionContext
|
|
112
|
+
? completionOwnerFor(sessionContext.sessionManager)
|
|
113
|
+
: undefined,
|
|
114
|
+
});
|
|
103
115
|
const hideLifecycleTools = () =>
|
|
104
116
|
patchOwnedTools(pi, "background", {
|
|
105
117
|
disable: OPENPI_TOOL_SURFACE.background.deferred,
|
|
@@ -115,9 +127,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
115
127
|
|
|
116
128
|
/** Resolve the manager service once per runtime and wire the extension hooks. */
|
|
117
129
|
const getManager = () => {
|
|
130
|
+
const scope = sessionContext?.sessionManager;
|
|
118
131
|
managerPromise ??= getRuntime()
|
|
119
132
|
.runPromise(TerminalManager)
|
|
120
133
|
.then((manager) => {
|
|
134
|
+
managerForHooks = manager;
|
|
135
|
+
unregisterWebCapability?.();
|
|
136
|
+
unregisterWebCapability =
|
|
137
|
+
scope && sessionContext?.sessionManager === scope
|
|
138
|
+
? registerWebCapability(scope, {
|
|
139
|
+
kind: "background-terminals",
|
|
140
|
+
snapshot: () =>
|
|
141
|
+
projectBackgroundTerminalCapability(manager.view.list()),
|
|
142
|
+
subscribe: (listener) => manager.view.subscribe(listener),
|
|
143
|
+
})
|
|
144
|
+
: undefined;
|
|
121
145
|
manager.view.setOnSettled(onSettled);
|
|
122
146
|
unsubStatus?.();
|
|
123
147
|
unsubStatus = manager.view.subscribe(() => updateWidget(manager));
|
|
@@ -214,6 +238,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
214
238
|
// costing a turn.
|
|
215
239
|
resultDeliveryOptions(wake),
|
|
216
240
|
);
|
|
241
|
+
for (const snap of snaps) managerForHooks?.view.releaseResult(snap.id);
|
|
217
242
|
return true;
|
|
218
243
|
} catch (error) {
|
|
219
244
|
// Session may be shutting down, but retain the snapshot so any later
|
|
@@ -226,6 +251,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
226
251
|
const flushResults = (wake: boolean) => {
|
|
227
252
|
const snaps = resultDelivery.drain(MAX_RUNNING);
|
|
228
253
|
if (!deliverResults(snaps, wake)) resultDelivery.restore(snaps);
|
|
254
|
+
else resultDelivery.acknowledge(snaps);
|
|
229
255
|
};
|
|
230
256
|
|
|
231
257
|
const idleResultBatcher = createIdleResultBatcher({
|
|
@@ -253,6 +279,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
253
279
|
stdout: { ...snap.stdout },
|
|
254
280
|
stderr: { ...snap.stderr },
|
|
255
281
|
});
|
|
282
|
+
managerForHooks?.view.retainResult(snap.id);
|
|
256
283
|
// Pending settlements remain retractable while busy, so bg_status/bg_kill
|
|
257
284
|
// can consume them before they are committed to context. bg_start applies
|
|
258
285
|
// backpressure across running + pending + reserved work, keeping this map
|
|
@@ -310,7 +337,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
310
337
|
ui = undefined;
|
|
311
338
|
const closing = runtime;
|
|
312
339
|
runtime = undefined;
|
|
340
|
+
unregisterWebCapability?.();
|
|
341
|
+
unregisterWebCapability = undefined;
|
|
313
342
|
managerPromise = undefined;
|
|
343
|
+
managerForHooks = undefined;
|
|
314
344
|
await closing?.dispose();
|
|
315
345
|
});
|
|
316
346
|
|
|
@@ -423,12 +453,16 @@ export default function (pi: ExtensionAPI) {
|
|
|
423
453
|
);
|
|
424
454
|
}
|
|
425
455
|
|
|
456
|
+
const text = buildStatusResult(snap);
|
|
426
457
|
// This status is returning the settlement itself; a pending automatic
|
|
427
458
|
// follow-up for the same settle would be a duplicate.
|
|
428
|
-
if (snap.status !== "running")
|
|
459
|
+
if (snap.status !== "running") {
|
|
460
|
+
resultDelivery.consume([snap.id]);
|
|
461
|
+
manager.view.releaseResult(snap.id);
|
|
462
|
+
}
|
|
429
463
|
|
|
430
464
|
return {
|
|
431
|
-
content: [{ type: "text", text
|
|
465
|
+
content: [{ type: "text", text }],
|
|
432
466
|
details: {
|
|
433
467
|
id: snap.id,
|
|
434
468
|
status: snap.status,
|
|
@@ -510,6 +544,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
510
544
|
// via the killInterest consumed flag). Remove any deferred automatic
|
|
511
545
|
// delivery now that this tool returns the final state itself.
|
|
512
546
|
resultDelivery.consume(ids);
|
|
547
|
+
for (const id of ids) manager.view.releaseResult(id);
|
|
513
548
|
|
|
514
549
|
return {
|
|
515
550
|
content: [{ type: "text", text: buildKillReport(report) }],
|
|
@@ -23,6 +23,8 @@ export type TerminalStatus =
|
|
|
23
23
|
export interface OutputView {
|
|
24
24
|
/** Decoded, possibly head-trimmed text (bounded by the in-memory cap). */
|
|
25
25
|
readonly text: string;
|
|
26
|
+
/** Stateful, bounded projection safe for model-facing output. */
|
|
27
|
+
readonly modelSafeText: string;
|
|
26
28
|
/** True total bytes ever received on this stream. */
|
|
27
29
|
readonly totalBytes: number;
|
|
28
30
|
/** Bytes dropped from the head of the in-memory view (0 = complete). */
|