pi-plus-sdk 0.1.4 → 0.1.5

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 CHANGED
@@ -4,6 +4,11 @@
4
4
 
5
5
  ### Added
6
6
 
7
+ - Runtime settings layering for hub profiles applies to SDK hosts with no environment plumbing (shared `loader/redirects.mjs` entry for `coding-agent/src/core/settings-manager.ts`): whenever the agent dir passed to `SettingsManager.create` is a hub-materialized profile dir (a direct child of `~/.pi/pi-hub/profiles`, honoring `PI_HUB_PI_DIR`/`PI_HUB_DIR`; the pipi-set `PI_PLUS_BASE_AGENT_DIR` marker also still activates it), the global scope reads the base agent `settings.json` deep-merged under the profile `settings.json` with the profile winning, and writes route per top-level key — `defaultProvider`/`defaultModel` to the profile file, everything else to the agent file with any profile shadow cleared. Any other agent dir keeps `SettingsManager.create` behaving exactly as upstream.
8
+ - The pi-plus context settings are re-exported from the SDK surface (`packages/plus/src/context/threshold-setting.ts`): `readPlusSettings/writePlusSettings/getPlusSettingsPath` for the pi-plus settings store next to `settings.json`, plus the typed getters/setters and UI formatters behind the pipi `/settings` rows — `get/setAutoCompactThresholdPercent` (default 80%), `get/setContextFloorTokens` (default/built-in minimum 13000), and `get/setContextWindowCapTokens` (undefined = no cap). Embedding hosts get the same knobs the TUI edits, against the same file detection actually reads. `SessionManager.deleteSession` and `SessionManager.search` (new in the re-exported upstream surface) cover transcript deletion and title/content search for history-management UIs.
7
9
  - Initial **`pi-plus-sdk`** npm artifact (published from this package): the programmatic pi-plus entry, split out of the `pi-plus` package so library hosts embed pi in-process with zero CLI logic. `import { createPlusAgentSession, createPlusUIContext, plusSdkExtensionFactories } from "pi-plus-sdk"` gives a host application (e.g. a desktop app) the compaction/context/reasoning overrides (baked in at bundle time by the shared redirect plugin) plus the nine non-TUI pi-plus extensions (subagent, tasks, memory, plan, ask-user, hooks, context-guard, /cd, /init). `createPlusAgentSession()` composes the upstream `createAgentSession` with a `DefaultResourceLoader` carrying those factories, reloads it, and binds extensions once (emitting `session_start`); hosts pass dialog handlers via the `ui` option to get working `ask_user` questions (select/confirm/input bridged to native UI), or omit it for a headless session. The staged package has an `exports` map (deep imports closed off), ships a hand-authored `api.d.ts` re-exporting `@earendil-works/pi-coding-agent` types (added as an exact-pinned dependency for type resolution only; the runtime stays self-contained), and has no bin. Hosts without a pi CLI on PATH should pass `excludeTools: ["subagent"]`: the subagent tool launches a pi subprocess and could otherwise relaunch the host app itself.
