@north-light/api 0.3.561 → 0.3.563
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/dist/api/client.d.ts +27 -4
- package/dist/api/client.js +53 -0
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/bash-jobs.d.ts +5 -1
- package/dist/api/dto/broker-ops.d.ts +25 -1
- package/dist/api/dto/charges.d.ts +9 -2
- package/dist/api/dto/delivery.d.ts +26 -2
- package/dist/api/dto/execution-start.d.ts +57 -0
- package/dist/api/dto/execution-start.js +2 -0
- package/dist/api/dto/modelauth.d.ts +5 -0
- package/dist/api/dto/node-records.d.ts +16 -0
- package/dist/api/dto/objects.d.ts +1 -1
- package/dist/api/dto/reports.d.ts +0 -3
- package/dist/api/dto/run-events.d.ts +7 -1
- package/dist/api/dto/settings.d.ts +55 -0
- package/dist/api/dto/settings.js +4 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/routes.d.ts +11 -0
- package/dist/api/routes.js +12 -0
- package/dist/core/canvas/paths.d.ts +0 -1
- package/dist/core/canvas/paths.js +0 -3
- package/dist/core/canvas/person-state.d.ts +26 -0
- package/dist/core/canvas/person-state.js +41 -0
- package/dist/core/command-plugins/bundle.d.ts +1 -1
- package/dist/core/command-plugins/bundle.js +5 -5
- package/dist/core/config.d.ts +16 -15
- package/dist/core/config.js +99 -76
- package/dist/core/conversation-store/client.d.ts +2 -0
- package/dist/core/conversation-store/client.js +111 -0
- package/dist/core/conversation-store/index.d.ts +96 -0
- package/dist/core/conversation-store/index.js +62 -0
- package/dist/core/conversation-store/patch.d.ts +3 -0
- package/dist/core/conversation-store/patch.js +192 -0
- package/dist/core/conversation-store/repair.d.ts +45 -0
- package/dist/core/conversation-store/repair.js +84 -0
- package/dist/core/env-name.d.ts +1 -0
- package/dist/core/env-name.js +16 -0
- package/dist/core/events/emit.js +7 -5
- package/dist/core/git.d.ts +28 -0
- package/dist/core/git.js +103 -0
- package/dist/core/installed-plugins.js +12 -7
- package/dist/core/managed-providers.d.ts +72 -0
- package/dist/core/managed-providers.js +61 -0
- package/dist/core/plugin-env.js +1 -2
- package/dist/core/profiles/manifest.d.ts +14 -18
- package/dist/core/profiles/manifest.js +79 -98
- package/dist/core/runtime/broker/attachments-fetch.d.ts +5 -0
- package/dist/core/runtime/broker/attachments-fetch.js +20 -0
- package/dist/core/runtime/broker/daemon-ops.d.ts +10 -1
- package/dist/core/runtime/broker/daemon-ops.js +41 -3
- package/dist/core/runtime/broker/node-token.d.ts +3 -0
- package/dist/core/runtime/broker/node-token.js +16 -0
- package/dist/core/runtime/broker/signal-stream.js +2 -2
- package/dist/core/scoped-state/materialized-plugins.d.ts +18 -0
- package/dist/core/scoped-state/materialized-plugins.js +103 -0
- package/dist/core/scoped-state/person-store.d.ts +72 -0
- package/dist/core/scoped-state/person-store.js +112 -0
- package/dist/core/user-settings.js +1 -1
- package/dist/daemon/broker-signals/bus.d.ts +48 -11
- package/dist/daemon/broker-signals/bus.js +116 -46
- package/dist/shared/inbox-entry-body.js +2 -2
- package/dist/types.d.ts +12 -0
- package/package.json +1 -1
- package/dist/core/canvas/legacy-person.d.ts +0 -4
- package/dist/core/canvas/legacy-person.js +0 -4
- package/dist/core/locked-file.d.ts +0 -27
- package/dist/core/locked-file.js +0 -121
- package/dist/core/profiles/env-store.d.ts +0 -22
- package/dist/core/profiles/env-store.js +0 -166
- package/dist/core/scoped-state/db.d.ts +0 -17
- package/dist/core/scoped-state/db.js +0 -261
- package/dist/core/scoped-state/migrate.d.ts +0 -8
- package/dist/core/scoped-state/migrate.js +0 -187
- package/dist/core/scoped-state/profiles.d.ts +0 -27
- package/dist/core/scoped-state/profiles.js +0 -94
- package/dist/core/scoped-state/providers.d.ts +0 -24
- package/dist/core/scoped-state/providers.js +0 -19
- package/dist/core/scoped-state/schema.d.ts +0 -10
- package/dist/core/scoped-state/schema.js +0 -47
- package/dist/core/scoped-state/settings.d.ts +0 -28
- package/dist/core/scoped-state/settings.js +0 -104
- package/dist/core/sqlite-statements.d.ts +0 -4
- package/dist/core/sqlite-statements.js +0 -17
- package/dist/core/subscription-state.d.ts +0 -129
- package/dist/core/subscription-state.js +0 -299
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface GitResult {
|
|
2
|
+
status: number;
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
timedOut?: boolean;
|
|
6
|
+
/** The child did not exit with a numeric status (for example, spawn failure or timeout). */
|
|
7
|
+
executionFailed?: true;
|
|
8
|
+
processError?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface GitSyncOptions {
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function gitSync(args: string[], cwd?: string, input?: string, options?: GitSyncOptions): GitResult;
|
|
14
|
+
export interface GitAsyncOptions {
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
env?: NodeJS.ProcessEnv;
|
|
17
|
+
}
|
|
18
|
+
export declare function gitAsync(args: string[], cwd?: string, input?: string, options?: GitAsyncOptions): Promise<GitResult>;
|
|
19
|
+
export declare function clone(url: string, dest: string, opts?: {
|
|
20
|
+
ref?: string;
|
|
21
|
+
depth?: number;
|
|
22
|
+
}): GitResult;
|
|
23
|
+
export declare function fetch(cwd: string, ref?: string): GitResult;
|
|
24
|
+
export declare function lsRemote(url: string): GitResult;
|
|
25
|
+
export declare function currentSha(cwd: string): string | null;
|
|
26
|
+
export declare function remoteSha(cwd: string, ref: string): string | null;
|
|
27
|
+
export declare function isGitRepo(cwd: string): boolean;
|
|
28
|
+
export declare function deriveNameFromUrl(url: string): string;
|
package/dist/core/git.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import { network } from './errors.js';
|
|
3
|
+
/** Git takes `.git/index.lock` to refresh the index stat cache even during
|
|
4
|
+
* otherwise read-only commands — `status` is the common one. Killed between
|
|
5
|
+
* creating that lock and renaming it over the index, the lock orphans and
|
|
6
|
+
* blocks every later writer in the checkout with no owning process left to
|
|
7
|
+
* name. Agent processes are killed mid-command routinely (a closed node, a
|
|
8
|
+
* cancelled daemon tick, a timed-out shell), so the residue accumulates.
|
|
9
|
+
*
|
|
10
|
+
* `GIT_OPTIONAL_LOCKS=0` suppresses exactly those optional locks and nothing
|
|
11
|
+
* else: a commit, rebase, or `add` still takes the lock it requires, and
|
|
12
|
+
* command output is byte-identical. Built per call rather than snapshotted at
|
|
13
|
+
* module load, so a later change to the ambient environment still reaches git. */
|
|
14
|
+
function gitEnv(base = process.env) {
|
|
15
|
+
return { ...base, GIT_OPTIONAL_LOCKS: '0' };
|
|
16
|
+
}
|
|
17
|
+
export function gitSync(args, cwd, input, options = {}) {
|
|
18
|
+
const res = spawnSync('git', args, { cwd, encoding: 'utf8', input, env: gitEnv(), timeout: options.timeoutMs });
|
|
19
|
+
const status = typeof res.status === 'number' ? res.status : 1;
|
|
20
|
+
const completed = typeof res.status === 'number';
|
|
21
|
+
const stdout = typeof res.stdout === 'string' ? res.stdout : '';
|
|
22
|
+
const stderr = typeof res.stderr === 'string' ? res.stderr : '';
|
|
23
|
+
const processError = res.error === undefined ? undefined : String(res.error);
|
|
24
|
+
const timedOut = res.error?.code === 'ETIMEDOUT';
|
|
25
|
+
return { status, stdout, stderr, ...(completed ? {} : { executionFailed: true }), ...(processError === undefined ? {} : { processError }), ...(timedOut ? { timedOut: true } : {}) };
|
|
26
|
+
}
|
|
27
|
+
export async function gitAsync(args, cwd, input, options = {}) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
const child = spawn('git', args, { cwd, env: gitEnv(options.env) });
|
|
30
|
+
if (input === undefined)
|
|
31
|
+
child.stdin.end();
|
|
32
|
+
else
|
|
33
|
+
child.stdin.end(input);
|
|
34
|
+
let stdout = '';
|
|
35
|
+
let stderr = '';
|
|
36
|
+
let timedOut = false;
|
|
37
|
+
let settled = false;
|
|
38
|
+
const timer = options.timeoutMs === undefined ? undefined : setTimeout(() => {
|
|
39
|
+
timedOut = true;
|
|
40
|
+
child.kill('SIGTERM');
|
|
41
|
+
}, options.timeoutMs);
|
|
42
|
+
const settle = (result) => {
|
|
43
|
+
if (settled)
|
|
44
|
+
return;
|
|
45
|
+
settled = true;
|
|
46
|
+
if (timer !== undefined)
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
resolve(result);
|
|
49
|
+
};
|
|
50
|
+
child.stdout.on('data', (d) => (stdout += d.toString()));
|
|
51
|
+
child.stderr.on('data', (d) => (stderr += d.toString()));
|
|
52
|
+
child.on('close', (status) => {
|
|
53
|
+
const code = typeof status === 'number' ? status : 1;
|
|
54
|
+
settle({ status: code, stdout, stderr, ...(timedOut ? { timedOut: true, executionFailed: true } : {}) });
|
|
55
|
+
});
|
|
56
|
+
child.on('error', (e) => settle({ status: 1, stdout: '', stderr: String(e), executionFailed: true, processError: String(e) }));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
export function clone(url, dest, opts = {}) {
|
|
60
|
+
const args = ['clone'];
|
|
61
|
+
if (opts.depth)
|
|
62
|
+
args.push('--depth', String(opts.depth));
|
|
63
|
+
if (opts.ref)
|
|
64
|
+
args.push('--branch', opts.ref);
|
|
65
|
+
args.push(url, dest);
|
|
66
|
+
const res = gitSync(args);
|
|
67
|
+
if (res.status !== 0) {
|
|
68
|
+
throw network(`git clone failed: ${url}\n${res.stderr.trim()}`);
|
|
69
|
+
}
|
|
70
|
+
return res;
|
|
71
|
+
}
|
|
72
|
+
export function fetch(cwd, ref) {
|
|
73
|
+
const args = ['fetch', 'origin'];
|
|
74
|
+
if (ref)
|
|
75
|
+
args.push(ref);
|
|
76
|
+
return gitSync(args, cwd);
|
|
77
|
+
}
|
|
78
|
+
export function lsRemote(url) {
|
|
79
|
+
return gitSync(['ls-remote', url]);
|
|
80
|
+
}
|
|
81
|
+
export function currentSha(cwd) {
|
|
82
|
+
const res = gitSync(['rev-parse', 'HEAD'], cwd);
|
|
83
|
+
if (res.status !== 0)
|
|
84
|
+
return null;
|
|
85
|
+
return res.stdout.trim();
|
|
86
|
+
}
|
|
87
|
+
export function remoteSha(cwd, ref) {
|
|
88
|
+
const res = gitSync(['rev-parse', `origin/${ref}`], cwd);
|
|
89
|
+
if (res.status !== 0)
|
|
90
|
+
return null;
|
|
91
|
+
return res.stdout.trim();
|
|
92
|
+
}
|
|
93
|
+
export function isGitRepo(cwd) {
|
|
94
|
+
const res = gitSync(['rev-parse', '--is-inside-work-tree'], cwd);
|
|
95
|
+
return res.status === 0 && res.stdout.trim() === 'true';
|
|
96
|
+
}
|
|
97
|
+
export function deriveNameFromUrl(url) {
|
|
98
|
+
const trimmed = url.replace(/\.git\/?$/, '').replace(/\/$/, '');
|
|
99
|
+
const last = trimmed.split('/').pop();
|
|
100
|
+
if (!last)
|
|
101
|
+
return url;
|
|
102
|
+
return last;
|
|
103
|
+
}
|
|
@@ -12,13 +12,13 @@ import { CONFIG_FILE } from '../types.js';
|
|
|
12
12
|
import { listDirs, pathExists, readJsonIfExists } from './fs-utils.js';
|
|
13
13
|
import { readPluginManifest } from './manifest.js';
|
|
14
14
|
import { awaitBundleSwap, bundleSwapInFlightElsewhere } from './plugin-swap-lock.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { isCloudRole, requestPersonId } from './canvas/person-state.js';
|
|
16
|
+
import { materializedPluginsDir } from './scoped-state/materialized-plugins.js';
|
|
17
|
+
import { readCachedSettings } from './scoped-state/person-store.js';
|
|
17
18
|
import { pluginsDir } from './scope.js';
|
|
18
19
|
function pluginConfigForRoot(scope, root) {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
? readRawSettings({ store, key: 'user', kind: 'user', root: store.root })
|
|
20
|
+
const cfg = scope === 'user'
|
|
21
|
+
? readCachedSettings('user').settings
|
|
22
22
|
: readJsonIfExists(join(root, CONFIG_FILE));
|
|
23
23
|
return cfg && cfg.plugins && typeof cfg.plugins === 'object' ? cfg.plugins : {};
|
|
24
24
|
}
|
|
@@ -41,8 +41,13 @@ function swapsToWaitOut(scopeRootPath, cfg, present) {
|
|
|
41
41
|
return names.filter((name) => !present.has(name) && bundleSwapInFlightElsewhere(scopeRootPath, name));
|
|
42
42
|
}
|
|
43
43
|
export function listInstalledPluginsInRoot(scope, scopeRootPath) {
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// A cloud-role process serves many people and holds no plugin directories of its own: it reads the bytes it materialized for the
|
|
45
|
+
// request's person, and none outside a request.
|
|
46
|
+
const person = scope === 'user' && isCloudRole() ? requestPersonId() : undefined;
|
|
47
|
+
if (scope === 'user' && isCloudRole() && person === undefined)
|
|
48
|
+
return [];
|
|
49
|
+
const dir = scope !== 'user' ? join(scopeRootPath, 'plugins') : person !== undefined ? materializedPluginsDir(person) : pluginsDir('user');
|
|
50
|
+
if (dir === undefined || !pathExists(dir))
|
|
46
51
|
return [];
|
|
47
52
|
const cfg = pluginConfigForRoot(scope, scopeRootPath);
|
|
48
53
|
const first = collectInstalledPlugins(scope, dir, cfg);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export declare const ANTHROPIC_PROVIDER_ID = "anthropic";
|
|
2
|
+
export declare const OPENAI_CODEX_PROVIDER_ID = "openai-codex";
|
|
3
|
+
/** pi's built-in `openai` provider signed in with a ChatGPT account (dynamic OAuth client). Distinct from Codex: its own pool, credential file and sign-in. */
|
|
4
|
+
export declare const OPENAI_CHATGPT_PROVIDER_ID = "openai";
|
|
5
|
+
/** Longest a recorded 429 keeps an account or model family cooling, whatever the provider's Retry-After or window reset says: after this the next real call probes the provider, and a success clears the row. */
|
|
6
|
+
export declare const MAX_RATE_LIMIT_COOLDOWN_MS: number;
|
|
7
|
+
export declare const INVALID_REFRESH_TOKEN_BACKOFF_MS: number;
|
|
8
|
+
export type ManagedProviderId = typeof ANTHROPIC_PROVIDER_ID | typeof OPENAI_CODEX_PROVIDER_ID | typeof OPENAI_CHATGPT_PROVIDER_ID;
|
|
9
|
+
export declare const MANAGED_PROVIDER_IDS: readonly ManagedProviderId[];
|
|
10
|
+
export type ModelStrength = 'ultra' | 'strong' | 'medium' | 'light';
|
|
11
|
+
export type RotationConfig = {
|
|
12
|
+
defaultFallbackStrength?: ModelStrength;
|
|
13
|
+
revertWhenAvailable?: boolean;
|
|
14
|
+
};
|
|
15
|
+
/** `rateLimitedUntil` cools the whole account; `modelCooldowns` cools one model family on it (family → epoch ms, stored rows including expired ones). Read both through `coolingUntilFor`. */
|
|
16
|
+
export type SubscriptionCredential = {
|
|
17
|
+
label: string;
|
|
18
|
+
refresh: string;
|
|
19
|
+
access: string;
|
|
20
|
+
expires: number;
|
|
21
|
+
rateLimitedUntil: number;
|
|
22
|
+
lastAttemptAt: number;
|
|
23
|
+
lastRateLimitedAt: number;
|
|
24
|
+
authFailure?: 'invalid_grant';
|
|
25
|
+
accountId?: string;
|
|
26
|
+
clientId?: string;
|
|
27
|
+
modelCooldowns?: Record<string, number>;
|
|
28
|
+
};
|
|
29
|
+
export type ProviderCandidate = {
|
|
30
|
+
providerId: string;
|
|
31
|
+
modelId: string;
|
|
32
|
+
thinkingLevel?: string;
|
|
33
|
+
registered: boolean;
|
|
34
|
+
authenticated: boolean;
|
|
35
|
+
coolingUntil?: number;
|
|
36
|
+
automaticFallback?: boolean;
|
|
37
|
+
routeId?: string;
|
|
38
|
+
credentialSource?: string;
|
|
39
|
+
family?: string;
|
|
40
|
+
strength?: ModelStrength;
|
|
41
|
+
rank?: number;
|
|
42
|
+
};
|
|
43
|
+
export type ProviderCandidateRejection = 'unregistered' | 'non-managed-fallback' | 'unauthenticated' | 'cooling';
|
|
44
|
+
/** When this account can next serve `modelId`: the later of its account-wide cooldown and the cooldown on that model's family. Without a model only the account-wide cooldown applies. 0 or a past time means ready. */
|
|
45
|
+
export declare function coolingUntilFor(entry: Pick<SubscriptionCredential, 'rateLimitedUntil' | 'modelCooldowns'>, modelId?: string): number;
|
|
46
|
+
/** Every cooldown on an account still in force at `now`: `scope` is `account` or the model family. */
|
|
47
|
+
export declare function activeCooldowns(entry: Pick<SubscriptionCredential, 'rateLimitedUntil' | 'modelCooldowns'>, now?: number): Array<{
|
|
48
|
+
scope: string;
|
|
49
|
+
until: number;
|
|
50
|
+
}>;
|
|
51
|
+
/** Human text for an account's active cooldowns (`cooldown 12m`, `opus cooldown 3d 4h`), or undefined when it is ready. */
|
|
52
|
+
export declare function formatCooldowns(entry: Pick<SubscriptionCredential, 'rateLimitedUntil' | 'modelCooldowns'>, now?: number): string | undefined;
|
|
53
|
+
/** Whether a successful call on `modelId` has stored cooldown or auth health to clear; callers skip the write otherwise (every provider write bumps provider_revision). */
|
|
54
|
+
export declare function subscriptionNeedsSuccessWrite(entry: Pick<SubscriptionCredential, 'rateLimitedUntil' | 'authFailure' | 'modelCooldowns'>, modelId?: string): boolean;
|
|
55
|
+
export declare function isManagedProvider(providerId: string | undefined): providerId is ManagedProviderId;
|
|
56
|
+
export declare function getProviderLabel(providerId: ManagedProviderId): string;
|
|
57
|
+
export declare function resolveProviderCandidates(candidates: readonly ProviderCandidate[], now?: number, options?: {
|
|
58
|
+
pinnedToFirst?: boolean;
|
|
59
|
+
}): {
|
|
60
|
+
candidates: ProviderCandidate[];
|
|
61
|
+
rejected: Array<{
|
|
62
|
+
candidate: ProviderCandidate;
|
|
63
|
+
reason: ProviderCandidateRejection;
|
|
64
|
+
}>;
|
|
65
|
+
};
|
|
66
|
+
export declare class DuplicateSubscriptionError extends Error {
|
|
67
|
+
constructor(message: string);
|
|
68
|
+
}
|
|
69
|
+
export declare function isInvalidRefreshTokenError(error: unknown): boolean;
|
|
70
|
+
export declare function parseRetryAfterHeader(value: string | undefined, now?: number): number | undefined;
|
|
71
|
+
export declare function retryAfterMsFromHeaders(headers: Headers | Record<string, string> | undefined): number | undefined;
|
|
72
|
+
export declare function retryAfterMsFromError(error: unknown): number | undefined;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Pure managed-provider vocabulary: ids, credential/candidate types, cooldown arithmetic, retry-after parsing.
|
|
2
|
+
// No database or secrets imports, so CLI entrypoints can use it; the pool rows live in subscription-state.ts (daemon only).
|
|
3
|
+
import { modelCooldownFamily } from './rate-limit-scope.js';
|
|
4
|
+
export const ANTHROPIC_PROVIDER_ID = 'anthropic';
|
|
5
|
+
export const OPENAI_CODEX_PROVIDER_ID = 'openai-codex';
|
|
6
|
+
/** pi's built-in `openai` provider signed in with a ChatGPT account (dynamic OAuth client). Distinct from Codex: its own pool, credential file and sign-in. */
|
|
7
|
+
export const OPENAI_CHATGPT_PROVIDER_ID = 'openai';
|
|
8
|
+
/** Longest a recorded 429 keeps an account or model family cooling, whatever the provider's Retry-After or window reset says: after this the next real call probes the provider, and a success clears the row. */
|
|
9
|
+
export const MAX_RATE_LIMIT_COOLDOWN_MS = 10 * 60 * 1000;
|
|
10
|
+
export const INVALID_REFRESH_TOKEN_BACKOFF_MS = 30 * 24 * 60 * 60 * 1000;
|
|
11
|
+
export const MANAGED_PROVIDER_IDS = [ANTHROPIC_PROVIDER_ID, OPENAI_CODEX_PROVIDER_ID, OPENAI_CHATGPT_PROVIDER_ID];
|
|
12
|
+
const PROVIDERS = { anthropic: { label: 'Claude' }, 'openai-codex': { label: 'OpenAI Codex' }, openai: { label: 'ChatGPT' } };
|
|
13
|
+
/** When this account can next serve `modelId`: the later of its account-wide cooldown and the cooldown on that model's family. Without a model only the account-wide cooldown applies. 0 or a past time means ready. */
|
|
14
|
+
export function coolingUntilFor(entry, modelId) { const family = modelCooldownFamily(modelId); return Math.max(entry.rateLimitedUntil || 0, family === undefined ? 0 : entry.modelCooldowns?.[family] ?? 0); }
|
|
15
|
+
/** Every cooldown on an account still in force at `now`: `scope` is `account` or the model family. */
|
|
16
|
+
export function activeCooldowns(entry, now = Date.now()) { return [...((entry.rateLimitedUntil || 0) > now ? [{ scope: 'account', until: entry.rateLimitedUntil }] : []), ...Object.entries(entry.modelCooldowns ?? {}).filter(([, until]) => until > now).map(([scope, until]) => ({ scope, until }))]; }
|
|
17
|
+
/** Human text for an account's active cooldowns (`cooldown 12m`, `opus cooldown 3d 4h`), or undefined when it is ready. */
|
|
18
|
+
export function formatCooldowns(entry, now = Date.now()) { const parts = activeCooldowns(entry, now).map(({ scope, until }) => `${scope === 'account' ? '' : `${scope} `}cooldown ${formatWait(until - now)}`); return parts.length ? parts.join(', ') : undefined; }
|
|
19
|
+
function formatWait(ms) { const minutes = Math.max(1, Math.ceil(ms / 60_000)); if (minutes < 60)
|
|
20
|
+
return `${minutes}m`; const hours = Math.floor(minutes / 60); if (hours < 24)
|
|
21
|
+
return `${hours}h${minutes % 60 ? ` ${minutes % 60}m` : ''}`; return `${Math.floor(hours / 24)}d${hours % 24 ? ` ${hours % 24}h` : ''}`; }
|
|
22
|
+
function hasModelCooldownRow(entry, modelId) { const family = modelCooldownFamily(modelId); return family !== undefined && entry.modelCooldowns?.[family] !== undefined; }
|
|
23
|
+
/** Whether a successful call on `modelId` has stored cooldown or auth health to clear; callers skip the write otherwise (every provider write bumps provider_revision). */
|
|
24
|
+
export function subscriptionNeedsSuccessWrite(entry, modelId) { return Boolean(entry.rateLimitedUntil || entry.authFailure) || hasModelCooldownRow(entry, modelId); }
|
|
25
|
+
export function isManagedProvider(providerId) { return providerId === ANTHROPIC_PROVIDER_ID || providerId === OPENAI_CODEX_PROVIDER_ID || providerId === OPENAI_CHATGPT_PROVIDER_ID; }
|
|
26
|
+
export function getProviderLabel(providerId) { return PROVIDERS[providerId].label; }
|
|
27
|
+
export function resolveProviderCandidates(candidates, now = Date.now(), options = {}) { const viable = []; const rejected = []; for (let i = 0; i < candidates.length; i++) {
|
|
28
|
+
const candidate = candidates[i];
|
|
29
|
+
const reason = !candidate.registered ? 'unregistered' : candidate.automaticFallback === false ? 'non-managed-fallback' : !candidate.authenticated ? 'unauthenticated' : (candidate.coolingUntil ?? 0) > now ? 'cooling' : undefined;
|
|
30
|
+
if (!reason)
|
|
31
|
+
viable.push(candidate);
|
|
32
|
+
else {
|
|
33
|
+
rejected.push({ candidate, reason });
|
|
34
|
+
if (options.pinnedToFirst && i === 0 && reason !== 'cooling')
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
} return { candidates: viable, rejected }; }
|
|
38
|
+
export class DuplicateSubscriptionError extends Error {
|
|
39
|
+
constructor(message) { super(message); this.name = 'DuplicateSubscriptionError'; }
|
|
40
|
+
}
|
|
41
|
+
export function isInvalidRefreshTokenError(error) { const value = error; const message = [value?.errorMessage, value?.message, error instanceof Error ? error.message : ''].filter((part) => typeof part === 'string').join(' '); const status = typeof value?.status === 'number' ? value.status : Number(message.match(/\bstatus[=:]\s*(\d+)/i)?.[1]); return status === 400 && /invalid_grant|refresh token .*invalid|refresh token .*not found/i.test(message); }
|
|
42
|
+
export function parseRetryAfterHeader(value, now = Date.now()) { if (!value?.trim())
|
|
43
|
+
return undefined; const seconds = Number(value.trim()); if (Number.isFinite(seconds))
|
|
44
|
+
return Math.max(0, Math.round(seconds * 1000)); const parsed = Date.parse(value); return Number.isFinite(parsed) ? Math.max(0, parsed - now) : undefined; }
|
|
45
|
+
export function retryAfterMsFromHeaders(headers) {
|
|
46
|
+
if (!headers)
|
|
47
|
+
return undefined;
|
|
48
|
+
const get = (name) => headers instanceof Headers ? headers.get(name) ?? undefined : Object.entries(headers).find(([key]) => key.toLowerCase() === name)?.[1];
|
|
49
|
+
const millis = get('retry-after-ms');
|
|
50
|
+
if (millis !== undefined && millis.trim() !== '' && Number.isFinite(Number(millis)))
|
|
51
|
+
return Math.max(0, Number(millis));
|
|
52
|
+
return parseRetryAfterHeader(get('retry-after'));
|
|
53
|
+
}
|
|
54
|
+
export function retryAfterMsFromError(error) {
|
|
55
|
+
if (!error || typeof error !== 'object' || !('headers' in error))
|
|
56
|
+
return undefined;
|
|
57
|
+
const headers = error.headers;
|
|
58
|
+
if (!(headers instanceof Headers) && (!headers || typeof headers !== 'object' || Array.isArray(headers)))
|
|
59
|
+
return undefined;
|
|
60
|
+
return retryAfterMsFromHeaders(headers);
|
|
61
|
+
}
|
package/dist/core/plugin-env.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { validateEnvVarName } from './env-name.js';
|
|
2
|
-
import { isReservedEnvName } from './profiles/env-store.js';
|
|
1
|
+
import { isReservedEnvName, validateEnvVarName } from './env-name.js';
|
|
3
2
|
export function invalidPluginEnvReasons(raw) {
|
|
4
3
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
5
4
|
return ['env must be an object mapping variable names to declarations'];
|
|
@@ -55,11 +55,7 @@ export declare function resolveProfileOperand(operand: string): ProfileEntry;
|
|
|
55
55
|
/** `resolveProfileOperand` over any candidate list — the CLI passes the
|
|
56
56
|
* profiles the daemon lists for its caller. */
|
|
57
57
|
export declare function matchProfileOperand<T>(all: readonly T[], operand: string, key: CandidateOf<T>): T;
|
|
58
|
-
|
|
59
|
-
* mutations (create/rename/add-project/remove-project/delete/last-used)
|
|
60
|
-
* below run inside this. */
|
|
61
|
-
export declare function withProfileManifestLock<T>(profileId: string, fn: () => T): T;
|
|
62
|
-
export declare function ensureRootProfile(): ProfileEntry;
|
|
58
|
+
export declare function ensureRootProfile(): Promise<ProfileEntry>;
|
|
63
59
|
/** The env name a metadata entry surfaces as (spawn-env source F). Also the
|
|
64
60
|
* uniqueness domain for keys: the fold is lossy (`a-b`, `a_b`, `A_B` →
|
|
65
61
|
* `CRTR_PROFILE_META_A_B`), so two stored keys may not share a fold. */
|
|
@@ -77,9 +73,9 @@ export declare function createProfile(name: string, projects?: ProfileProject[],
|
|
|
77
73
|
defaultKind?: string;
|
|
78
74
|
metadata?: Record<string, string>;
|
|
79
75
|
grantee?: string;
|
|
80
|
-
}): ProfileEntry
|
|
81
|
-
export declare function ensureAppProfile(grantee: string): ProfileEntry
|
|
82
|
-
export declare function updateProfileLastUsed(profileId: string): ProfileEntry
|
|
76
|
+
}): Promise<ProfileEntry>;
|
|
77
|
+
export declare function ensureAppProfile(grantee: string): Promise<ProfileEntry>;
|
|
78
|
+
export declare function updateProfileLastUsed(profileId: string): Promise<ProfileEntry>;
|
|
83
79
|
/** Paused check for an ALREADY-RESOLVED durable id (a canvas row's `profile_id`,
|
|
84
80
|
* `CRTR_PROFILE_ID`) — exact-id only, no name matching, so it costs one small
|
|
85
81
|
* read on the hot delivery paths that call it every poll. Fails OPEN: an id
|
|
@@ -92,14 +88,14 @@ export declare function pausedCanvasProfiles(): Set<string>;
|
|
|
92
88
|
* every gate that already holds a loaded manifest. */
|
|
93
89
|
export declare function assertProfileEntryActive(entry: ProfileEntry): void;
|
|
94
90
|
export declare function assertProfileActive(profileId: string | null | undefined): ProfileEntry | null;
|
|
95
|
-
export declare function pauseProfile(profileId: string): ProfileEntry
|
|
96
|
-
export declare function resumeProfile(profileId: string): ProfileEntry & {
|
|
91
|
+
export declare function pauseProfile(profileId: string): Promise<ProfileEntry>;
|
|
92
|
+
export declare function resumeProfile(profileId: string): Promise<ProfileEntry & {
|
|
97
93
|
pausedAt: string | null;
|
|
98
|
-
}
|
|
99
|
-
export declare function setProfileDefaultKind(profileId: string, defaultKind: string): ProfileEntry
|
|
94
|
+
}>;
|
|
95
|
+
export declare function setProfileDefaultKind(profileId: string, defaultKind: string): Promise<ProfileEntry>;
|
|
100
96
|
/** Merge `set` entries over the stored map and drop `unset` keys; the
|
|
101
97
|
* `metadata` field is omitted entirely when the result is empty. */
|
|
102
|
-
export declare function updateProfileMetadata(profileId: string, set: Record<string, string>, unset?: string[]): ProfileEntry
|
|
98
|
+
export declare function updateProfileMetadata(profileId: string, set: Record<string, string>, unset?: string[]): Promise<ProfileEntry>;
|
|
103
99
|
/** A profile's stored metadata for broker-env injection — sanitized entry by
|
|
104
100
|
* entry (a manifest is hand-editable, so a bad key/value is dropped rather
|
|
105
101
|
* than trusted) and never throwing: a stale/invalid/absent profile id is a
|
|
@@ -109,12 +105,12 @@ export declare function readProfileMetadata(profileId: string | null): Record<st
|
|
|
109
105
|
/** Change the manifest `name` only. Callers that own the canvas (the daemon's
|
|
110
106
|
* `PATCH /v1/profiles/:name`) must also rename the profile's canvas handle —
|
|
111
107
|
* use `renameProfileAndHandle` there; the CLI and panels go through that route. */
|
|
112
|
-
export declare function renameProfile(profileId: string, name: string): ProfileEntry
|
|
108
|
+
export declare function renameProfile(profileId: string, name: string): Promise<ProfileEntry>;
|
|
113
109
|
/** A new path appends; a path already listed keeps its position. */
|
|
114
|
-
export declare function addProfileProject(profileId: string, dir: string): ProfileEntry
|
|
110
|
+
export declare function addProfileProject(profileId: string, dir: string): Promise<ProfileEntry>;
|
|
115
111
|
/** Set one owner's automatic delivery limit. `content` is the uncapped level,
|
|
116
112
|
* so it removes the owner's entry instead of storing it. */
|
|
117
|
-
export declare function setProfileDeliveryLimit(profileId: string, owner: string, level: ProfileProjectMemory): ProfileEntry
|
|
118
|
-
export declare function removeProfileProject(profileId: string, dir: string): ProfileEntry
|
|
119
|
-
export declare function deleteProfile(profileId: string): void
|
|
113
|
+
export declare function setProfileDeliveryLimit(profileId: string, owner: string, level: ProfileProjectMemory): Promise<ProfileEntry>;
|
|
114
|
+
export declare function removeProfileProject(profileId: string, dir: string): Promise<ProfileEntry>;
|
|
115
|
+
export declare function deleteProfile(profileId: string): Promise<void>;
|
|
120
116
|
export {};
|