@ory/pi 1.3.0 → 1.4.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
@@ -1,4 +1,4 @@
1
- # Ory Agent Plugin: Pi
1
+ # Ory Agent Plugin and SDK Integration: Pi
2
2
 
3
3
  Security and developer experience for [Pi](https://pi.dev), powered by [Ory](https://ory.com).
4
4
 
@@ -16,21 +16,15 @@ One command installs two independent halves:
16
16
  - [Docker](https://docs.docker.com/get-docker/) — only if you want to run Ory locally
17
17
  - macOS or Linux (Windows works via WSL2)
18
18
 
19
- Pi loads the plugin from your project's `node_modules`, so add it to your project first:
20
-
21
- ```bash
22
- npm install @ory/pi
23
- ```
24
-
25
19
  ## Get started
26
20
 
27
- Then run one command. It installs the plugin.:
21
+ Run one command:
28
22
 
29
23
  ```bash
30
24
  npx -y -p @ory/pi ory-pi install
31
25
  ```
32
26
 
33
- The installer drops a tiny loader at `<project>/.pi/extensions/ory.js` so Pi discovers the plugin and adds the Ory skills under `<project>/.pi/skills`. Add `--global` to install the loader for all projects (at `~/.pi/agent/extensions/ory.js`) instead of just this one.
27
+ The installer resolves the runtime once and drops a tiny loader at `<project>/.pi/extensions/ory.js` so Pi discovers the plugin, and adds the Ory skills under `<project>/.pi/skills`. Add `--global` to install the loader for all projects (at `~/.pi/agent/extensions/ory.js`) instead of just this one. No project dependency is required for CLI installation.
34
28
 
35
29
  Confirm everything landed with:
36
30
 
@@ -49,7 +43,7 @@ Point Pi's own package installer at the npm package:
49
43
  pi install npm:@ory/pi
50
44
  ```
51
45
 
52
- This registers the extension through Pi's package manifest. Run `npx -y -p @ory/pi ory-pi install` in a terminal afterwards to materialize the skills (and, later, `configure` if you want to connect).
46
+ This registers the security extension through Pi's package manifest. Use either this registration or the Ory installer above, not both: duplicate extensions produce duplicate checks and activity. The Ory installer is the supported route for the complete skill catalog and runtime health checks.
53
47
 
54
48
  </details>
55
49
 
@@ -93,7 +87,59 @@ Once you connect to Ory Agent Security, two more things happen automatically:
93
87
 
94
88
  If Ory is ever unreachable, the plugin gets out of the way and lets Pi keep working — so it can't lock you out.
95
89
 
96
- > Pi has no MCP support and no sub-agents by design, so there's nothing extra to register — just the plugin itself.
90
+ The extension gates tools exposed through Pi's `tool_call` event and records `tool_result`. Extension-provided tools use their Pi tool names; the installer does not configure an MCP server, and this adapter does not translate MCP-specific grants or child-agent delegation. Calls that another extension executes outside Pi's tool lifecycle are outside this boundary.
91
+
92
+ Session authentication runs on `session_start`, using `ctx.sessionManager.getSessionId()` for identity and activity. Loading extension resources alone does not start authentication. SDK hosts that omit lifecycle binding still start authentication lazily before their first tool call.
93
+
94
+ ## Embed Pi with the SDK
95
+
96
+ The same `createOryPlugin` factory works in `DefaultResourceLoader.extensionFactories`.
97
+ Install `@ory/pi`, `@earendil-works/pi-coding-agent@0.87.1`, and `tsx` in your application.
98
+ Configure the routing URLs and runtime credential from your Agent Security deployment;
99
+ the model provider credential remains separate.
100
+
101
+ ```typescript
102
+ import {
103
+ createAgentSession, DefaultResourceLoader, getAgentDir,
104
+ ModelRuntime, SessionManager, SettingsManager,
105
+ } from "@earendil-works/pi-coding-agent";
106
+ import { createOryPlugin, OryAgentClient } from "@ory/pi";
107
+
108
+ const ory = OryAgentClient.fromEnv("pi");
109
+ const settingsManager = SettingsManager.inMemory();
110
+ const resourceLoader = new DefaultResourceLoader({
111
+ cwd: process.cwd(), agentDir: getAgentDir(), settingsManager,
112
+ noExtensions: true, // Load only the inline extension, avoiding duplicate discovery.
113
+ noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true,
114
+ extensionFactories: [createOryPlugin(ory)],
115
+ });
116
+ await resourceLoader.reload();
117
+ const modelRuntime = await ModelRuntime.create();
118
+ const model = modelRuntime.getModel("openai", "gpt-4o-mini");
119
+ if (!model) throw new Error("The OpenAI gpt-4o-mini model is unavailable");
120
+ const { session } = await createAgentSession({
121
+ resourceLoader, settingsManager, modelRuntime, model,
122
+ sessionManager: SessionManager.inMemory(),
123
+ });
124
+ try {
125
+ await session.bindExtensions({});
126
+ await session.prompt("Use the read tool to summarize README.md.");
127
+ console.log(session.getLastAssistantText());
128
+ } finally {
129
+ session.dispose();
130
+ }
131
+ ```
132
+
133
+ Run this as an ESM TypeScript entry (for example `npx tsx agent.mts`) with
134
+ `OPENAI_API_KEY` set. Built-in and custom tools share the same gate. In enforce mode,
135
+ an explicit block returns `{ block: true, reason }` before execution, and Pi reports
136
+ the denial to the model. Give each independent SDK session its own client and
137
+ extension factory. Resume persistent sessions using Pi's `SessionManager` so the
138
+ native session identity is retained.
139
+
140
+ The Agent Security **Build → Pi SDK** quickstart and **Guides → Build → Pi SDK**
141
+ provide a complete custom-tool example. Choose **Pi SDK** when creating a hosted
142
+ Agent deployment; managed CLI installations continue to use the **Pi** plugin.
97
143
 
98
144
  ### See what's happening
99
145
 
@@ -163,12 +209,12 @@ All prefixed with `npx -y -p @ory/pi`.
163
209
  - **Local Ory won't start** — make sure Docker is running and ports `4000`, `4100`, `4455`, and `16686` are free.
164
210
  - **Browser sign-in loops** (after connecting) — confirm the reserved login client has all four documented loopback redirect URIs; `agent unregister` only clears legacy agent OAuth state.
165
211
  - **Running an older CLI than expected** — `npx -p @ory/pi` (no version pin) reuses a previously-downloaded copy instead of re-resolving to the latest. The install banner prints the running version; confirm it with `npx -y -p @ory/pi ory-pi version`. To force the current release, clear the cache and reinstall: `rm -rf ~/.npm/_npx` then `npx -y -p @ory/pi ory-pi install`. Pinning an exact version (`@ory/pi@<version>`) also bypasses the cached copy.
166
- - **Pi can't find the plugin** — make sure `@ory/pi` is installed in the project (`npm install @ory/pi`) and the loader exists at `.pi/extensions/ory.js`; re-run the installer if not.
212
+ - **Pi can't find the plugin** — check the loader at `.pi/extensions/ory.js` (or `~/.pi/agent/extensions/ory.js` for global installs) and run `ory-pi status` to verify its resolved runtime; re-run the installer if needed.
167
213
 
168
214
  ## Learn more
169
215
 
170
216
  - [Ory documentation](https://www.ory.com/docs/) · [Ory Console](https://console.ory.sh) · [Ory Elements](https://github.com/ory/elements)
171
- - [Pi repository](https://github.com/badlogic/pi-mono)
217
+ - [Pi repository](https://github.com/earendil-works/pi) · [Pi SDK documentation](https://pi.dev/docs/latest/sdk)
172
218
 
173
219
  ## License
174
220
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createOryPlugin } from "./plugin.js";
2
2
  export { createOryPlugin };
3
+ export { OryAgentClient } from "@ory/argus";
3
4
  export type { CreateOryPluginDeps } from "./plugin.js";
4
5
  export type { PiExtension, PiExtensionApi, PiEvent, PiEventHandlers, PiToolCallContext, PiToolResultContext, PiToolCallResult, PiHookRuntimeContext, } from "./types.js";
5
6
  declare const _default: import("./types.js").PiExtension;
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createOryPlugin = void 0;
3
+ exports.OryAgentClient = exports.createOryPlugin = void 0;
4
4
  const plugin_js_1 = require("./plugin.js");
5
5
  Object.defineProperty(exports, "createOryPlugin", { enumerable: true, get: function () { return plugin_js_1.createOryPlugin; } });
6
+ var argus_1 = require("@ory/argus");
7
+ Object.defineProperty(exports, "OryAgentClient", { enumerable: true, get: function () { return argus_1.OryAgentClient; } });
6
8
  // Pi loads in-process extensions via jiti and expects a default-exported
7
9
  // factory function `(pi) => void | Promise<void>`. CJS output (module:
8
10
  // nodenext → CJS) surfaces `module.exports` as the ESM `default`, so a single
package/dist/plugin.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { PiExtension } from "./types.js";
3
3
  export interface CreateOryPluginDeps {
4
4
  userLogin?: typeof ensureUserAuthenticated;
5
5
  agentGate?: typeof ensureAgentIdentity;
6
- /** Pin the per-process id for deterministic tests. Production mints one. */
6
+ /** Fallback for hosts that do not supply a Pi session manager. */
7
7
  sessionId?: string;
8
8
  }
9
9
  export declare function createOryPlugin(clientOrConfig?: OryAgentClient | {
package/dist/plugin.js CHANGED
@@ -11,19 +11,37 @@ function createOryPlugin(clientOrConfig, deps = {}) {
11
11
  : clientOrConfig
12
12
  ? new argus_1.OryAgentClient({ ...clientOrConfig, harness: HARNESS })
13
13
  : argus_1.OryAgentClient.fromEnv(HARNESS);
14
- // Pi events have no session id, so one process UUID identifies the run.
15
- const sessionId = deps.sessionId ?? (0, node_crypto_1.randomUUID)();
16
- await (0, argus_1.withHookContext)(client, { sessionId }, async () => {
17
- client.logger.info("lifecycle.session_start", { harness: HARNESS });
18
- await (0, argus_1.sessionStart)(client, {
19
- harness: HARNESS,
20
- binName: "ory-pi",
21
- userLogin: deps.userLogin,
22
- agentGate: deps.agentGate,
14
+ const fallbackSessionId = deps.sessionId ?? (0, node_crypto_1.randomUUID)();
15
+ const sessionId = (ctx) => ctx?.sessionManager.getSessionId() ?? fallbackSessionId;
16
+ let activeSessionId;
17
+ let start;
18
+ const ensureStarted = async (id) => {
19
+ if (id !== activeSessionId) {
20
+ activeSessionId = id;
21
+ start = (0, argus_1.createSessionStarter)(client, {
22
+ harness: HARNESS,
23
+ binName: "ory-pi",
24
+ userLogin: deps.userLogin,
25
+ agentGate: deps.agentGate,
26
+ });
27
+ }
28
+ await start();
29
+ };
30
+ // Loading resources is not a session start. Pi supplies the real session
31
+ // through the hook context, including after resume/new/fork operations.
32
+ pi.on("session_start", (_event, ctx) => {
33
+ const id = sessionId(ctx);
34
+ return (0, argus_1.withHookContext)(client, { sessionId: id }, () => ensureStarted(id));
35
+ });
36
+ pi.on("tool_call", (event, ctx) => {
37
+ const id = sessionId(ctx);
38
+ return (0, argus_1.withHookContext)(client, { sessionId: id }, async () => {
39
+ // SDK hosts may prompt without binding extension UI/lifecycle handlers.
40
+ await ensureStarted(id);
41
+ return createToolCallHandler(client, id)(event);
23
42
  });
24
43
  });
25
- pi.on("tool_call", createToolCallHandler(client, sessionId));
26
- pi.on("tool_result", createToolResultHandler(client, sessionId));
44
+ pi.on("tool_result", (event, ctx) => createToolResultHandler(client, sessionId(ctx))(event));
27
45
  };
28
46
  }
29
47
  function createToolCallHandler(client, sessionId) {
package/dist/types.d.ts CHANGED
@@ -1,114 +1,42 @@
1
- /**
2
- * Pi (the minimal coding agent — pi.dev / github.com/badlogic/pi-mono) plugin
3
- * types.
4
- *
5
- * Pi loads in-process TypeScript extensions via jiti. The module's DEFAULT
6
- * EXPORT is a factory function `(pi: ExtensionAPI) => void | Promise<void>`.
7
- * Pi awaits async factories before startup continues, so the factory body is
8
- * the de-facto session-start hook: we run the auth gates inline there.
9
- *
10
- * Tool hooks are registered with `pi.on(event, handler)`. The pre-execution
11
- * `tool_call` hook BLOCKS a tool by returning `{ block: true, reason }`;
12
- * returning nothing/undefined allows. A best-effort `tool_result` hook fires
13
- * after a tool completes and is used for activity auditing.
14
- *
15
- * Pi deliberately excludes MCP and sub-agents, so there is no MCP tool
16
- * parsing and no sub-agent identity path.
17
- *
18
- * Contract verified against @earendil-works/pi-coding-agent v0.80.2 (dist
19
- * core/extensions/types.d.ts). The `tool_call` event "Fired before a tool
20
- * executes. Can block." and a handler returns
21
- * `ToolCallEventResult { block?: boolean; reason?: string }`; the
22
- * `tool_result` event "Fired after a tool executes." carries `isError` and
23
- * `content`. The factory default export receives an `ExtensionAPI`.
24
- */
25
- /**
26
- * Context delivered to a `tool_call` handler (pre-execution).
27
- *
28
- * Verified shape: `{ type: "tool_call", toolCallId, toolName, input }`.
29
- * `input` is mutable in place to patch tool arguments; we only read it. Pi
30
- * does not put a session id on this event, so the plugin derives the
31
- * permission subject from a stable per-process fallback.
1
+ /** Minimal structural Pi extension contract. No runtime SDK dependency.
2
+ * Verified against @earendil-works/pi-coding-agent 0.87.1.
3
+ * https://pi.dev/docs/latest/extensions
32
4
  */
33
5
  export interface PiToolCallContext {
34
- /** Discriminant: always "tool_call". */
35
6
  type?: "tool_call";
36
- /** Correlates the call with its later `tool_result`. */
37
7
  toolCallId?: string;
38
- /** Tool being invoked. */
39
8
  toolName?: string;
40
- /** Tool parameters (mutable in place per Pi; the gate only reads them). */
41
9
  input?: Record<string, unknown>;
42
- [key: string]: unknown;
43
10
  }
44
- /**
45
- * Context delivered to a `tool_result` handler (post-execution).
46
- *
47
- * Verified shape: `{ type: "tool_result", toolCallId, toolName, input,
48
- * content, isError, details }`. Note Pi reports failure via the `isError`
49
- * boolean and the result payload via `content` (not `error`/`result`).
50
- */
51
11
  export interface PiToolResultContext {
52
- /** Discriminant: always "tool_result". */
53
12
  type?: "tool_result";
54
- /** Correlates with the originating `tool_call`. */
55
13
  toolCallId?: string;
56
14
  toolName?: string;
57
15
  input?: Record<string, unknown>;
58
- /** Tool output blocks (text / images). */
59
16
  content?: unknown;
60
- /** True when the tool returned an error result. */
61
17
  isError?: boolean;
62
- /** Tool-specific structured details, when present. */
63
18
  details?: unknown;
64
- [key: string]: unknown;
65
19
  }
66
- /**
67
- * The ExtensionContext Pi passes as the second argument to a hook (`ctx`).
68
- * Modeled minimally; the plugin does not depend on it beyond optional UI.
69
- */
20
+ /** Pi passes this as the second argument, not as fields on the event. */
70
21
  export interface PiHookRuntimeContext {
71
- ui?: {
72
- confirm?: (message: string) => boolean | Promise<boolean>;
73
- [key: string]: unknown;
22
+ sessionManager: {
23
+ getSessionId(): string;
74
24
  };
75
- [key: string]: unknown;
76
25
  }
77
- /**
78
- * The result a `tool_call` handler returns to block a tool (Pi's
79
- * `ToolCallEventResult`). Returning nothing/undefined allows the tool
80
- * through. Verified: `{ block?: boolean; reason?: string }`.
81
- */
82
26
  export interface PiToolCallResult {
83
27
  block: true;
84
28
  reason?: string;
85
29
  }
86
- /**
87
- * Map of Pi extension event name → handler signature. Handlers may be async.
88
- * Verified against Pi's `ExtensionAPI.on(...)` overloads.
89
- */
90
30
  export interface PiEventHandlers {
31
+ session_start: (event: {
32
+ type: "session_start";
33
+ }, ctx?: PiHookRuntimeContext) => void | Promise<void>;
91
34
  tool_call: (event: PiToolCallContext, ctx?: PiHookRuntimeContext) => void | PiToolCallResult | Promise<void | PiToolCallResult>;
92
35
  tool_result: (event: PiToolResultContext, ctx?: PiHookRuntimeContext) => void | Promise<void>;
93
36
  }
94
37
  export type PiEvent = keyof PiEventHandlers;
95
- /**
96
- * The ExtensionAPI object Pi passes to the default-exported factory.
97
- * Verified against Pi's exported `ExtensionAPI` interface (a superset — we
98
- * model only the members this plugin uses).
99
- */
100
38
  export interface PiExtensionApi {
101
- /** Subscribe to a lifecycle event. */
102
39
  on<E extends PiEvent>(event: E, handler: PiEventHandlers[E]): void;
103
- /** Register a custom tool. Unused by this plugin; modeled for completeness. */
104
- registerTool?: (name: string, def: unknown) => void;
105
- /** Register a custom command. Unused by this plugin; modeled for completeness. */
106
- registerCommand?: (name: string, def: unknown) => void;
107
- [key: string]: unknown;
108
40
  }
109
- /**
110
- * The default-exported extension factory Pi invokes at load time. Pi awaits
111
- * the returned promise before startup continues, so it is safe to run the
112
- * session-start auth gates in the body.
113
- */
41
+ /** Used both by Pi's CLI loader and DefaultResourceLoader.extensionFactories. */
114
42
  export type PiExtension = (pi: PiExtensionApi) => void | Promise<void>;
package/dist/types.js CHANGED
@@ -1,26 +1,2 @@
1
1
  "use strict";
2
- /**
3
- * Pi (the minimal coding agent — pi.dev / github.com/badlogic/pi-mono) plugin
4
- * types.
5
- *
6
- * Pi loads in-process TypeScript extensions via jiti. The module's DEFAULT
7
- * EXPORT is a factory function `(pi: ExtensionAPI) => void | Promise<void>`.
8
- * Pi awaits async factories before startup continues, so the factory body is
9
- * the de-facto session-start hook: we run the auth gates inline there.
10
- *
11
- * Tool hooks are registered with `pi.on(event, handler)`. The pre-execution
12
- * `tool_call` hook BLOCKS a tool by returning `{ block: true, reason }`;
13
- * returning nothing/undefined allows. A best-effort `tool_result` hook fires
14
- * after a tool completes and is used for activity auditing.
15
- *
16
- * Pi deliberately excludes MCP and sub-agents, so there is no MCP tool
17
- * parsing and no sub-agent identity path.
18
- *
19
- * Contract verified against @earendil-works/pi-coding-agent v0.80.2 (dist
20
- * core/extensions/types.d.ts). The `tool_call` event "Fired before a tool
21
- * executes. Can block." and a handler returns
22
- * `ToolCallEventResult { block?: boolean; reason?: string }`; the
23
- * `tool_result` event "Fired after a tool executes." carries `isError` and
24
- * `content`. The factory default export receives an `ExtensionAPI`.
25
- */
26
2
  Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/pi",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Ory plugin for Pi (the minimal coding agent): scaffolding skills, a local Ory instance, and authentication, authorization, and audit for every tool call",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://ory.com",
@@ -75,7 +75,7 @@
75
75
  ],
76
76
  "dependencies": {
77
77
  "reova": "^0.7.0",
78
- "@ory/argus": "1.3.0"
78
+ "@ory/argus": "1.4.0"
79
79
  },
80
80
  "devDependencies": {
81
81
  "typescript": "^6.0.2"