8
- - The `/init` extension (shared core, `packages/plus/src/extensions/init/`) is registered by `plusSdkExtensionFactories` too: the `/init` command creates or improves `PI.md` at the cwd root and `PI.md` is auto-loaded into every agent run via the system prompt's context files.
10
+ - The `/init` extension (shared core, `packages/plus/src/extensions/init/`) is registered by `plusSdkExtensionFactories` too: the `/init` command creates or improves `AGENTS.md` at the cwd root — pi's default project instructions file, loaded automatically by upstream's context loader.
11
+ - `createPlusAgentSessionRuntime(options?)` — a runtime-backed sibling of `createPlusAgentSession` for embedding hosts that need the CLI's session-replacement machinery. It composes `createAgentSessionServices`/`createAgentSessionFromServices`/`createAgentSessionRuntime` (the same layering the CLI's main uses, trimmed to host concerns) with the pi-plus extension factories and binding rules: extensions are bound exactly once per session through the rebind hook (initial bind plus every `switchSession`/`fork`/`newSession` replacement, emitting one `session_start` each with the matching reason), and the default `commandContextActions` route `waitForIdle`/`newSession`/`fork`/`navigateTree`/`switchSession`/`reload` to the runtime + live session, which is what makes `/cd` (and `/clone`-style `runtime.fork(leafId, { position: "at" })`) work in-process. The live session is `runtime.session` — hosts read it after each replacement or subscribe in the new `onRebind` callback; the initial session defaults to `SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir))`, and `projectTrusted` (default true) plus `resourceLoaderOptions` passthroughs cover per-cwd settings and loader customization. The coding-agent barrel additionally exports `getDefaultSessionDir` (plus the `DefaultResourceLoaderOptions` type) to support this entry.
12
+ - `addProfileModel(name, model)` and `removeProfileModel(name, model)` join the curated profile surface (implemented in `@earendil-works/pi-hub`): append a model without selecting it (bounded by the same three-models-per-profile limit) and remove one with the `pipi` CLI delete semantics — the default promotes to the next remaining model, and removing the last clears `models`/`model` so the profile inherits. Both return a human-readable message for host UIs, completing the per-profile model editing that `setProfileDefaultModel` started.
13
+ - `setProfileDefaultModel(name, model)` joins the curated profile surface (implemented in `@earendil-works/pi-hub`): selects a profile's default model — the list position 1 that materialization writes as `settings.defaultModel` — moving an existing model or adding a new one (bounded by the same three-models-per-profile limit the `pipi` CLI enforces), and returns a human-readable message for host UIs. This is the library entry for the "select model" behavior that previously lived only in the CLI command layer.
9
14
  - The SDK build redirects the upstream `main.ts` to a stub that throws — the CLI entry graph (startup UI, session picker, auth command, …) stays out of `api.js`, and calling `main()` is a clear error instead of silently running un-layered upstream pi. The re-exported `parseArgs`/`SettingsSelectorComponent` resolve to upstream pi (no pipi help text or CLI settings rows leak into the library surface).
package/README.md CHANGED
@@ -32,11 +32,21 @@ await session.prompt("Review this repository");
32
32
  completion, no pipi help text, no banner/vim/tab-title/plain-tools.
33
33
  - Profile management ships as a library: the curated `@earendil-works/pi-hub`
34
34
  surface is re-exported (`src/profiles.ts`) — `loadProfiles/findProfile/
35
- addProfile/updateProfile/removeProfile/renameProfile/setDefaultProfile/
36
- clearDefaultProfile/getDefaultProfileName` for `~/.pi/profiles.json`, and
35
+ addProfile/addProfileModel/updateProfile/removeProfile/removeProfileModel/
36
+ renameProfile/setDefaultProfile/clearDefaultProfile/getDefaultProfileName/
37
+ setProfileDefaultModel` for
38
+ `~/.pi/profiles.json`, and
37
39
  `materializeProfile/removeProfileDir/profileDirFor/AGENT_DIR/
