@selesai/code 0.13.2 → 0.13.4
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 +20 -0
- package/dist/defaults/models.json +85 -13
- package/dist/extensions/cost-reconcile.test.ts +200 -4
- package/dist/extensions/cost-reconcile.ts +88 -102
- package/dist/extensions/pi-intercom/CHANGELOG.md +13 -0
- package/dist/extensions/pi-intercom/README.md +4 -5
- package/dist/extensions/pi-intercom/config.test.ts +3 -31
- package/dist/extensions/pi-intercom/config.ts +0 -15
- package/dist/extensions/pi-intercom/index.ts +9 -46
- package/dist/extensions/pi-intercom/intercom.integration.test.ts +49 -57
- package/dist/extensions/pi-intercom/package.json +1 -1
- package/dist/extensions/pi-intercom/reply-tracker.test.ts +20 -0
- package/dist/extensions/pi-intercom/reply-tracker.ts +8 -0
- package/dist/extensions/pi-subagents/CHANGELOG.md +27 -0
- package/dist/extensions/pi-subagents/docs/tool-reference.md +4 -1
- package/dist/extensions/pi-subagents/docs/workflows.md +2 -2
- package/dist/extensions/pi-subagents/package-lock.json +2 -2
- package/dist/extensions/pi-subagents/package.json +1 -1
- package/dist/extensions/pi-subagents/skills/council-mode/SKILL.md +48 -243
- package/dist/extensions/pi-subagents/skills/council-mode/references/pass-contracts.md +150 -0
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +87 -37
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +29 -233
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +49 -8
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +13 -1
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +34 -27
- package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +73 -0
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +157 -28
- package/dist/extensions/pi-subagents/src/api/shared-types.ts +2 -0
- package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +1 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +45 -2
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +3 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +13 -2
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +5 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +10 -2
- package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +3 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +11 -2
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +98 -1
- package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +138 -4
- package/dist/extensions/pi-subagents/src/runs/shared/background-process-options.ts +9 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-grant.ts +2 -5
- package/dist/extensions/pi-subagents/src/runs/shared/mutation-evidence.ts +52 -3
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +47 -1
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +45 -18
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +21 -2
- package/dist/extensions/pi-subagents/src/runs/shared/workflow-graph.ts +15 -0
- package/dist/extensions/pi-subagents/src/shared/types.ts +34 -1
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +11 -3
- package/dist/extensions/pi-subagents/src/tui/render-helpers.ts +31 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +597 -112
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +40 -1
- package/dist/extensions/pi-subagents/src/workflows/host-command.ts +6 -1
- package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +53 -2
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +55 -6
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +206 -38
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +522 -31
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +123 -0
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +48 -0
- package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +57 -1
- package/dist/extensions/pi-subagents/test/unit/background-process-options.test.ts +17 -0
- package/dist/extensions/pi-subagents/test/unit/external-cli-runner.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +44 -4
- package/dist/extensions/pi-subagents/test/unit/fork-cache-key.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/host-command.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +0 -1
- package/dist/extensions/pi-subagents/test/unit/mcp-direct-tool-grant.test.ts +20 -3
- package/dist/extensions/pi-subagents/test/unit/mutation-evidence.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +93 -17
- package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +1 -0
- package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +103 -26
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +22 -2
- package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +21 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +13 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +54 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +2 -0
- package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +32 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +21 -6
- package/dist/extensions/pi-subagents/test/unit/windows-hide-spawn.test.ts +11 -0
- package/dist/extensions/pi-web-agent/CHANGELOG.md +410 -0
- package/dist/extensions/pi-web-agent/README.md +131 -0
- package/dist/extensions/pi-web-agent/package.json +4 -2
- package/dist/extensions/pi-web-agent/src/backends/config.ts +62 -5
- package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -0
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +96 -6
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +183 -47
- package/dist/extensions/pi-web-agent/src/extension.ts +62 -25
- package/dist/extensions/pi-web-agent/src/extract/readability.ts +19 -11
- package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +5 -4
- package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +2 -25
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +5 -2
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +2 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +72 -7
- package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +8 -1
- package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +28 -3
- package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +4 -0
- package/dist/extensions/pi-web-agent/src/orchestration/url.ts +35 -0
- package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +18 -6
- package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +14 -2
- package/dist/extensions/pi-web-agent/src/readers/github-reader.ts +150 -0
- package/dist/extensions/pi-web-agent/src/readers/limits.ts +3 -0
- package/dist/extensions/pi-web-agent/src/readers/pdf-reader.ts +87 -0
- package/dist/extensions/pi-web-agent/src/readers/resolver.ts +25 -0
- package/dist/extensions/pi-web-agent/src/readers/types.ts +11 -0
- package/dist/extensions/pi-web-agent/src/readers/youtube-reader.ts +79 -0
- package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +32 -5
- package/dist/extensions/pi-web-agent/src/search/exa.ts +109 -0
- package/dist/extensions/pi-web-agent/src/search/fanout.ts +154 -0
- package/dist/extensions/pi-web-agent/src/search/tavily.ts +113 -0
- package/dist/extensions/pi-web-agent/src/search/youcom.ts +109 -0
- package/dist/extensions/pi-web-agent/src/tools/web-search.ts +22 -9
- package/dist/extensions/pi-web-agent/src/types.ts +19 -4
- package/dist/extensions/tokenin-onboarding.ts +302 -0
- package/package.json +1 -1
|
@@ -16,20 +16,33 @@ export type SearchResult = {
|
|
|
16
16
|
snippet: string;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
+
export type SearchProviderName = 'duckduckgo' | 'searxng' | 'brave' | 'youcom' | 'exa' | 'tavily';
|
|
20
|
+
|
|
21
|
+
export type FanoutMode = 'off' | 'on' | 'auto';
|
|
22
|
+
|
|
23
|
+
export type FanoutMetadata = {
|
|
24
|
+
mode: Exclude<FanoutMode, 'off'>;
|
|
25
|
+
providers: SearchProviderName[]; // providers that actually contributed results
|
|
26
|
+
skipped?: SearchProviderName[]; // providers queried but errored or returned nothing
|
|
27
|
+
};
|
|
28
|
+
|
|
19
29
|
export type ToolError = {
|
|
20
30
|
code: string;
|
|
21
31
|
message: string;
|
|
22
32
|
};
|
|
23
33
|
|
|
24
34
|
export type SearchMetadata = {
|
|
25
|
-
backend: 'duckduckgo' | 'searxng' | 'brave';
|
|
35
|
+
backend: 'duckduckgo' | 'searxng' | 'brave' | 'youcom' | 'exa' | 'tavily';
|
|
26
36
|
cacheHit: boolean;
|
|
27
|
-
fallbackFrom?: 'searxng' | 'brave';
|
|
37
|
+
fallbackFrom?: 'searxng' | 'brave' | 'youcom' | 'exa' | 'tavily' | 'duckduckgo';
|
|
28
38
|
fallbackReason?: string;
|
|
39
|
+
fanout?: FanoutMetadata;
|
|
29
40
|
};
|
|
30
41
|
|
|
42
|
+
export type FetchMethod = 'http' | 'headless' | 'firecrawl' | 'github' | 'pdf' | 'youtube';
|
|
43
|
+
|
|
31
44
|
export type FetchMetadata = {
|
|
32
|
-
method:
|
|
45
|
+
method: FetchMethod;
|
|
33
46
|
cacheHit: boolean;
|
|
34
47
|
fallbackFrom?: 'firecrawl';
|
|
35
48
|
fallbackReason?: string;
|
|
@@ -74,7 +87,7 @@ export type WebFetchHeadlessResponse = {
|
|
|
74
87
|
export type WebExploreResponse = {
|
|
75
88
|
status: 'ok' | 'error';
|
|
76
89
|
findings: string[];
|
|
77
|
-
sources: Array<{ title: string; url: string; method?:
|
|
90
|
+
sources: Array<{ title: string; url: string; method?: FetchMethod }>;
|
|
78
91
|
caveat?: string;
|
|
79
92
|
metadata?: {
|
|
80
93
|
searchPasses: number;
|
|
@@ -82,6 +95,8 @@ export type WebExploreResponse = {
|
|
|
82
95
|
headlessAttempts: number;
|
|
83
96
|
exhaustedBudget: boolean;
|
|
84
97
|
caveatReasons?: string[];
|
|
98
|
+
fanoutProviders?: SearchProviderName[];
|
|
99
|
+
fanoutSkipped?: SearchProviderName[];
|
|
85
100
|
};
|
|
86
101
|
presentation?: PresentationEnvelope;
|
|
87
102
|
error?: ToolError;
|
|
@@ -13,6 +13,16 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "n
|
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { getAgentDir, getModelsPath } from "@selesai/code";
|
|
15
15
|
import type { AuthStorage, ExtensionAPI, ExtensionCommandContext, ExtensionContext, SessionStartEvent } from "@selesai/code";
|
|
16
|
+
import {
|
|
17
|
+
createAssistantMessageEventStream,
|
|
18
|
+
lazyStream,
|
|
19
|
+
type AssistantMessageEvent,
|
|
20
|
+
type AssistantMessageEventStream,
|
|
21
|
+
type Context,
|
|
22
|
+
type Model,
|
|
23
|
+
type SimpleStreamOptions,
|
|
24
|
+
} from "@earendil-works/pi-ai";
|
|
25
|
+
import { getApiProvider } from "@earendil-works/pi-ai/compat";
|
|
16
26
|
|
|
17
27
|
export const TOKEN_IN_PROVIDER = "tokenin";
|
|
18
28
|
export const TOKEN_IN_DASHBOARD_URL = "https://token.selesai.in/dashboard/tokens";
|
|
@@ -216,6 +226,291 @@ function isOnboardingComplete(agentDir: string = getAgentDir()): boolean {
|
|
|
216
226
|
return existsSync(getOnboardingMarkerPath(agentDir));
|
|
217
227
|
}
|
|
218
228
|
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// Multi-key load balancing / auto-failover
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
|
|
233
|
+
export const DEFAULT_TOKENIN_COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
|
|
234
|
+
|
|
235
|
+
/** In-memory cooldown tracker mapping token/accountId -> cooldown expires timestamp (ms). */
|
|
236
|
+
const tokenCooldowns = new Map<string, number>();
|
|
237
|
+
|
|
238
|
+
/** Clear all in-memory cooldowns. Exported for tests. */
|
|
239
|
+
export function clearTokenInCooldowns(): void {
|
|
240
|
+
tokenCooldowns.clear();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Check if a token/account is currently on cooldown. */
|
|
244
|
+
export function isTokenInOnCooldown(tokenId: string, now: number = Date.now()): boolean {
|
|
245
|
+
const expires = tokenCooldowns.get(tokenId);
|
|
246
|
+
if (expires === undefined) return false;
|
|
247
|
+
if (now >= expires) {
|
|
248
|
+
tokenCooldowns.delete(tokenId);
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Put a token/account on cooldown for a given duration. */
|
|
255
|
+
export function setTokenInCooldown(tokenId: string, durationMs: number = DEFAULT_TOKENIN_COOLDOWN_MS, now: number = Date.now()): void {
|
|
256
|
+
tokenCooldowns.set(tokenId, now + durationMs);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Detect if an error message or AssistantMessage stop indicates an auth/quota failure eligible for failover. */
|
|
260
|
+
export function isRotateableTokenInError(error: unknown): boolean {
|
|
261
|
+
if (!error) return false;
|
|
262
|
+
let text = "";
|
|
263
|
+
if (typeof error === "string") {
|
|
264
|
+
text = error;
|
|
265
|
+
} else if (error instanceof Error) {
|
|
266
|
+
text = `${error.name}: ${error.message}`;
|
|
267
|
+
} else if (typeof error === "object" && error !== null) {
|
|
268
|
+
const obj = error as Record<string, unknown>;
|
|
269
|
+
if (typeof obj.errorMessage === "string") {
|
|
270
|
+
text = obj.errorMessage;
|
|
271
|
+
} else if (typeof obj.message === "string") {
|
|
272
|
+
text = obj.message;
|
|
273
|
+
} else if (Array.isArray(obj.content)) {
|
|
274
|
+
// AssistantMessage with content blocks containing error text or errorMessage
|
|
275
|
+
for (const block of obj.content) {
|
|
276
|
+
if (typeof block === "object" && block !== null) {
|
|
277
|
+
const b = block as Record<string, unknown>;
|
|
278
|
+
if (typeof b.errorMessage === "string") text += ` ${b.errorMessage}`;
|
|
279
|
+
if (typeof b.text === "string") text += ` ${b.text}`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (!text) text = JSON.stringify(error);
|
|
284
|
+
} else {
|
|
285
|
+
text = JSON.stringify(error);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const lower = text.toLowerCase();
|
|
289
|
+
// HTTP 401 / Invalid Key
|
|
290
|
+
if (lower.includes("401") || lower.includes("unauthorized") || lower.includes("invalid_api_key") || lower.includes("invalid api key") || lower.includes("authentication_error")) {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
// HTTP 429 / Rate limit
|
|
294
|
+
if (lower.includes("429") || lower.includes("rate_limit") || lower.includes("rate limit") || lower.includes("too many requests")) {
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
// Budget / Quota / Credits
|
|
298
|
+
if (
|
|
299
|
+
lower.includes("budget has been exceeded") ||
|
|
300
|
+
lower.includes("budget_exceeded") ||
|
|
301
|
+
lower.includes("max_budget") ||
|
|
302
|
+
lower.includes("insufficient_quota") ||
|
|
303
|
+
lower.includes("quota exceeded") ||
|
|
304
|
+
lower.includes("exceeds max_budget") ||
|
|
305
|
+
lower.includes("credit limit") ||
|
|
306
|
+
lower.includes("out of credits")
|
|
307
|
+
) {
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Helper to persist active credential to auth.json and tokenin-auth.json without needing AuthStorage instance. */
|
|
314
|
+
function persistActiveTokenInAccount(account: TokenInAccount, authPath: string = getTokenInAuthPath()): void {
|
|
315
|
+
try {
|
|
316
|
+
// Update tokenin-auth.json
|
|
317
|
+
const auth = readTokenInAuth(authPath);
|
|
318
|
+
auth.activeId = account.id;
|
|
319
|
+
writeTokenInAuth(auth, authPath);
|
|
320
|
+
|
|
321
|
+
// Update auth.json
|
|
322
|
+
const agentDir = dirname(authPath);
|
|
323
|
+
const globalAuthPath = join(agentDir, "auth.json");
|
|
324
|
+
const current = existsSync(globalAuthPath) ? JSON.parse(readFileSync(globalAuthPath, "utf-8")) as Record<string, unknown> : {};
|
|
325
|
+
current[TOKEN_IN_PROVIDER] = { type: "api_key", key: account.apiKey };
|
|
326
|
+
mkdirSync(dirname(globalAuthPath), { recursive: true, mode: 0o700 });
|
|
327
|
+
writeFileSync(globalAuthPath, `${JSON.stringify(current, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
328
|
+
try {
|
|
329
|
+
chmodSync(globalAuthPath, 0o600);
|
|
330
|
+
} catch {
|
|
331
|
+
// best-effort
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
// best-effort sync
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Custom streamSimple implementation for tokenin provider that wraps OpenAI completions
|
|
340
|
+
* and automatically rotates to alternative saved keys on 401/429/budget exceeded errors.
|
|
341
|
+
*/
|
|
342
|
+
export function createTokenInStreamSimple(options?: {
|
|
343
|
+
authPath?: string;
|
|
344
|
+
getAuthStorage?: () => AuthStorage | undefined;
|
|
345
|
+
onRotate?: (failedAccount: TokenInAccount, nextAccount: TokenInAccount, reason: string) => void;
|
|
346
|
+
streamSimple?: (model: Model<any>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
347
|
+
}) {
|
|
348
|
+
const authPath = options?.authPath ?? getTokenInAuthPath();
|
|
349
|
+
const baseApi = options?.streamSimple ? undefined : getApiProvider("openai-completions");
|
|
350
|
+
const streamSimple = options?.streamSimple ?? baseApi!.streamSimple.bind(baseApi!);
|
|
351
|
+
|
|
352
|
+
const activate = (account: TokenInAccount): void => {
|
|
353
|
+
const authStorage = options?.getAuthStorage?.();
|
|
354
|
+
if (authStorage) {
|
|
355
|
+
applyTokenInAccountToAuth(account, authStorage, authPath);
|
|
356
|
+
} else {
|
|
357
|
+
persistActiveTokenInAccount(account, authPath);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const withAccountAuthorization = (streamOptions: SimpleStreamOptions | undefined, account: TokenInAccount): SimpleStreamOptions => {
|
|
362
|
+
const headers = Object.fromEntries(
|
|
363
|
+
Object.entries(streamOptions?.headers ?? {}).filter(([name]) => name.toLowerCase() !== "authorization"),
|
|
364
|
+
);
|
|
365
|
+
return {
|
|
366
|
+
...streamOptions,
|
|
367
|
+
apiKey: account.apiKey,
|
|
368
|
+
headers: { ...headers, Authorization: `Bearer ${account.apiKey}` },
|
|
369
|
+
};
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
return function tokenInStreamSimple(
|
|
373
|
+
model: Model<any>,
|
|
374
|
+
context: Context,
|
|
375
|
+
streamOptions?: SimpleStreamOptions,
|
|
376
|
+
): AssistantMessageEventStream {
|
|
377
|
+
return lazyStream(model, async () => {
|
|
378
|
+
const auth = readTokenInAuth(authPath);
|
|
379
|
+
const accounts = auth.accounts;
|
|
380
|
+
|
|
381
|
+
// If no accounts saved, fall back directly to base implementation
|
|
382
|
+
if (accounts.length === 0) {
|
|
383
|
+
return streamSimple(model, context, streamOptions);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Order accounts: active account first, followed by remaining accounts
|
|
387
|
+
const activeIndex = accounts.findIndex((a) => a.id === auth.activeId || a.apiKey === streamOptions?.apiKey);
|
|
388
|
+
const orderedAccounts: TokenInAccount[] = [];
|
|
389
|
+
if (activeIndex >= 0) {
|
|
390
|
+
orderedAccounts.push(accounts[activeIndex]!);
|
|
391
|
+
for (let i = 0; i < accounts.length; i++) {
|
|
392
|
+
if (i !== activeIndex) orderedAccounts.push(accounts[i]!);
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
orderedAccounts.push(...accounts);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Filter/order candidates: prioritize those not currently on cooldown
|
|
399
|
+
const availableAccounts = orderedAccounts.filter((a) => !isTokenInOnCooldown(a.id));
|
|
400
|
+
const candidateAccounts = (availableAccounts.length > 0 ? availableAccounts : orderedAccounts).map((account) => ({ ...account }));
|
|
401
|
+
|
|
402
|
+
let lastErrorEvent: AssistantMessageEvent | undefined;
|
|
403
|
+
let lastThrownError: unknown;
|
|
404
|
+
|
|
405
|
+
for (let i = 0; i < candidateAccounts.length; i++) {
|
|
406
|
+
const currentAccount = candidateAccounts[i]!;
|
|
407
|
+
|
|
408
|
+
const requestOptions = withAccountAuthorization(streamOptions, currentAccount);
|
|
409
|
+
|
|
410
|
+
let underlyingStream: AssistantMessageEventStream;
|
|
411
|
+
try {
|
|
412
|
+
underlyingStream = streamSimple(model, context, requestOptions);
|
|
413
|
+
} catch (err) {
|
|
414
|
+
lastThrownError = err;
|
|
415
|
+
if (isRotateableTokenInError(err) && i + 1 < candidateAccounts.length) {
|
|
416
|
+
setTokenInCooldown(currentAccount.id);
|
|
417
|
+
const nextAccount = candidateAccounts[i + 1]!;
|
|
418
|
+
activate(nextAccount);
|
|
419
|
+
options?.onRotate?.(currentAccount, nextAccount, String(err));
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
throw err;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Hold a leading start event until the request proves it produced output. This
|
|
426
|
+
// permits an immediate upstream error to be retried without exposing two starts.
|
|
427
|
+
const iterator = underlyingStream[Symbol.asyncIterator]();
|
|
428
|
+
let firstResult: IteratorResult<AssistantMessageEvent>;
|
|
429
|
+
try {
|
|
430
|
+
firstResult = await iterator.next();
|
|
431
|
+
} catch (err) {
|
|
432
|
+
lastThrownError = err;
|
|
433
|
+
if (isRotateableTokenInError(err) && i + 1 < candidateAccounts.length) {
|
|
434
|
+
setTokenInCooldown(currentAccount.id);
|
|
435
|
+
const nextAccount = candidateAccounts[i + 1]!;
|
|
436
|
+
activate(nextAccount);
|
|
437
|
+
options?.onRotate?.(currentAccount, nextAccount, String(err));
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
throw err;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (firstResult.done) {
|
|
444
|
+
const passthrough = createAssistantMessageEventStream();
|
|
445
|
+
passthrough.end();
|
|
446
|
+
return passthrough;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const bufferedEvents = [firstResult.value];
|
|
450
|
+
if (bufferedEvents[0].type === "start") {
|
|
451
|
+
try {
|
|
452
|
+
const next = await iterator.next();
|
|
453
|
+
if (!next.done) bufferedEvents.push(next.value);
|
|
454
|
+
} catch (err) {
|
|
455
|
+
lastThrownError = err;
|
|
456
|
+
if (isRotateableTokenInError(err) && i + 1 < candidateAccounts.length) {
|
|
457
|
+
setTokenInCooldown(currentAccount.id);
|
|
458
|
+
const nextAccount = candidateAccounts[i + 1]!;
|
|
459
|
+
activate(nextAccount);
|
|
460
|
+
options?.onRotate?.(currentAccount, nextAccount, String(err));
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
throw err;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const immediateError = bufferedEvents.find((event) => event.type === "error");
|
|
468
|
+
if (immediateError?.type === "error" && isRotateableTokenInError(immediateError.error)) {
|
|
469
|
+
lastErrorEvent = immediateError;
|
|
470
|
+
setTokenInCooldown(currentAccount.id);
|
|
471
|
+
if (i + 1 < candidateAccounts.length) {
|
|
472
|
+
const nextAccount = candidateAccounts[i + 1]!;
|
|
473
|
+
activate(nextAccount);
|
|
474
|
+
options?.onRotate?.(currentAccount, nextAccount, immediateError.error.errorMessage ?? "Error");
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const outputStream = createAssistantMessageEventStream();
|
|
480
|
+
(async () => {
|
|
481
|
+
try {
|
|
482
|
+
for (const event of bufferedEvents) outputStream.push(event);
|
|
483
|
+
while (true) {
|
|
484
|
+
const next = await iterator.next();
|
|
485
|
+
if (next.done) break;
|
|
486
|
+
outputStream.push(next.value);
|
|
487
|
+
}
|
|
488
|
+
outputStream.end();
|
|
489
|
+
} catch {
|
|
490
|
+
outputStream.end();
|
|
491
|
+
}
|
|
492
|
+
})();
|
|
493
|
+
|
|
494
|
+
return outputStream;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// If loop exhausted with a rotateable error event, return a stream with that error
|
|
498
|
+
if (lastErrorEvent) {
|
|
499
|
+
const errorStream = createAssistantMessageEventStream();
|
|
500
|
+
errorStream.push(lastErrorEvent);
|
|
501
|
+
errorStream.end();
|
|
502
|
+
return errorStream;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (lastThrownError) {
|
|
506
|
+
throw lastThrownError;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return streamSimple(model, context, streamOptions);
|
|
510
|
+
});
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
219
514
|
async function openDashboard(pi: ExtensionAPI): Promise<void> {
|
|
220
515
|
const url = TOKEN_IN_DASHBOARD_URL;
|
|
221
516
|
let command: string;
|
|
@@ -537,7 +832,14 @@ async function runOnboarding(pi: ExtensionAPI, ctx: ExtensionContext): Promise<v
|
|
|
537
832
|
}
|
|
538
833
|
|
|
539
834
|
export default function tokenInOnboardingExtension(pi: ExtensionAPI): void {
|
|
835
|
+
let authStorage: AuthStorage | undefined;
|
|
836
|
+
pi.registerProvider("tokenin", {
|
|
837
|
+
api: "openai-completions",
|
|
838
|
+
streamSimple: createTokenInStreamSimple({ getAuthStorage: () => authStorage }),
|
|
839
|
+
});
|
|
840
|
+
|
|
540
841
|
pi.on("session_start", async (event: SessionStartEvent, ctx: ExtensionContext) => {
|
|
842
|
+
authStorage = ctx.modelRegistry.authStorage;
|
|
541
843
|
if (event.reason !== "startup") return;
|
|
542
844
|
if (isOnboardingComplete()) return;
|
|
543
845
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selesai/code",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
4
4
|
"description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|