@hyperspaceng/neural-coding-agent 0.63.0 → 0.64.1
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 +112 -0
- package/README.md +3 -3
- package/dist/core/agent-session.d.ts +14 -6
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +111 -54
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts +3 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +5 -2
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +2 -2
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +3 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +27 -26
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +5 -4
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +4 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +14 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/model-registry.d.ts +21 -3
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +90 -70
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +4 -4
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +88 -24
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts +6 -0
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +37 -5
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +14 -23
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +2 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +3 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/timings.d.ts +1 -0
- package/dist/core/timings.d.ts.map +1 -1
- package/dist/core/timings.js +6 -0
- package/dist/core/timings.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +23 -1
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +150 -57
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +13 -11
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +55 -77
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +4 -4
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/index.d.ts +9 -6
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +2 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +29 -11
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +14 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +18 -5
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +0 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +2 -7
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +51 -67
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +83 -71
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +3 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/compaction.md +4 -2
- package/docs/development.md +3 -1
- package/docs/extensions.md +107 -2
- package/docs/json.md +5 -2
- package/docs/models.md +6 -0
- package/docs/rpc.md +32 -9
- package/docs/sdk.md +12 -9
- package/docs/settings.md +12 -0
- package/docs/skills.md +3 -2
- package/examples/extensions/README.md +1 -0
- package/examples/extensions/custom-compaction.ts +17 -4
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/handoff.ts +5 -2
- package/examples/extensions/hidden-thinking-label.ts +57 -0
- package/examples/extensions/qna.ts +5 -2
- package/examples/extensions/sandbox/index.ts +4 -0
- package/examples/extensions/summarize.ts +15 -4
- package/examples/extensions/trigger-compact.ts +11 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +3 -3
- package/examples/sdk/12-full-control.ts +1 -1
- package/examples/sdk/README.md +3 -3
- package/package.json +5 -4
package/docs/extensions.md
CHANGED
|
@@ -293,10 +293,11 @@ Fired by the `pi` CLI during startup session resolution, before the initial sess
|
|
|
293
293
|
This event is:
|
|
294
294
|
- CLI-only. It is not emitted in SDK mode.
|
|
295
295
|
- Startup-only. It is not emitted for later interactive `/new` or `/resume` actions.
|
|
296
|
-
-
|
|
296
|
+
- Lower priority than `--session-dir` and `sessionDir` in `settings.json`.
|
|
297
297
|
- Special-cased to receive no `ctx` argument.
|
|
298
298
|
|
|
299
299
|
If multiple extensions return `sessionDir`, the last one wins.
|
|
300
|
+
Combined precedence is: `--session-dir` CLI flag, then `sessionDir` in settings, then extension `session_directory` hooks.
|
|
300
301
|
|
|
301
302
|
```typescript
|
|
302
303
|
pi.on("session_directory", async (event) => {
|
|
@@ -564,17 +565,27 @@ Before `tool_call` runs, pi waits for previously emitted Agent events to finish
|
|
|
564
565
|
|
|
565
566
|
In the default parallel tool execution mode, sibling tool calls from the same assistant message are preflighted sequentially, then executed concurrently. `tool_call` is not guaranteed to see sibling tool results from that same assistant message in `ctx.sessionManager`.
|
|
566
567
|
|
|
568
|
+
`event.input` is mutable. Mutate it in place to patch tool arguments before execution.
|
|
569
|
+
|
|
570
|
+
Behavior guarantees:
|
|
571
|
+
- Mutations to `event.input` affect the actual tool execution
|
|
572
|
+
- Later `tool_call` handlers see mutations made by earlier handlers
|
|
573
|
+
- No re-validation is performed after your mutation
|
|
574
|
+
- Return values from `tool_call` only control blocking via `{ block: true, reason?: string }`
|
|
575
|
+
|
|
567
576
|
```typescript
|
|
568
577
|
import { isToolCallEventType } from "@mariozechner/pi-coding-agent";
|
|
569
578
|
|
|
570
579
|
pi.on("tool_call", async (event, ctx) => {
|
|
571
580
|
// event.toolName - "bash", "read", "write", "edit", etc.
|
|
572
581
|
// event.toolCallId
|
|
573
|
-
// event.input - tool parameters
|
|
582
|
+
// event.input - tool parameters (mutable)
|
|
574
583
|
|
|
575
584
|
// Built-in tools: no type params needed
|
|
576
585
|
if (isToolCallEventType("bash", event)) {
|
|
577
586
|
// event.input is { command: string; timeout?: number }
|
|
587
|
+
event.input.command = `source ~/.profile\n${event.input.command}`;
|
|
588
|
+
|
|
578
589
|
if (event.input.command.includes("rm -rf")) {
|
|
579
590
|
return { block: true, reason: "Dangerous command" };
|
|
580
591
|
}
|
|
@@ -618,6 +629,8 @@ Fired after tool execution finishes and before `tool_execution_end` plus the fin
|
|
|
618
629
|
- Each handler sees the latest result after previous handler changes
|
|
619
630
|
- Handlers can return partial patches (`content`, `details`, or `isError`); omitted fields keep their current values
|
|
620
631
|
|
|
632
|
+
Use `ctx.signal` for nested async work inside the handler. This lets Esc cancel model calls, `fetch()`, and other abort-aware operations started by the extension.
|
|
633
|
+
|
|
621
634
|
```typescript
|
|
622
635
|
import { isBashToolResult } from "@mariozechner/pi-coding-agent";
|
|
623
636
|
|
|
@@ -629,6 +642,12 @@ pi.on("tool_result", async (event, ctx) => {
|
|
|
629
642
|
// event.details is typed as BashToolDetails
|
|
630
643
|
}
|
|
631
644
|
|
|
645
|
+
const response = await fetch("https://example.com/summarize", {
|
|
646
|
+
method: "POST",
|
|
647
|
+
body: JSON.stringify({ content: event.content }),
|
|
648
|
+
signal: ctx.signal,
|
|
649
|
+
});
|
|
650
|
+
|
|
632
651
|
// Modify result:
|
|
633
652
|
return { content: [...], details: {...}, isError: false };
|
|
634
653
|
});
|
|
@@ -748,6 +767,31 @@ ctx.sessionManager.getLeafId() // Current leaf entry ID
|
|
|
748
767
|
|
|
749
768
|
Access to models and API keys.
|
|
750
769
|
|
|
770
|
+
### ctx.signal
|
|
771
|
+
|
|
772
|
+
The current agent abort signal, or `undefined` when no agent turn is active.
|
|
773
|
+
|
|
774
|
+
Use this for abort-aware nested work started by extension handlers, for example:
|
|
775
|
+
- `fetch(..., { signal: ctx.signal })`
|
|
776
|
+
- model calls that accept `signal`
|
|
777
|
+
- file or process helpers that accept `AbortSignal`
|
|
778
|
+
|
|
779
|
+
`ctx.signal` is typically defined during active turn events such as `tool_call`, `tool_result`, `message_update`, and `turn_end`.
|
|
780
|
+
It is usually `undefined` in idle or non-turn contexts such as session events, extension commands, and shortcuts fired while pi is idle.
|
|
781
|
+
|
|
782
|
+
```typescript
|
|
783
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
784
|
+
const response = await fetch("https://example.com/api", {
|
|
785
|
+
method: "POST",
|
|
786
|
+
body: JSON.stringify(event),
|
|
787
|
+
signal: ctx.signal,
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
const data = await response.json();
|
|
791
|
+
return { details: data };
|
|
792
|
+
});
|
|
793
|
+
```
|
|
794
|
+
|
|
751
795
|
### ctx.isIdle() / ctx.abort() / ctx.hasPendingMessages()
|
|
752
796
|
|
|
753
797
|
Control flow helpers.
|
|
@@ -964,6 +1008,12 @@ pi.registerTool({
|
|
|
964
1008
|
action: StringEnum(["list", "add"] as const),
|
|
965
1009
|
text: Type.Optional(Type.String()),
|
|
966
1010
|
}),
|
|
1011
|
+
prepareArguments(args) {
|
|
1012
|
+
// Optional compatibility shim. Runs before schema validation.
|
|
1013
|
+
// Return the current schema shape, for example to fold legacy fields
|
|
1014
|
+
// into the modern parameter object.
|
|
1015
|
+
return args;
|
|
1016
|
+
},
|
|
967
1017
|
|
|
968
1018
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
969
1019
|
// Stream progress
|
|
@@ -1428,6 +1478,14 @@ pi.registerTool({
|
|
|
1428
1478
|
action: StringEnum(["list", "add"] as const), // Use StringEnum for Google compatibility
|
|
1429
1479
|
text: Type.Optional(Type.String()),
|
|
1430
1480
|
}),
|
|
1481
|
+
prepareArguments(args) {
|
|
1482
|
+
if (!args || typeof args !== "object") return args;
|
|
1483
|
+
const input = args as { action?: string; oldAction?: string };
|
|
1484
|
+
if (typeof input.oldAction === "string" && input.action === undefined) {
|
|
1485
|
+
return { ...input, action: input.oldAction };
|
|
1486
|
+
}
|
|
1487
|
+
return args;
|
|
1488
|
+
},
|
|
1431
1489
|
|
|
1432
1490
|
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
1433
1491
|
// Check for cancellation
|
|
@@ -1471,6 +1529,53 @@ async execute(toolCallId, params) {
|
|
|
1471
1529
|
|
|
1472
1530
|
**Important:** Use `StringEnum` from `@mariozechner/pi-ai` for string enums. `Type.Union`/`Type.Literal` doesn't work with Google's API.
|
|
1473
1531
|
|
|
1532
|
+
**Argument preparation:** `prepareArguments(args)` is optional. If defined, it runs before schema validation and before `execute()`. Use it to mimic an older accepted input shape when pi resumes an older session whose stored tool call arguments no longer match the current schema. Return the object you want validated against `parameters`. Keep the public schema strict. Do not add deprecated compatibility fields to `parameters` just to keep old resumed sessions working.
|
|
1533
|
+
|
|
1534
|
+
Example: an older session may contain an `edit` tool call with top-level `oldText` and `newText`, while the current schema only accepts `edits: [{ oldText, newText }]`.
|
|
1535
|
+
|
|
1536
|
+
```typescript
|
|
1537
|
+
pi.registerTool({
|
|
1538
|
+
name: "edit",
|
|
1539
|
+
label: "Edit",
|
|
1540
|
+
description: "Edit a single file using exact text replacement",
|
|
1541
|
+
parameters: Type.Object({
|
|
1542
|
+
path: Type.String(),
|
|
1543
|
+
edits: Type.Array(
|
|
1544
|
+
Type.Object({
|
|
1545
|
+
oldText: Type.String(),
|
|
1546
|
+
newText: Type.String(),
|
|
1547
|
+
}),
|
|
1548
|
+
),
|
|
1549
|
+
}),
|
|
1550
|
+
prepareArguments(args) {
|
|
1551
|
+
if (!args || typeof args !== "object") return args;
|
|
1552
|
+
|
|
1553
|
+
const input = args as {
|
|
1554
|
+
path?: string;
|
|
1555
|
+
edits?: Array<{ oldText: string; newText: string }>;
|
|
1556
|
+
oldText?: unknown;
|
|
1557
|
+
newText?: unknown;
|
|
1558
|
+
};
|
|
1559
|
+
|
|
1560
|
+
if (typeof input.oldText !== "string" || typeof input.newText !== "string") {
|
|
1561
|
+
return args;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
return {
|
|
1565
|
+
...input,
|
|
1566
|
+
edits: [...(input.edits ?? []), { oldText: input.oldText, newText: input.newText }],
|
|
1567
|
+
};
|
|
1568
|
+
},
|
|
1569
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
1570
|
+
// params now matches the current schema
|
|
1571
|
+
return {
|
|
1572
|
+
content: [{ type: "text", text: `Applying ${params.edits.length} edit block(s)` }],
|
|
1573
|
+
details: {},
|
|
1574
|
+
};
|
|
1575
|
+
},
|
|
1576
|
+
});
|
|
1577
|
+
```
|
|
1578
|
+
|
|
1474
1579
|
### Overriding Built-in Tools
|
|
1475
1580
|
|
|
1476
1581
|
Extensions can override built-in tools (`read`, `bash`, `edit`, `write`, `grep`, `find`, `ls`) by registering a tool with the same name. Interactive mode displays a warning when this happens.
|
package/docs/json.md
CHANGED
|
@@ -13,12 +13,15 @@ Events are defined in [`AgentSessionEvent`](https://github.com/badlogic/pi-mono/
|
|
|
13
13
|
```typescript
|
|
14
14
|
type AgentSessionEvent =
|
|
15
15
|
| AgentEvent
|
|
16
|
-
| { type: "
|
|
17
|
-
| { type: "
|
|
16
|
+
| { type: "queue_update"; steering: readonly string[]; followUp: readonly string[] }
|
|
17
|
+
| { type: "compaction_start"; reason: "manual" | "threshold" | "overflow" }
|
|
18
|
+
| { type: "compaction_end"; reason: "manual" | "threshold" | "overflow"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean; errorMessage?: string }
|
|
18
19
|
| { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
|
|
19
20
|
| { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string };
|
|
20
21
|
```
|
|
21
22
|
|
|
23
|
+
`queue_update` emits the full pending steering and follow-up queues whenever they change. `compaction_start` and `compaction_end` cover both manual and automatic compaction.
|
|
24
|
+
|
|
22
25
|
Base events from [`AgentEvent`](https://github.com/badlogic/pi-mono/blob/main/packages/agent/src/types.ts#L179):
|
|
23
26
|
|
|
24
27
|
```typescript
|
package/docs/models.md
CHANGED
|
@@ -131,6 +131,12 @@ The `apiKey` and `headers` fields support three formats:
|
|
|
131
131
|
"apiKey": "sk-..."
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
For `models.json`, shell commands are resolved at request time. pi intentionally does not apply built-in TTL, stale reuse, or recovery logic for arbitrary commands. Different commands need different caching and failure strategies, and pi cannot infer the right one.
|
|
135
|
+
|
|
136
|
+
If your command is slow, expensive, rate-limited, or should keep using a previous value on transient failures, wrap it in your own script or command that implements the caching or TTL behavior you want.
|
|
137
|
+
|
|
138
|
+
`/model` availability checks use configured auth presence and do not execute shell commands.
|
|
139
|
+
|
|
134
140
|
### Custom Headers
|
|
135
141
|
|
|
136
142
|
```json
|
package/docs/rpc.md
CHANGED
|
@@ -494,7 +494,7 @@ Response:
|
|
|
494
494
|
|
|
495
495
|
#### get_session_stats
|
|
496
496
|
|
|
497
|
-
Get token usage and
|
|
497
|
+
Get token usage, cost statistics, and current context window usage.
|
|
498
498
|
|
|
499
499
|
```json
|
|
500
500
|
{"type": "get_session_stats"}
|
|
@@ -521,11 +521,20 @@ Response:
|
|
|
521
521
|
"cacheWrite": 5000,
|
|
522
522
|
"total": 105000
|
|
523
523
|
},
|
|
524
|
-
"cost": 0.45
|
|
524
|
+
"cost": 0.45,
|
|
525
|
+
"contextUsage": {
|
|
526
|
+
"tokens": 60000,
|
|
527
|
+
"contextWindow": 200000,
|
|
528
|
+
"percent": 30
|
|
529
|
+
}
|
|
525
530
|
}
|
|
526
531
|
}
|
|
527
532
|
```
|
|
528
533
|
|
|
534
|
+
`tokens` contains assistant usage totals for the current session state. `contextUsage` contains the actual current context-window estimate used for compaction and footer display.
|
|
535
|
+
|
|
536
|
+
`contextUsage` is omitted when no model or context window is available. `contextUsage.tokens` and `contextUsage.percent` are `null` immediately after compaction until a fresh post-compaction assistant response provides valid usage data.
|
|
537
|
+
|
|
529
538
|
#### export_html
|
|
530
539
|
|
|
531
540
|
Export session to an HTML file.
|
|
@@ -716,8 +725,9 @@ Events are streamed to stdout as JSON lines during agent operation. Events do NO
|
|
|
716
725
|
| `tool_execution_start` | Tool begins execution |
|
|
717
726
|
| `tool_execution_update` | Tool execution progress (streaming output) |
|
|
718
727
|
| `tool_execution_end` | Tool completes |
|
|
719
|
-
| `
|
|
720
|
-
| `
|
|
728
|
+
| `queue_update` | Pending steering/follow-up queue changed |
|
|
729
|
+
| `compaction_start` | Compaction begins |
|
|
730
|
+
| `compaction_end` | Compaction completes |
|
|
721
731
|
| `auto_retry_start` | Auto-retry begins (after transient error) |
|
|
722
732
|
| `auto_retry_end` | Auto-retry completes (success or final failure) |
|
|
723
733
|
| `extension_error` | Extension threw an error |
|
|
@@ -853,19 +863,32 @@ When complete:
|
|
|
853
863
|
|
|
854
864
|
Use `toolCallId` to correlate events. The `partialResult` in `tool_execution_update` contains the accumulated output so far (not just the delta), allowing clients to simply replace their display on each update.
|
|
855
865
|
|
|
856
|
-
###
|
|
866
|
+
### queue_update
|
|
867
|
+
|
|
868
|
+
Emitted whenever the pending steering or follow-up queue changes.
|
|
869
|
+
|
|
870
|
+
```json
|
|
871
|
+
{
|
|
872
|
+
"type": "queue_update",
|
|
873
|
+
"steering": ["Focus on error handling"],
|
|
874
|
+
"followUp": ["After that, summarize the result"]
|
|
875
|
+
}
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### compaction_start / compaction_end
|
|
857
879
|
|
|
858
|
-
Emitted when
|
|
880
|
+
Emitted when compaction runs, whether manual or automatic.
|
|
859
881
|
|
|
860
882
|
```json
|
|
861
|
-
{"type": "
|
|
883
|
+
{"type": "compaction_start", "reason": "threshold"}
|
|
862
884
|
```
|
|
863
885
|
|
|
864
|
-
The `reason` field is `"threshold"
|
|
886
|
+
The `reason` field is `"manual"`, `"threshold"`, or `"overflow"`.
|
|
865
887
|
|
|
866
888
|
```json
|
|
867
889
|
{
|
|
868
|
-
"type": "
|
|
890
|
+
"type": "compaction_end",
|
|
891
|
+
"reason": "threshold",
|
|
869
892
|
"result": {
|
|
870
893
|
"summary": "Summary of conversation...",
|
|
871
894
|
"firstKeptEntryId": "abc123",
|
package/docs/sdk.md
CHANGED
|
@@ -20,7 +20,7 @@ import { AuthStorage, createAgentSession, ModelRegistry, SessionManager } from "
|
|
|
20
20
|
|
|
21
21
|
// Set up credential storage and model registry
|
|
22
22
|
const authStorage = AuthStorage.create();
|
|
23
|
-
const modelRegistry =
|
|
23
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
24
24
|
|
|
25
25
|
const { session } = await createAgentSession({
|
|
26
26
|
sessionManager: SessionManager.inMemory(),
|
|
@@ -232,9 +232,12 @@ session.subscribe((event) => {
|
|
|
232
232
|
// event.toolResults: tool results from this turn
|
|
233
233
|
break;
|
|
234
234
|
|
|
235
|
-
// Session events (
|
|
236
|
-
case "
|
|
237
|
-
|
|
235
|
+
// Session events (queue, compaction, retry)
|
|
236
|
+
case "queue_update":
|
|
237
|
+
console.log(event.steering, event.followUp);
|
|
238
|
+
break;
|
|
239
|
+
case "compaction_start":
|
|
240
|
+
case "compaction_end":
|
|
238
241
|
case "auto_retry_start":
|
|
239
242
|
case "auto_retry_end":
|
|
240
243
|
break;
|
|
@@ -286,7 +289,7 @@ import { getModel } from "@mariozechner/pi-ai";
|
|
|
286
289
|
import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
287
290
|
|
|
288
291
|
const authStorage = AuthStorage.create();
|
|
289
|
-
const modelRegistry =
|
|
292
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
290
293
|
|
|
291
294
|
// Find specific built-in model (doesn't check if API key exists)
|
|
292
295
|
const opus = getModel("anthropic", "claude-opus-4-5");
|
|
@@ -334,7 +337,7 @@ import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
|
|
|
334
337
|
|
|
335
338
|
// Default: uses ~/.pi/agent/auth.json and ~/.pi/agent/models.json
|
|
336
339
|
const authStorage = AuthStorage.create();
|
|
337
|
-
const modelRegistry =
|
|
340
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
338
341
|
|
|
339
342
|
const { session } = await createAgentSession({
|
|
340
343
|
sessionManager: SessionManager.inMemory(),
|
|
@@ -347,7 +350,7 @@ authStorage.setRuntimeApiKey("anthropic", "sk-my-temp-key");
|
|
|
347
350
|
|
|
348
351
|
// Custom auth storage location
|
|
349
352
|
const customAuth = AuthStorage.create("/my/app/auth.json");
|
|
350
|
-
const customRegistry =
|
|
353
|
+
const customRegistry = ModelRegistry.create(customAuth, "/my/app/models.json");
|
|
351
354
|
|
|
352
355
|
const { session } = await createAgentSession({
|
|
353
356
|
sessionManager: SessionManager.inMemory(),
|
|
@@ -356,7 +359,7 @@ const { session } = await createAgentSession({
|
|
|
356
359
|
});
|
|
357
360
|
|
|
358
361
|
// No custom models.json (built-in models only)
|
|
359
|
-
const simpleRegistry =
|
|
362
|
+
const simpleRegistry = ModelRegistry.inMemory(authStorage);
|
|
360
363
|
```
|
|
361
364
|
|
|
362
365
|
> See [examples/sdk/09-api-keys-and-oauth.ts](../examples/sdk/09-api-keys-and-oauth.ts)
|
|
@@ -785,7 +788,7 @@ if (process.env.MY_KEY) {
|
|
|
785
788
|
}
|
|
786
789
|
|
|
787
790
|
// Model registry (no custom models.json)
|
|
788
|
-
const modelRegistry =
|
|
791
|
+
const modelRegistry = ModelRegistry.create(authStorage);
|
|
789
792
|
|
|
790
793
|
// Inline tool
|
|
791
794
|
const statusTool: ToolDefinition = {
|
package/docs/settings.md
CHANGED
|
@@ -127,6 +127,18 @@ When a provider requests a retry delay longer than `maxDelayMs` (e.g., Google's
|
|
|
127
127
|
|
|
128
128
|
`npmCommand` is used for all npm package-manager operations, including `npm root -g`, installs, uninstalls, and `npm install` inside git packages. Use argv-style entries exactly as the process should be launched.
|
|
129
129
|
|
|
130
|
+
### Sessions
|
|
131
|
+
|
|
132
|
+
| Setting | Type | Default | Description |
|
|
133
|
+
|---------|------|---------|-------------|
|
|
134
|
+
| `sessionDir` | string | - | Directory where session files are stored. Accepts absolute or relative paths. |
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{ "sessionDir": ".pi/sessions" }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
When multiple sources specify a session directory, `--session-dir` CLI flag takes precedence, then `sessionDir` in settings.json, then extension hooks.
|
|
141
|
+
|
|
130
142
|
### Model Cycling
|
|
131
143
|
|
|
132
144
|
| Setting | Type | Default | Description |
|
package/docs/skills.md
CHANGED
|
@@ -34,8 +34,9 @@ Pi loads skills from:
|
|
|
34
34
|
- CLI: `--skill <path>` (repeatable, additive even with `--no-skills`)
|
|
35
35
|
|
|
36
36
|
Discovery rules:
|
|
37
|
-
-
|
|
38
|
-
-
|
|
37
|
+
- In `~/.pi/agent/skills/` and `.pi/skills/`, direct root `.md` files are discovered as individual skills
|
|
38
|
+
- In all skill locations, directories containing `SKILL.md` are discovered recursively
|
|
39
|
+
- In `~/.agents/skills/` and project `.agents/skills/`, root `.md` files are ignored
|
|
39
40
|
|
|
40
41
|
Disable discovery with `--no-skills` (explicit `--skill` paths still load).
|
|
41
42
|
|
|
@@ -52,6 +52,7 @@ cp permission-gate.ts ~/.pi/agent/extensions/
|
|
|
52
52
|
| `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` |
|
|
53
53
|
| `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors |
|
|
54
54
|
| `widget-placement.ts` | Shows widgets above and below the editor via `ctx.ui.setWidget()` placement |
|
|
55
|
+
| `hidden-thinking-label.ts` | Customizes the collapsed thinking label via `ctx.ui.setHiddenThinkingLabel()` |
|
|
55
56
|
| `model-status.ts` | Shows model changes in status bar via `model_select` hook |
|
|
56
57
|
| `snake.ts` | Snake game with custom UI, keyboard handling, and session persistence |
|
|
57
58
|
| `send-user-message.ts` | Demonstrates `pi.sendUserMessage()` for sending user messages from extensions |
|
|
@@ -31,9 +31,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// Resolve
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
34
|
+
// Resolve request auth for the summarization model
|
|
35
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
|
|
36
|
+
if (!auth.ok) {
|
|
37
|
+
ctx.ui.notify(`Compaction auth failed: ${auth.error}`, "warning");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!auth.apiKey) {
|
|
37
41
|
ctx.ui.notify(`No API key for ${model.provider}, using default compaction`, "warning");
|
|
38
42
|
return;
|
|
39
43
|
}
|
|
@@ -83,7 +87,16 @@ ${conversationText}
|
|
|
83
87
|
|
|
84
88
|
try {
|
|
85
89
|
// Pass signal to honor abort requests (e.g., user cancels compaction)
|
|
86
|
-
const response = await complete(
|
|
90
|
+
const response = await complete(
|
|
91
|
+
model,
|
|
92
|
+
{ messages: summaryMessages },
|
|
93
|
+
{
|
|
94
|
+
apiKey: auth.apiKey,
|
|
95
|
+
headers: auth.headers,
|
|
96
|
+
maxTokens: 8192,
|
|
97
|
+
signal,
|
|
98
|
+
},
|
|
99
|
+
);
|
|
87
100
|
|
|
88
101
|
const summary = response.content
|
|
89
102
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-extension-custom-provider",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pi-extension-custom-provider",
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.15.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@anthropic-ai/sdk": "^0.52.0"
|
|
12
12
|
}
|
|
@@ -80,7 +80,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
80
80
|
loader.onAbort = () => done(null);
|
|
81
81
|
|
|
82
82
|
const doGenerate = async () => {
|
|
83
|
-
const
|
|
83
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
|
|
84
|
+
if (!auth.ok || !auth.apiKey) {
|
|
85
|
+
throw new Error(auth.ok ? `No API key for ${ctx.model!.provider}` : auth.error);
|
|
86
|
+
}
|
|
84
87
|
|
|
85
88
|
const userMessage: Message = {
|
|
86
89
|
role: "user",
|
|
@@ -96,7 +99,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
96
99
|
const response = await complete(
|
|
97
100
|
ctx.model!,
|
|
98
101
|
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
|
99
|
-
{ apiKey, signal: loader.signal },
|
|
102
|
+
{ apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
|
|
100
103
|
);
|
|
101
104
|
|
|
102
105
|
if (response.stopReason === "aborted") {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hidden Thinking Label Extension
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates `ctx.ui.setHiddenThinkingLabel()` for customizing the label shown
|
|
5
|
+
* when thinking blocks are hidden.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* pi --extension examples/extensions/hidden-thinking-label.ts
|
|
9
|
+
*
|
|
10
|
+
* Test:
|
|
11
|
+
* 1. Load this extension
|
|
12
|
+
* 2. Hide thinking blocks with Ctrl+T
|
|
13
|
+
* 3. Ask for something that produces reasoning output
|
|
14
|
+
* 4. The collapsed thinking block label will show the custom text
|
|
15
|
+
*
|
|
16
|
+
* Commands:
|
|
17
|
+
* /thinking-label <text> Set a custom hidden thinking label
|
|
18
|
+
* /thinking-label Reset to the default label
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
22
|
+
|
|
23
|
+
const DEFAULT_LABEL = "Pondering...";
|
|
24
|
+
|
|
25
|
+
export default function (pi: ExtensionAPI) {
|
|
26
|
+
let label = DEFAULT_LABEL;
|
|
27
|
+
|
|
28
|
+
const applyLabel = (ctx: ExtensionContext) => {
|
|
29
|
+
ctx.ui.setHiddenThinkingLabel(label);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
33
|
+
applyLabel(ctx);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
pi.on("session_switch", async (_event, ctx) => {
|
|
37
|
+
applyLabel(ctx);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
pi.registerCommand("thinking-label", {
|
|
41
|
+
description: "Set the hidden thinking label. Use without args to reset.",
|
|
42
|
+
handler: async (args, ctx) => {
|
|
43
|
+
const nextLabel = args.trim();
|
|
44
|
+
|
|
45
|
+
if (!nextLabel) {
|
|
46
|
+
label = DEFAULT_LABEL;
|
|
47
|
+
ctx.ui.setHiddenThinkingLabel();
|
|
48
|
+
ctx.ui.notify(`Hidden thinking label reset to: ${DEFAULT_LABEL}`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
label = nextLabel;
|
|
53
|
+
ctx.ui.setHiddenThinkingLabel(label);
|
|
54
|
+
ctx.ui.notify(`Hidden thinking label set to: ${label}`);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -77,7 +77,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
77
77
|
|
|
78
78
|
// Do the work
|
|
79
79
|
const doExtract = async () => {
|
|
80
|
-
const
|
|
80
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(ctx.model!);
|
|
81
|
+
if (!auth.ok || !auth.apiKey) {
|
|
82
|
+
throw new Error(auth.ok ? `No API key for ${ctx.model!.provider}` : auth.error);
|
|
83
|
+
}
|
|
81
84
|
const userMessage: UserMessage = {
|
|
82
85
|
role: "user",
|
|
83
86
|
content: [{ type: "text", text: lastAssistantText! }],
|
|
@@ -87,7 +90,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
87
90
|
const response = await complete(
|
|
88
91
|
ctx.model!,
|
|
89
92
|
{ systemPrompt: SYSTEM_PROMPT, messages: [userMessage] },
|
|
90
|
-
{ apiKey, signal: loader.signal },
|
|
93
|
+
{ apiKey: auth.apiKey, headers: auth.headers, signal: loader.signal },
|
|
91
94
|
);
|
|
92
95
|
|
|
93
96
|
if (response.stopReason === "aborted") {
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* restrictions on bash commands at the OS level (sandbox-exec on macOS,
|
|
6
6
|
* bubblewrap on Linux).
|
|
7
7
|
*
|
|
8
|
+
* Note: this example intentionally overrides the built-in `bash` tool to show
|
|
9
|
+
* how built-in tools can be replaced. Alternatively, you could sandbox `bash`
|
|
10
|
+
* via `tool_call` input mutation without replacing the tool.
|
|
11
|
+
*
|
|
8
12
|
* Config files (merged, project takes precedence):
|
|
9
13
|
* - ~/.pi/agent/sandbox.json (global)
|
|
10
14
|
* - <cwd>/.pi/sandbox.json (project-local)
|
|
@@ -165,12 +165,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
165
165
|
ctx.ui.notify("Model openai/gpt-5.2 not found", "warning");
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
const
|
|
169
|
-
if (!
|
|
168
|
+
const auth = model ? await ctx.modelRegistry.getApiKeyAndHeaders(model) : undefined;
|
|
169
|
+
if (auth && !auth.ok && ctx.hasUI) {
|
|
170
|
+
ctx.ui.notify(auth.error, "warning");
|
|
171
|
+
}
|
|
172
|
+
if (auth?.ok && !auth.apiKey && ctx.hasUI) {
|
|
170
173
|
ctx.ui.notify("No API key for openai/gpt-5.2", "warning");
|
|
171
174
|
}
|
|
172
175
|
|
|
173
|
-
if (!model || !apiKey) {
|
|
176
|
+
if (!model || !auth?.ok || !auth.apiKey) {
|
|
174
177
|
return;
|
|
175
178
|
}
|
|
176
179
|
|
|
@@ -182,7 +185,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
182
185
|
},
|
|
183
186
|
];
|
|
184
187
|
|
|
185
|
-
const response = await complete(
|
|
188
|
+
const response = await complete(
|
|
189
|
+
model,
|
|
190
|
+
{ messages: summaryMessages },
|
|
191
|
+
{
|
|
192
|
+
apiKey: auth.apiKey,
|
|
193
|
+
headers: auth.headers,
|
|
194
|
+
reasoningEffort: "high",
|
|
195
|
+
},
|
|
196
|
+
);
|
|
186
197
|
|
|
187
198
|
const summary = response.content
|
|
188
199
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
@@ -3,6 +3,8 @@ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-age
|
|
|
3
3
|
const COMPACT_THRESHOLD_TOKENS = 100_000;
|
|
4
4
|
|
|
5
5
|
export default function (pi: ExtensionAPI) {
|
|
6
|
+
let previousTokens: number | null | undefined;
|
|
7
|
+
|
|
6
8
|
const triggerCompaction = (ctx: ExtensionContext, customInstructions?: string) => {
|
|
7
9
|
if (ctx.hasUI) {
|
|
8
10
|
ctx.ui.notify("Compaction started", "info");
|
|
@@ -24,7 +26,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
24
26
|
|
|
25
27
|
pi.on("turn_end", (_event, ctx) => {
|
|
26
28
|
const usage = ctx.getContextUsage();
|
|
27
|
-
|
|
29
|
+
const currentTokens = usage?.tokens ?? null;
|
|
30
|
+
if (currentTokens === null) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const crossedThreshold =
|
|
35
|
+
previousTokens !== undefined && previousTokens !== null && previousTokens <= COMPACT_THRESHOLD_TOKENS;
|
|
36
|
+
previousTokens = currentTokens;
|
|
37
|
+
if (!crossedThreshold || currentTokens <= COMPACT_THRESHOLD_TOKENS) {
|
|
28
38
|
return;
|
|
29
39
|
}
|
|
30
40
|
triggerCompaction(ctx);
|