38
40
  syncProfilePackagesToSource` for the per-profile agent dirs. Hosts get the
39
41
  exact contract the `pipi` CLI dispatches instead of reimplementing it.
42
+ - The pi-plus context settings ship as a library: `readPlusSettings/writePlusSettings`
43
+ and the `get/setAutoCompactThresholdPercent`, `get/setContextFloorTokens`,
44
+ `get/setContextWindowCapTokens` helpers (plus formatters/parsers) manage the
45
+ pi-plus settings store (`<agentDir>/pi-plus-settings.json`) that drives
46
+ auto-compaction trigger math — the same file the pipi TUI `/settings` rows edit.
47
+ Session history management completes the surface: `SessionManager.listAll` to
48
+ enumerate, `SessionManager.search(query)` to match titles and transcript text, and
49
+ `SessionManager.deleteSession(path)` to remove a transcript.
40
50
  - Provider login ships as a library (`src/auth.ts`): `loginProvider(providerId, { agentDir?, interaction?, signal?, method? })`
41
51
  runs pi's model-runtime login flow (OAuth login page / API-key setup — the same
42
52
  flow `pipi profile add <name> -p <provider>` triggers) and persists the credential
@@ -50,6 +60,21 @@ await session.prompt("Review this repository");
50
60
  `session.bindExtensions()` yourself. Pass `ui` dialog handlers to get working
51
61
  `ask_user` questions (bound with mode `"rpc"`, so the tool falls back to sequential
52
62
  select/input/confirm dialogs bridged to your UI); omit `ui` for a headless session.
63
+ - `createPlusAgentSessionRuntime()` returns an `AgentSessionRuntime` instead of a
64
+ single session: the same pi-plus layer and one-bind-per-session rules, plus the
65
+ CLI's session-replacement machinery — `runtime.cd`-style switches
66
+ (`switchSession`), `newSession`, and `fork` (with `{position: "at"}` for the CLI's
67
+ in-place `/clone`). The pi-plus `/cd`/`/init` extensions and anything else needing
68
+ `commandContextActions` work out of the box: the factory wires runtime-routed
69
+ actions by default (override via the `commandContextActions` option). The live
70
+ session is `runtime.session` and **changes on every replacement** — re-subscribe in
71
+ the `onRebind` callback (it also fires for the initial bind) and never call
72
+ `bindExtensions()` yourself. `runtime.dispose()` is async and emits
73
+ `session_shutdown`.
74
+ - `session.setAutoCompactionEnabled(bool)` writes the **global**
75
+ `<agentDir>/settings.json` compaction flag (not a per-session setting), so a host
76
+ toggle labeled per-session would surprise users — it also flips behavior for the
77
+ `pipi` CLI sharing that agent dir.
53
78
  - The staged `package.json` has an `exports` map, so deep imports are not reachable;
54
79
  `api.d.ts` re-exports the `@earendil-works/pi-coding-agent` types (exact-pinned
55
80
  dependency, type resolution only — the runtime is self-contained).
package/anthropic.js ADDED
@@ -0,0 +1,74 @@
1
+ var __require=(x=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(x,{get:(a,b)=>(typeof require<"u"?require:a)[b]}):x)(function(x){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+x+'" is not supported')});var procEnvCache=null;function getBunSandboxEnvValue(name){if(!(typeof process>"u"||!process.versions?.bun||Object.keys(process.env).length>0)){if(procEnvCache===null){procEnvCache=new Map;try{let{readFileSync}=__require("node:fs"),data=readFileSync("/proc/self/environ","utf-8");for(let entry of data.split("\0")){let idx=entry.indexOf("=");idx>0&&procEnvCache.set(entry.slice(0,idx),entry.slice(idx+1))}}catch{}}return procEnvCache.get(name)}}function getProviderEnvValue(name,env){return env?.[name]||(typeof process<"u"?process.env[name]:void 0)||getBunSandboxEnvValue(name)||void 0}var LOGO_SVG='<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800" aria-hidden="true"><path fill="#fff" fill-rule="evenodd" d="M165.29 165.29 H517.36 V400 H400 V517.36 H282.65 V634.72 H165.29 Z M282.65 282.65 V400 H400 V282.65 Z"/><path fill="#fff" d="M517.36 400 H634.72 V634.72 H517.36 Z"/></svg>';function escapeHtml(value){return value.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}function renderPage(options){let title=escapeHtml(options.title),heading=escapeHtml(options.heading),message=escapeHtml(options.message),details=options.details?escapeHtml(options.details):void 0;return`<!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>${title}</title>
7
+ <style>
8
+ :root {
9
+ --text: #fafafa;
10
+ --text-dim: #a1a1aa;
11
+ --page-bg: #09090b;
12
+ --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
13
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
14
+ }
15
+ * { box-sizing: border-box; }
16
+ html { color-scheme: dark; }
17
+ body {
18
+ margin: 0;
19
+ min-height: 100vh;
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ padding: 24px;
24
+ background: var(--page-bg);
25
+ color: var(--text);
26
+ font-family: var(--font-sans);
27
+ text-align: center;
28
+ }
29
+ main {
30
+ width: 100%;
31
+ max-width: 560px;
32
+ display: flex;
33
+ flex-direction: column;
34
+ align-items: center;
35
+ justify-content: center;
36
+ }
37
+ .logo {
38
+ width: 72px;
39
+ height: 72px;
40
+ display: block;
41
+ margin-bottom: 24px;
42
+ }
43
+ h1 {
44
+ margin: 0 0 10px;
45
+ font-size: 28px;
46
+ line-height: 1.15;
47
+ font-weight: 650;
48
+ color: var(--text);
49
+ }
50
+ p {
51
+ margin: 0;
52
+ line-height: 1.7;
53
+ color: var(--text-dim);
54
+ font-size: 15px;
55
+ }
56
+ .details {
57
+ margin-top: 16px;
58
+ font-family: var(--font-mono);
59
+ font-size: 13px;
60
+ color: var(--text-dim);
61
+ white-space: pre-wrap;
62
+ word-break: break-word;
63
+ }
64
+ </style>
65
+ </head>
66
+ <body>
67
+ <main>
68
+ <div class="logo">${LOGO_SVG}</div>
69
+ <h1>${heading}</h1>
70
+ <p>${message}</p>
71
+ ${details?`<div class="details">${details}</div>`:""}
72
+ </main>
73
+ </body>
74
+ </html>`}function oauthSuccessHtml(message){return renderPage({title:"Authentication successful",heading:"Authentication successful",message})}function oauthErrorHtml(message,details){return renderPage({title:"Authentication failed",heading:"Authentication failed",message,details})}function base64urlEncode(bytes){let binary="";for(let byte of bytes)binary+=String.fromCharCode(byte);return btoa(binary).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}async function generatePKCE(){let verifierBytes=new Uint8Array(32);crypto.getRandomValues(verifierBytes);let verifier=base64urlEncode(verifierBytes),data=new TextEncoder().encode(verifier),hashBuffer=await crypto.subtle.digest("SHA-256",data),challenge=base64urlEncode(new Uint8Array(hashBuffer));return{verifier,challenge}}var nodeApis=null,nodeApisPromise=null,decode=s=>atob(s),CLIENT_ID=decode("OWQxYzI1MGEtZTYxYi00NGQ5LTg4ZWQtNTk0NGQxOTYyZjVl"),AUTHORIZE_URL="https://claude.ai/oauth/authorize",TOKEN_URL="https://platform.claude.com/v1/oauth/token",CALLBACK_HOST=getProviderEnvValue("PI_OAUTH_CALLBACK_HOST")||"127.0.0.1",CALLBACK_PORT=53692,CALLBACK_PATH="/callback",REDIRECT_URI=`http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`,SCOPES="org:create_api_key user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";async function getNodeApis(){if(nodeApis)return nodeApis;if(!nodeApisPromise){if(typeof process>"u"||!process.versions?.node&&!process.versions?.bun)throw new Error("Anthropic OAuth is only available in Node.js environments");nodeApisPromise=import("node:http").then(httpModule=>({createServer:httpModule.createServer}))}return nodeApis=await nodeApisPromise,nodeApis}function parseAuthorizationInput(input){let value=input.trim();if(!value)return{};try{let url=new URL(value);return{code:url.searchParams.get("code")??void 0,state:url.searchParams.get("state")??void 0}}catch{}if(value.includes("#")){let[code,state]=value.split("#",2);return{code,state}}if(value.includes("code=")){let params=new URLSearchParams(value);return{code:params.get("code")??void 0,state:params.get("state")??void 0}}return{code:value}}function formatErrorDetails(error){if(error instanceof Error){let details=[`${error.name}: ${error.message}`],errorWithCode=error;return errorWithCode.code&&details.push(`code=${errorWithCode.code}`),typeof errorWithCode.errno<"u"&&details.push(`errno=${String(errorWithCode.errno)}`),typeof error.cause<"u"&&details.push(`cause=${formatErrorDetails(error.cause)}`),error.stack&&details.push(`stack=${error.stack}`),details.join("; ")}return String(error)}async function startCallbackServer(expectedState){let{createServer}=await getNodeApis();return new Promise((resolve,reject)=>{let settleWait,waitForCodePromise=new Promise(resolveWait=>{let settled=!1;settleWait=value=>{settled||(settled=!0,resolveWait(value))}}),server=createServer((req,res)=>{try{let url=new URL(req.url||"","http://localhost");if(url.pathname!==CALLBACK_PATH){res.writeHead(404,{"Content-Type":"text/html; charset=utf-8"}),res.end(oauthErrorHtml("Callback route not found."));return}let code=url.searchParams.get("code"),state=url.searchParams.get("state"),error=url.searchParams.get("error");if(error){res.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),res.end(oauthErrorHtml("Anthropic authentication did not complete.",`Error: ${error}`));return}if(!code||!state){res.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),res.end(oauthErrorHtml("Missing code or state parameter."));return}if(state!==expectedState){res.writeHead(400,{"Content-Type":"text/html; charset=utf-8"}),res.end(oauthErrorHtml("State mismatch."));return}res.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),res.end(oauthSuccessHtml("Anthropic authentication completed. You can close this window.")),settleWait?.({code,state})}catch{res.writeHead(500,{"Content-Type":"text/plain; charset=utf-8"}),res.end("Internal error")}});server.on("error",err=>{reject(err)}),server.listen(CALLBACK_PORT,CALLBACK_HOST,()=>{resolve({server,redirectUri:REDIRECT_URI,cancelWait:()=>{settleWait?.(null)},waitForCode:()=>waitForCodePromise})})})}async function postJson(url,body,signal){let response=await fetch(url,{method:"POST",headers:{"Content-Type":"application/json",Accept:"application/json"},body:JSON.stringify(body),signal:AbortSignal.any([signal,AbortSignal.timeout(3e4)])}),responseBody=await response.text();if(!response.ok)throw new Error(`HTTP request failed. status=${response.status}; url=${url}; body=${responseBody}`);return responseBody}async function exchangeAuthorizationCode(code,state,verifier,redirectUri,signal){let responseBody;try{responseBody=await postJson(TOKEN_URL,{grant_type:"authorization_code",client_id:CLIENT_ID,code,state,redirect_uri:redirectUri,code_verifier:verifier},signal)}catch(error){throw new Error(`Token exchange request failed. url=${TOKEN_URL}; redirect_uri=${redirectUri}; response_type=authorization_code; details=${formatErrorDetails(error)}`)}let tokenData;try{tokenData=JSON.parse(responseBody)}catch(error){throw new Error(`Token exchange returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`)}return{type:"oauth",refresh:tokenData.refresh_token,access:tokenData.access_token,expires:Date.now()+tokenData.expires_in*1e3-300*1e3}}async function loginAnthropic(interaction){let{verifier,challenge}=await generatePKCE(),server=await startCallbackServer(verifier),manualAbort=new AbortController,onAbort=()=>server.cancelWait();interaction.signal.addEventListener("abort",onAbort,{once:!0}),interaction.signal.aborted&&onAbort();let code,state,manualInput,manualError;try{let authParams=new URLSearchParams({code:"true",client_id:CLIENT_ID,response_type:"code",redirect_uri:REDIRECT_URI,scope:SCOPES,code_challenge:challenge,code_challenge_method:"S256",state:verifier});interaction.notify({type:"auth_url",url:`${AUTHORIZE_URL}?${authParams.toString()}`,instructions:"Complete login in your browser. If the browser is on another machine, paste the final redirect URL here."});let manualPromise=interaction.prompt({type:"manual_code",message:"Complete login in your browser, or paste the authorization code / redirect URL here:",placeholder:REDIRECT_URI,signal:manualAbort.signal}).then(input=>{manualInput=input,server.cancelWait()}).catch(error=>{manualError=error instanceof Error?error:new Error(String(error)),server.cancelWait()}),result=await server.waitForCode();if(manualError)throw manualError;if(result?.code)code=result.code,state=result.state;else if(manualInput){let parsed=parseAuthorizationInput(manualInput);if(parsed.state&&parsed.state!==verifier)throw new Error("OAuth state mismatch");code=parsed.code,state=parsed.state??verifier}if(!code){if(await manualPromise,manualError)throw manualError;if(manualInput){let parsed=parseAuthorizationInput(manualInput);if(parsed.state&&parsed.state!==verifier)throw new Error("OAuth state mismatch");code=parsed.code,state=parsed.state??verifier}}if(!code)throw new Error("Missing authorization code");if(!state)throw new Error("Missing OAuth state");return interaction.notify({type:"progress",message:"Exchanging authorization code for tokens..."}),exchangeAuthorizationCode(code,state,verifier,REDIRECT_URI,interaction.signal)}finally{interaction.signal.removeEventListener("abort",onAbort),manualAbort.abort(),server.server.close()}}async function refreshAnthropicToken(refreshToken,signal){let responseBody;try{responseBody=await postJson(TOKEN_URL,{grant_type:"refresh_token",client_id:CLIENT_ID,refresh_token:refreshToken},signal)}catch(error){throw new Error(`Anthropic token refresh request failed. url=${TOKEN_URL}; details=${formatErrorDetails(error)}`)}let data;try{data=JSON.parse(responseBody)}catch(error){throw new Error(`Anthropic token refresh returned invalid JSON. url=${TOKEN_URL}; body=${responseBody}; details=${formatErrorDetails(error)}`)}return{type:"oauth",refresh:data.refresh_token,access:data.access_token,expires:Date.now()+data.expires_in*1e3-300*1e3}}var anthropicOAuth={name:"Anthropic (Claude Pro/Max)",isSubscription:!0,login:loginAnthropic,refresh:(credential,signal)=>refreshAnthropicToken(credential.refresh,signal),async toAuth(credential){return{apiKey:credential.access}}};export{anthropicOAuth};
package/api.d.ts CHANGED
@@ -10,8 +10,11 @@
10
10
  export * from "@earendil-works/pi-coding-agent";
11
11
 
12
12
  import type {
13
+ AgentSession,
14
+ AgentSessionRuntime,
13
15
  CreateAgentSessionOptions,
14
16
  CreateAgentSessionResult,
17
+ DefaultResourceLoaderOptions,
15
18
  ExtensionCommandContextActions,
16
19
  ExtensionError,
17
20
  ExtensionUIContext,
@@ -43,6 +46,38 @@ export declare function createPlusAgentSession(
43
46
  options?: CreatePlusAgentSessionOptions,
44
47
  ): Promise<CreateAgentSessionResult>;
45
48
 
49
+ export interface CreatePlusAgentSessionRuntimeOptions extends CreateAgentSessionOptions {
50
+ extensionFactories?: InlineExtension[];
51
+ ui?: PlusUIDialogHandlers;
52
+ /** Overrides the runtime-routed actions (switchSession/fork/newSession/...)
53
+ * the factory wires by default, same shape as bindExtensions' binding. */
54
+ commandContextActions?: ExtensionCommandContextActions;
55
+ abortHandler?: () => void;
56
+ shutdownHandler?: () => void;
57
+ onError?: (error: ExtensionError) => void;
58
+ /** Called after every extension bind: the initial session and each session
59
+ * replacement. Re-subscribe to the new session here; the previous
60
+ * AgentSession object is disposed after a replacement. */
61
+ onRebind?: (session: AgentSession) => void | Promise<void>;
62
+ /** Project-trust flag for per-cwd SettingsManagers (default true). */
63
+ projectTrusted?: boolean;
64
+ /** Loader passthroughs (additionalSkillPaths, noSkills, systemPrompt, ...)
65
+ * applied on every recreation; cwd/agentDir/settingsManager/extensionFactories are owned by the factory. */
66
+ resourceLoaderOptions?: Omit<
67
+ DefaultResourceLoaderOptions,
68
+ "cwd" | "agentDir" | "settingsManager" | "extensionFactories"
69
+ >;
70
+ }
71
+
72
+ /** Runtime-backed createPlusAgentSession: same pi-plus layer and binding rules,
73
+ * but returns an AgentSessionRuntime with CLI-level session replacement
74
+ * (/cd, newSession, fork/clone, switchSession). The live session is
75
+ * `runtime.session` — it changes on every replacement; hosts must NOT call
76
+ * bindExtensions themselves. */
77
+ export declare function createPlusAgentSessionRuntime(
78
+ options?: CreatePlusAgentSessionRuntimeOptions,
79
+ ): Promise<AgentSessionRuntime>;
80
+
46
81
  /**
47
82
  * Profile management: the curated @earendil-works/pi-hub surface bundled into
48
83
  * api.js (profiles.json CRUD + per-profile materialized agent dirs). Types are
@@ -84,9 +119,21 @@ export declare function findProfile(name: string): Profile | undefined;
84
119
  export declare function getDefaultProfileName(): string | undefined;
85
120
  export declare function setDefaultProfile(name: string): void;
86
121
  export declare function clearDefaultProfile(): void;
122
+ /** Select a profile's default model (list position 1, materialized as
123
+ * settings.defaultModel); adds it when absent. Throws for an unknown profile,
124
+ * an empty model, or a new model beyond the 3-per-profile bound. */
125
+ export declare function setProfileDefaultModel(name: string, model: string): string;
87
126
  export declare function addProfile(name: string, profile: Profile): void;
127
+ /** Append a model to a profile without selecting it; becomes the default when
128
+ * the profile had no model. Throws for an unknown profile, a duplicate or
129
+ * empty model, or a fourth model. */
130
+ export declare function addProfileModel(name: string, model: string): string;
88
131
  export declare function updateProfile(name: string, profile: Profile): void;
89
132
  export declare function removeProfile(name: string): void;
133
+ /** Remove a model from a profile; the default promotes to the next remaining
134
+ * model, and removing the last clears both `models` and `model`. A model not
135
+ * in the list is a no-op with a message; throws for an unknown profile. */
136
+ export declare function removeProfileModel(name: string, model: string): string;
90
137
  export declare function renameProfile(oldName: string, newName: string): void;
91
138
  export declare function profileDirFor(name: string): string;
92
139
  export declare function materializeProfile(name: string, profile: Profile): string;
@@ -164,3 +211,53 @@ export declare function loginProvider(providerId: string, options?: LoginProvide
164
211
 
165
212
  /** Readline-based AuthInteraction for terminal use (prompts on stdin, auth URLs opened in the browser). */
166
213
  export declare function createTerminalAuthInteraction(): AuthInteraction;
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // pi-plus settings store (~/.pi/agent/pi-plus-settings.json): auto-compaction
217
+ // threshold percent, context floor buffer and context window cap. Re-exported
218
+ // from packages/plus/src/context/threshold-setting.ts; the runtime bundle
219
+ // inlines the module. Pass an explicit `path` to read/write a specific agent
220
+ // dir's copy; the default follows the process agent dir (PI_AGENT_DIR).
221
+ // ---------------------------------------------------------------------------
222
+
223
+ /** Env var that overrides the settings file path (tests, debugging). */
224
+ export declare const PLUS_SETTINGS_ENV: "PI_PLUS_SETTINGS_FILE";
225
+
226
+ export interface PlusSettings {
227
+ /** Percent (1-100) of the effective context window at which auto-compaction triggers. */
228
+ autoCompactThresholdPercent?: number;
229
+ /** Minimum floor buffer (tokens) for the effective context window (>= 13000). */
230
+ contextFloorTokens?: number;
231
+ /** Cap (tokens >= 32768) on the context window used for auto-compact math. */
232
+ contextWindowCapTokens?: number;
233
+ }
234
+
235
+ export declare function getPlusSettingsPath(): string;
236
+ /** Read the store; missing or malformed files yield {} (never throws). */
237
+ export declare function readPlusSettings(path?: string): PlusSettings;
238
+ /** Persist the store, replacing only the fields present in `patch`; a field
239
+ * explicitly set to undefined deletes that key, absent fields are untouched. */
240
+ export declare function writePlusSettings(patch: PlusSettings, path?: string): void;
241
+
242
+ /** Default threshold: 80% of the effective context window. */
243
+ export declare const DEFAULT_AUTO_COMPACT_THRESHOLD_PERCENT: number;
244
+ export declare function getAutoCompactThresholdPercent(): number;
245
+ /** Persist a choice; undefined resets to the default (deletes the key). */
246
+ export declare function setAutoCompactThresholdPercent(percent: number | undefined): void;
247
+ export declare function formatAutoCompactThresholdPercent(percent: number): string;
248
+ export declare function parseAutoCompactThresholdChoice(choice: string): number;
249
+
250
+ /** Default/built-in minimum context floor buffer. */
251
+ export declare const DEFAULT_CONTEXT_FLOOR_TOKENS: number;
252
+ export declare const MIN_CONTEXT_FLOOR_TOKENS: number;
253
+ export declare function getContextFloorTokens(): number;
254
+ export declare function setContextFloorTokens(tokens: number | undefined): void;
255
+ export declare function formatContextFloorTokens(tokens: number): string;
256
+ export declare function parseContextFloorChoice(choice: string): number;
257
+
258
+ /** Lowest accepted context window cap; undefined means no cap. */
259
+ export declare const MIN_CONTEXT_WINDOW_CAP_TOKENS: number;
260
+ export declare function getContextWindowCapTokens(): number | undefined;
261
+ export declare function setContextWindowCapTokens(tokens: number | undefined): void;
262
+ export declare function formatContextWindowCapTokens(tokens: number | undefined): string;
263
+ export declare function parseContextWindowCapChoice(choice: string): number | undefined;