@meetopenbot/pi 0.0.2 → 0.1.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 CHANGED
@@ -22,13 +22,21 @@ plugins:
22
22
  tools: read,bash,edit,write,grep,find,ls
23
23
  ```
24
24
 
25
+ ## Auth
26
+
27
+ On cloud, `authMode: credits` (the default) uses your workspace credit balance via OpenBot for `openai`, `anthropic`, and `deepseek`. For BYOK, set `authMode: byok` and add the matching provider key under workspace settings (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `DEEPSEEK_API_KEY`, or `GEMINI_API_KEY`).
28
+
29
+ Locally, Pi always uses BYOK from environment variables or `~/.pi/agent/auth.json`.
30
+
31
+ Credits does not cover Google Gemini — Pi uses Gemini's native API, while OpenBot Credits proxies the OpenAI-compatible Gemini surface. Use openai/anthropic/deepseek on credits, or switch to BYOK for Gemini.
32
+
25
33
  ## Configuration
26
34
 
27
35
  | Option | Description |
28
36
  |--------|-------------|
29
- | `cwd` | Working directory for Pi tools and resource discovery. Defaults to the OpenBot channel `cwd`, then `process.cwd()`. |
37
+ | `authMode` | Cloud only: `credits` (default) or `byok`. |
30
38
  | `agentDir` | Pi config directory (credentials, settings, sessions). Default: `~/.pi/agent`. |
31
- | `provider` | Model provider (e.g. `anthropic`, `openai`). |
39
+ | `provider` | Model provider (e.g. `anthropic`, `openai`, `deepseek`). |
32
40
  | `model` | Model id (e.g. `claude-opus-4-5`). |
33
41
  | `thinkingLevel` | Extended thinking: `off`, `minimal`, `low`, `medium`, `high`, `xhigh`. |
34
42
  | `tools` | Comma-separated built-in tools to enable. |
@@ -36,13 +44,7 @@ plugins:
36
44
  | `noTools` | `all` or `builtin` to disable tools. |
37
45
  | `systemPrompt` | Override Pi's system prompt for this agent. |
38
46
 
39
- ## API Keys
40
-
41
- Pi resolves credentials via `AuthStorage` (see [Pi SDK docs](https://pi.dev/docs/latest/sdk)):
42
-
43
- 1. Runtime overrides
44
- 2. `~/.pi/agent/auth.json`
45
- 3. Environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.)
47
+ Working directory always comes from the OpenBot channel `cwd`.
46
48
 
47
49
  ## Sessions
48
50
 
@@ -0,0 +1,10 @@
1
+ /** True when this runtime is a platform-managed cloud deployment. */
2
+ export const isCloudMode = () => process.env.OPENBOT_CLOUD_MODE === '1';
3
+ /** Default auth mode: Credits on cloud, BYOK locally. */
4
+ export const defaultAuthMode = () => (isCloudMode() ? 'credits' : 'byok');
5
+ export function resolveAuthMode(config) {
6
+ if (config.authMode === 'byok' || config.authMode === 'credits') {
7
+ return config.authMode;
8
+ }
9
+ return defaultAuthMode();
10
+ }
package/dist/config.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { resolveAuthMode } from './cloud-mode.js';
1
2
  const THINKING_LEVELS = new Set([
2
3
  'off',
3
4
  'minimal',
@@ -8,19 +9,21 @@ const THINKING_LEVELS = new Set([
8
9
  ]);
9
10
  export const resolveConfig = (context, channelCwd) => {
10
11
  const config = context.config;
11
- const thinkingLevel = config.thinkingLevel && THINKING_LEVELS.has(config.thinkingLevel)
12
+ const thinkingLevel = typeof config.thinkingLevel === 'string' && THINKING_LEVELS.has(config.thinkingLevel)
12
13
  ? config.thinkingLevel
13
14
  : undefined;
15
+ const asString = (value) => typeof value === 'string' && value.trim() ? value.trim() : undefined;
14
16
  return {
15
- cwd: channelCwd || config.cwd,
16
- agentDir: config.agentDir,
17
- provider: config.provider?.trim() || undefined,
18
- model: config.model?.trim() || undefined,
17
+ cwd: channelCwd || process.cwd(),
18
+ agentDir: asString(config.agentDir),
19
+ provider: asString(config.provider),
20
+ model: asString(config.model),
19
21
  thinkingLevel,
20
- tools: config.tools?.trim() || undefined,
21
- excludeTools: config.excludeTools?.trim() || undefined,
22
- noTools: config.noTools,
23
- systemPrompt: config.systemPrompt?.trim() || undefined,
22
+ tools: asString(config.tools),
23
+ excludeTools: asString(config.excludeTools),
24
+ noTools: config.noTools === 'all' || config.noTools === 'builtin' ? config.noTools : undefined,
25
+ systemPrompt: asString(config.systemPrompt),
26
+ authMode: resolveAuthMode(config),
24
27
  };
25
28
  };
26
29
  export const parseToolList = (value) => {
@@ -0,0 +1,90 @@
1
+ export const INTEGRATIONS_TOKEN_HEADER = 'x-openbot-integrations-token';
2
+ export const CREDITS_API_KEY_PLACEHOLDER = 'openbot-credits';
3
+ /**
4
+ * Gateway paths matching each Pi provider's default base URL shape:
5
+ * - openai models use `https://api.openai.com/v1`
6
+ * - anthropic models use `https://api.anthropic.com` (SDK appends `/v1`)
7
+ * - deepseek is OpenAI-compat; `/v1` keeps metering on `/v1/chat/completions`
8
+ *
9
+ * Google Gemini is omitted: Pi speaks the native Generative Language API, while
10
+ * the credits gateway only proxies Gemini's OpenAI-compatible surface.
11
+ */
12
+ export const CREDITS_PROVIDERS = [
13
+ { id: 'openai', basePath: 'openai/v1', envVar: 'OPENAI_API_KEY' },
14
+ { id: 'anthropic', basePath: 'anthropic', envVar: 'ANTHROPIC_API_KEY' },
15
+ { id: 'deepseek', basePath: 'deepseek/v1', envVar: 'DEEPSEEK_API_KEY' },
16
+ ];
17
+ const CREDITS_PROVIDER_IDS = new Set(CREDITS_PROVIDERS.map((provider) => provider.id));
18
+ export function isCreditsProvider(provider) {
19
+ return CREDITS_PROVIDER_IDS.has(provider);
20
+ }
21
+ /** Cloud host injects these when routing LLM calls through OpenBot Credits. */
22
+ export function resolveCreditsAuthConfig() {
23
+ const baseUrl = process.env.OPENBOT_INTEGRATIONS_BASE_URL?.trim();
24
+ const token = process.env.OPENBOT_INTEGRATIONS_TOKEN?.trim();
25
+ if (!baseUrl || !token)
26
+ return undefined;
27
+ return { baseUrl: baseUrl.replace(/\/$/, ''), token };
28
+ }
29
+ export function creditsProviderBaseUrl(config, provider) {
30
+ const match = CREDITS_PROVIDERS.find((entry) => entry.id === provider);
31
+ if (!match) {
32
+ throw new Error(`Unsupported credits provider: ${provider}`);
33
+ }
34
+ return `${config.baseUrl}/${match.basePath}`;
35
+ }
36
+ export function applyCreditsProviders(modelRegistry, authStorage, config) {
37
+ const apiKey = config.token || CREDITS_API_KEY_PLACEHOLDER;
38
+ const headers = { [INTEGRATIONS_TOKEN_HEADER]: config.token };
39
+ for (const provider of CREDITS_PROVIDERS) {
40
+ modelRegistry.registerProvider(provider.id, {
41
+ baseUrl: creditsProviderBaseUrl(config, provider.id),
42
+ headers,
43
+ });
44
+ authStorage.setRuntimeApiKey(provider.id, apiKey);
45
+ }
46
+ }
47
+ export function isCreditsErrorMessage(message) {
48
+ const lower = message.toLowerCase();
49
+ return (lower.includes('insufficient_credits') ||
50
+ lower.includes('insufficient credits') ||
51
+ lower.includes('402'));
52
+ }
53
+ export function isAuthErrorMessage(message) {
54
+ const lower = message.toLowerCase();
55
+ return (lower.includes('api key') ||
56
+ lower.includes('apikey') ||
57
+ lower.includes('401') ||
58
+ lower.includes('unauthorized') ||
59
+ lower.includes('authentication') ||
60
+ lower.includes('not logged in') ||
61
+ lower.includes('login'));
62
+ }
63
+ export function isIntegrationsProviderError(message) {
64
+ const lower = message.toLowerCase();
65
+ return (lower.includes('provider api key not configured') ||
66
+ (lower.includes('503') && lower.includes('provider')));
67
+ }
68
+ export const CREDITS_NOT_CONFIGURED_MESSAGE = 'OpenBot Credits is not configured on this runtime. The cloud host must set OPENBOT_INTEGRATIONS_BASE_URL and OPENBOT_INTEGRATIONS_TOKEN (try redeploying the workspace).';
69
+ export const CREDITS_PROVIDER_UNAVAILABLE_MESSAGE = 'OpenBot Credits could not reach the model provider — the platform provider API key is not configured yet. Try again later or switch this agent to BYOK mode.';
70
+ export const CREDITS_AUTH_FAILED_MESSAGE = 'Pi could not authenticate via OpenBot Credits. Check your workspace credit balance in settings, or switch this agent to BYOK mode.';
71
+ export function creditsErrorMessage(message) {
72
+ if (isIntegrationsProviderError(message)) {
73
+ return CREDITS_PROVIDER_UNAVAILABLE_MESSAGE;
74
+ }
75
+ if (isCreditsErrorMessage(message)) {
76
+ return 'Insufficient workspace credits. Add credits in workspace settings or switch this agent to BYOK mode.';
77
+ }
78
+ if (isAuthErrorMessage(message))
79
+ return CREDITS_AUTH_FAILED_MESSAGE;
80
+ return undefined;
81
+ }
82
+ export function remapPiError(message, authMode) {
83
+ if (authMode !== 'credits')
84
+ return message;
85
+ return creditsErrorMessage(message) ?? message;
86
+ }
87
+ export function unsupportedCreditsProviderMessage(provider) {
88
+ return (`OpenBot Credits does not support the "${provider}" provider. ` +
89
+ 'Use openai, anthropic, or deepseek, or switch this agent to BYOK mode.');
90
+ }
package/dist/diff.js ADDED
@@ -0,0 +1 @@
1
+ export { buildDiffWidget as runDiffWidget, diffFileFromMutationTool as diffFileFromPiTool, } from '@meetopenbot/plugin-sdk';
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from '@meetopenbot/plugin-sdk';
2
+ declare const plugin: Plugin;
3
+ export { plugin };
4
+ export default plugin;
package/dist/index.js CHANGED
@@ -1,8 +1,64 @@
1
- import { agentOutput, definePlugin, shouldHandleInvoke, uiWidget, } from '@meetopenbot/plugin-sdk';
1
+ import { agentOutput, buildDiffWidget, definePlugin, diffFileFromMutationTool, resolveRunDiffFiles, shouldHandleInvoke, snapshotWorkspace, toolTraceWidget, uiWidget, } from '@meetopenbot/plugin-sdk';
2
+ import { isCloudMode } from './cloud-mode.js';
2
3
  import { resolveConfig } from './config.js';
4
+ import { isAuthErrorMessage, remapPiError, } from './credits-auth.js';
3
5
  import { formatPiError, formatToolResult } from './format.js';
4
- import { getOrCreatePiSession } from './session.js';
6
+ import { disposePiSessionsForThread, getOrCreatePiSession } from './session.js';
5
7
  import { streamPiPrompt } from './stream.js';
8
+ const BYOK_PROVIDERS = {
9
+ anthropic: {
10
+ envVar: 'ANTHROPIC_API_KEY',
11
+ label: 'Anthropic',
12
+ placeholder: 'sk-ant-...',
13
+ },
14
+ openai: {
15
+ envVar: 'OPENAI_API_KEY',
16
+ label: 'OpenAI',
17
+ placeholder: 'sk-...',
18
+ },
19
+ deepseek: {
20
+ envVar: 'DEEPSEEK_API_KEY',
21
+ label: 'DeepSeek',
22
+ placeholder: 'sk-...',
23
+ },
24
+ google: {
25
+ envVar: 'GEMINI_API_KEY',
26
+ label: 'Google Gemini',
27
+ placeholder: 'AIza...',
28
+ },
29
+ };
30
+ const resolveByokProvider = (provider) => BYOK_PROVIDERS[provider ?? ''] ?? BYOK_PROVIDERS.openai;
31
+ const buildApiKeyWidget = (agentId, threadId, reason, provider) => {
32
+ const byok = resolveByokProvider(provider);
33
+ return uiWidget({
34
+ agentId,
35
+ threadId,
36
+ widget: {
37
+ kind: 'form',
38
+ widgetId: `pi_api_key_request_${Date.now()}`,
39
+ title: `${byok.label} API Key Required`,
40
+ description: `Pi could not authenticate (${reason}). ` +
41
+ `Provide a ${byok.label} API key to continue. ` +
42
+ 'The key is stored as a workspace variable on your machine and never leaves your local runtime.',
43
+ fields: [
44
+ {
45
+ id: 'apiKey',
46
+ label: 'API Key',
47
+ type: 'password',
48
+ placeholder: byok.placeholder,
49
+ required: true,
50
+ },
51
+ ],
52
+ submitLabel: 'Save API Key',
53
+ metadata: {
54
+ type: 'api_key_request',
55
+ provider: provider && BYOK_PROVIDERS[provider] ? provider : 'openai',
56
+ envVar: byok.envVar,
57
+ source: 'pi',
58
+ },
59
+ },
60
+ });
61
+ };
6
62
  export default definePlugin({
7
63
  id: 'pi',
8
64
  name: 'Pi',
@@ -10,17 +66,23 @@ export default definePlugin({
10
66
  configSchema: {
11
67
  type: 'object',
12
68
  properties: {
13
- cwd: {
14
- type: 'string',
15
- description: 'Working directory for Pi tools and resource discovery.',
16
- },
69
+ ...(isCloudMode()
70
+ ? {
71
+ authMode: {
72
+ type: 'string',
73
+ description: 'Credits — use your workspace credit balance via OpenBot. BYOK — bring your own provider API key.',
74
+ enum: ['credits', 'byok'],
75
+ default: 'credits',
76
+ },
77
+ }
78
+ : {}),
17
79
  agentDir: {
18
80
  type: 'string',
19
81
  description: 'Pi config directory (default: ~/.pi/agent).',
20
82
  },
21
83
  provider: {
22
84
  type: 'string',
23
- description: 'Model provider (e.g. anthropic, openai).',
85
+ description: 'Model provider (e.g. anthropic, openai, deepseek).',
24
86
  },
25
87
  model: {
26
88
  type: 'string',
@@ -67,6 +129,17 @@ export default definePlugin({
67
129
  }
68
130
  const channelCwd = handlerCtx.state.channelDetails?.cwd;
69
131
  const config = resolveConfig(context, channelCwd);
132
+ const fail = function* (message) {
133
+ const mapped = remapPiError(message, config.authMode);
134
+ if (config.authMode === 'byok' && isAuthErrorMessage(message)) {
135
+ yield buildApiKeyWidget(context.agentId, threadId, mapped, config.provider);
136
+ }
137
+ yield agentOutput({
138
+ agentId: context.agentId,
139
+ content: `**Pi error:** ${mapped}`,
140
+ threadId,
141
+ });
142
+ };
70
143
  try {
71
144
  const { session } = await getOrCreatePiSession({
72
145
  config,
@@ -75,6 +148,8 @@ export default definePlugin({
75
148
  });
76
149
  let fullTextContent = '';
77
150
  const toolInfoMap = new Map();
151
+ const changedFiles = new Map();
152
+ const snapshot = snapshotWorkspace(config.cwd);
78
153
  for await (const chunk of streamPiPrompt(session, prompt, {
79
154
  streaming: session.isStreaming,
80
155
  })) {
@@ -87,14 +162,12 @@ export default definePlugin({
87
162
  yield uiWidget({
88
163
  agentId: context.agentId,
89
164
  threadId,
90
- widget: {
91
- kind: 'message',
165
+ widget: toolTraceWidget({
92
166
  widgetId: chunk.toolCallId,
167
+ groupId: 'pi:tools',
93
168
  title: chunk.statusLine,
94
169
  body: `**Input**\n\`\`\`json\n${JSON.stringify(chunk.args, null, 2)}\n\`\`\``,
95
- variant: 'basic',
96
- display: "collapsed",
97
- },
170
+ }),
98
171
  });
99
172
  break;
100
173
  case 'tool_end': {
@@ -106,37 +179,46 @@ export default definePlugin({
106
179
  yield uiWidget({
107
180
  agentId: context.agentId,
108
181
  threadId,
109
- widget: {
110
- kind: 'message',
182
+ widget: toolTraceWidget({
111
183
  widgetId: chunk.toolCallId,
184
+ groupId: 'pi:tools',
112
185
  title: chunk.statusLine || info?.statusLine || `Tool ${chunk.toolName} finished`,
113
186
  body: inputMd + outputMd,
114
- variant: 'basic',
115
- display: "collapsed",
116
187
  state: chunk.isError ? 'error' : 'submitted',
117
- },
188
+ }),
118
189
  });
190
+ if (!chunk.isError) {
191
+ const file = diffFileFromMutationTool({
192
+ toolName: chunk.toolName,
193
+ input: info?.args,
194
+ result: chunk.result,
195
+ });
196
+ if (file)
197
+ changedFiles.set(file.path, file);
198
+ }
119
199
  break;
120
200
  }
121
201
  case 'status':
122
202
  yield uiWidget({
123
203
  agentId: context.agentId,
124
204
  threadId,
125
- widget: {
126
- kind: 'message',
205
+ widget: toolTraceWidget({
127
206
  widgetId: `status-${Date.now()}`,
128
- title: "Status",
207
+ groupId: 'pi:tools',
208
+ title: 'Status',
129
209
  body: chunk.statusLine,
130
- variant: 'basic',
131
- display: "collapsed",
132
- },
210
+ }),
133
211
  });
134
212
  break;
135
- case 'text':
213
+ case 'text': {
214
+ const content = chunk.content.startsWith('**Pi error:** ')
215
+ ? `**Pi error:** ${remapPiError(chunk.content.slice('**Pi error:** '.length), config.authMode)}`
216
+ : chunk.content;
136
217
  if (fullTextContent)
137
218
  fullTextContent += '\n\n';
138
- fullTextContent += chunk.content;
219
+ fullTextContent += content;
139
220
  break;
221
+ }
140
222
  }
141
223
  }
142
224
  if (fullTextContent.trim()) {
@@ -146,12 +228,70 @@ export default definePlugin({
146
228
  threadId,
147
229
  });
148
230
  }
231
+ const diff = buildDiffWidget({
232
+ widgetId: `pi-diff:${threadId ?? 'run'}:${Date.now()}`,
233
+ files: resolveRunDiffFiles({
234
+ snapshot,
235
+ fallback: changedFiles.values(),
236
+ }),
237
+ });
238
+ if (diff) {
239
+ yield uiWidget({
240
+ agentId: context.agentId,
241
+ threadId,
242
+ widget: diff,
243
+ });
244
+ }
149
245
  }
150
246
  catch (error) {
247
+ yield* fail(formatPiError(error));
248
+ }
249
+ });
250
+ builder.on('client:ui:widget:response', async function* (event, handlerCtx) {
251
+ const { metadata, values, widgetId } = event.data ?? {};
252
+ if (!metadata || metadata.type !== 'api_key_request')
253
+ return;
254
+ if (metadata.source !== 'pi')
255
+ return;
256
+ const apiKey = values?.apiKey;
257
+ if (typeof apiKey !== 'string' || !apiKey)
258
+ return;
259
+ const envVar = typeof metadata.envVar === 'string' ? metadata.envVar : 'OPENAI_API_KEY';
260
+ const storage = context.storage;
261
+ if (!storage) {
151
262
  yield agentOutput({
152
263
  agentId: context.agentId,
153
- content: `**Pi error:** ${formatPiError(error)}`,
154
- threadId,
264
+ content: '[pi] no storage available; cannot persist API key.',
265
+ threadId: handlerCtx.state.threadId,
266
+ });
267
+ return;
268
+ }
269
+ try {
270
+ await storage.createVariable({ key: envVar, value: apiKey, secret: true });
271
+ process.env[envVar] = apiKey;
272
+ disposePiSessionsForThread(handlerCtx.state);
273
+ yield uiWidget({
274
+ agentId: context.agentId,
275
+ widget: {
276
+ widgetId: widgetId ?? `pi_api_key_saved_${Date.now()}`,
277
+ kind: 'message',
278
+ title: 'API Key Saved',
279
+ body: `Saved ${envVar} as a workspace variable. You can now continue the conversation.`,
280
+ state: 'submitted',
281
+ },
282
+ });
283
+ yield agentOutput({
284
+ agentId: context.agentId,
285
+ content: `Saved ${envVar} to workspace variables. Re-send your last message to retry.`,
286
+ threadId: handlerCtx.state.threadId,
287
+ });
288
+ }
289
+ catch (error) {
290
+ const errorMessage = error instanceof Error ? error.message : String(error);
291
+ yield agentOutput({
292
+ agentId: context.agentId,
293
+ content: `[pi] failed to save API key: ${errorMessage}`,
294
+ threadId: handlerCtx.state.threadId,
155
295
  });
156
296
  }
157
297
  });
package/dist/session.js CHANGED
@@ -1,8 +1,12 @@
1
1
  import { AuthStorage, createAgentSession, DefaultResourceLoader, getAgentDir, ModelRegistry, SessionManager, SettingsManager, } from '@earendil-works/pi-coding-agent';
2
2
  import { parseToolList } from './config.js';
3
+ import { applyCreditsProviders, CREDITS_NOT_CONFIGURED_MESSAGE, isCreditsProvider, resolveCreditsAuthConfig, unsupportedCreditsProviderMessage, } from './credits-auth.js';
3
4
  import { persistPiState, readPersistedState } from './state.js';
4
5
  const sessionCache = new Map();
5
- const buildSessionKey = (state) => state.threadId ? `${state.channelId}:${state.threadId}` : state.channelId;
6
+ const buildSessionKey = (state, config) => {
7
+ const scope = state.threadId ? `${state.channelId}:${state.threadId}` : state.channelId;
8
+ return `${scope}:${config.authMode}`;
9
+ };
6
10
  const resolveModel = async (config, modelRegistry) => {
7
11
  if (config.provider && config.model) {
8
12
  return modelRegistry.find(config.provider, config.model) ?? undefined;
@@ -12,24 +16,32 @@ const resolveModel = async (config, modelRegistry) => {
12
16
  };
13
17
  export const getOrCreatePiSession = async (args) => {
14
18
  const { config, state, storage } = args;
15
- const sessionKey = buildSessionKey(state);
19
+ const sessionKey = buildSessionKey(state, config);
16
20
  const cached = sessionCache.get(sessionKey);
17
21
  if (cached)
18
22
  return cached;
23
+ if (config.authMode === 'credits' && config.provider && !isCreditsProvider(config.provider)) {
24
+ throw new Error(unsupportedCreditsProviderMessage(config.provider));
25
+ }
19
26
  const cwd = config.cwd || process.cwd();
20
27
  const agentDir = config.agentDir || getAgentDir();
21
28
  const persisted = readPersistedState(state);
22
29
  const authStorage = AuthStorage.create(`${agentDir}/auth.json`);
23
30
  const modelRegistry = ModelRegistry.create(authStorage, `${agentDir}/models.json`);
31
+ if (config.authMode === 'credits') {
32
+ const credits = resolveCreditsAuthConfig();
33
+ if (!credits) {
34
+ throw new Error(CREDITS_NOT_CONFIGURED_MESSAGE);
35
+ }
36
+ applyCreditsProviders(modelRegistry, authStorage, credits);
37
+ }
24
38
  const model = await resolveModel(config, modelRegistry);
25
39
  const settingsManager = SettingsManager.create(cwd, agentDir);
26
40
  const loader = new DefaultResourceLoader({
27
41
  cwd,
28
42
  agentDir,
29
43
  settingsManager,
30
- ...(config.systemPrompt
31
- ? { systemPromptOverride: () => config.systemPrompt }
32
- : {}),
44
+ ...(config.systemPrompt ? { systemPromptOverride: () => config.systemPrompt } : {}),
33
45
  });
34
46
  await loader.reload();
35
47
  const sessionManager = persisted.sessionFile
@@ -66,3 +78,10 @@ export const disposePiSession = (sessionKey) => {
66
78
  cached.session.dispose();
67
79
  sessionCache.delete(sessionKey);
68
80
  };
81
+ export const disposePiSessionsForThread = (state) => {
82
+ const prefix = state.threadId ? `${state.channelId}:${state.threadId}:` : `${state.channelId}:`;
83
+ for (const key of sessionCache.keys()) {
84
+ if (key.startsWith(prefix))
85
+ disposePiSession(key);
86
+ }
87
+ };
package/package.json CHANGED
@@ -1,30 +1,37 @@
1
1
  {
2
2
  "name": "@meetopenbot/pi",
3
- "version": "0.0.2",
3
+ "version": "0.1.0",
4
4
  "description": "OpenBot agent plugin powered by the Pi coding agent SDK.",
5
5
  "type": "module",
6
- "main": "dist/index.js",
6
+ "main": "./dist/index.js",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
10
10
  "exports": {
11
- ".": "./dist/index.js"
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
12
16
  },
13
17
  "files": [
14
18
  "dist"
15
19
  ],
16
- "scripts": {
17
- "build": "tsc -p tsconfig.json",
18
- "typecheck": "tsc -p tsconfig.json --noEmit"
19
- },
20
20
  "license": "MIT",
21
21
  "dependencies": {
22
22
  "@earendil-works/pi-ai": "^0.79.1",
23
23
  "@earendil-works/pi-coding-agent": "^0.79.1",
24
- "@meetopenbot/plugin-sdk": "^0.1.4"
24
+ "@meetopenbot/plugin-sdk": "^0.2.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^25.9.2",
28
28
  "typescript": "^6.0.3"
29
+ },
30
+ "types": "./dist/index.d.ts",
31
+ "scripts": {
32
+ "build": "tsc -p tsconfig.json && node ../../scripts/write-plugin-declaration.mjs",
33
+ "typecheck": "tsc -p tsconfig.json --noEmit",
34
+ "test": "node --experimental-strip-types --test src/*.test.ts",
35
+ "dev": "tsc -p tsconfig.json --watch --preserveWatchOutput"
29
36
  }
30
- }
37
+ }