noosphere 0.1.2 → 0.1.3
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 +476 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -478,68 +478,507 @@ A unified gateway that routes to 8 LLM providers through 4 different API protoco
|
|
|
478
478
|
|
|
479
479
|
Aggregator providing access to hundreds of additional models including Llama, Deepseek, Mistral, Qwen, and many more. Full list available via `ai.getModels('llm')`.
|
|
480
480
|
|
|
481
|
-
####
|
|
481
|
+
#### The Pi-AI Engine — Deep Dive
|
|
482
482
|
|
|
483
|
-
|
|
483
|
+
Noosphere's LLM provider is powered by `@mariozechner/pi-ai`, part of the **Pi mono-repo** by Mario Zechner (badlogic). Pi is NOT a wrapper like LangChain or Mastra — it's a **micro-framework for agentic AI** (~15K LOC, 4 npm packages) that was built from scratch as a minimalist alternative to Claude Code.
|
|
484
|
+
|
|
485
|
+
Pi consists of 4 packages in 3 tiers:
|
|
486
|
+
|
|
487
|
+
```
|
|
488
|
+
TIER 1 — FOUNDATION
|
|
489
|
+
@mariozechner/pi-ai LLM API: stream(), complete(), model registry
|
|
490
|
+
0 internal deps, talks to 20+ providers
|
|
491
|
+
|
|
492
|
+
TIER 2 — INFRASTRUCTURE
|
|
493
|
+
@mariozechner/pi-agent-core Agent loop, tool execution, lifecycle events
|
|
494
|
+
Depends on pi-ai
|
|
495
|
+
|
|
496
|
+
@mariozechner/pi-tui Terminal UI with differential rendering
|
|
497
|
+
Standalone, 0 internal deps
|
|
498
|
+
|
|
499
|
+
TIER 3 — APPLICATION
|
|
500
|
+
@mariozechner/pi-coding-agent CLI + SDK: sessions, compaction, extensions
|
|
501
|
+
Depends on all above
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Noosphere uses `@mariozechner/pi-ai` (Tier 1) directly for LLM access. But the full Pi ecosystem provides capabilities that can be layered on top.
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
#### How Pi Keeps 200+ Models Updated
|
|
509
|
+
|
|
510
|
+
Pi does NOT hardcode models. It has an **auto-generation pipeline** that runs at build time:
|
|
511
|
+
|
|
512
|
+
```
|
|
513
|
+
STEP 1: FETCH (3 sources in parallel)
|
|
514
|
+
┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐
|
|
515
|
+
│ models.dev │ │ OpenRouter │ │ Vercel AI │
|
|
516
|
+
│ /api.json │ │ /v1/models │ │ Gateway │
|
|
517
|
+
│ │ │ │ │ /v1/models │
|
|
518
|
+
│ Context windows │ │ Pricing ($/M) │ │ Capability │
|
|
519
|
+
│ Capabilities │ │ Availability │ │ tags │
|
|
520
|
+
│ Tool support │ │ Provider routing │ │ │
|
|
521
|
+
└────────┬─────────┘ └────────┬─────────┘ └──────┬────────┘
|
|
522
|
+
└─────────┬───────────┴────────────────────┘
|
|
523
|
+
▼
|
|
524
|
+
STEP 2: MERGE & DEDUPLICATE
|
|
525
|
+
Priority: models.dev > OpenRouter > Vercel
|
|
526
|
+
Key: provider + modelId
|
|
527
|
+
│
|
|
528
|
+
▼
|
|
529
|
+
STEP 3: FILTER
|
|
530
|
+
✅ tool_call === true
|
|
531
|
+
✅ streaming supported
|
|
532
|
+
✅ system messages supported
|
|
533
|
+
✅ not deprecated
|
|
534
|
+
│
|
|
535
|
+
▼
|
|
536
|
+
STEP 4: NORMALIZE
|
|
537
|
+
Costs → $/million tokens
|
|
538
|
+
API type → one of 4 protocols
|
|
539
|
+
Input modes → ["text"] or ["text","image"]
|
|
540
|
+
│
|
|
541
|
+
▼
|
|
542
|
+
STEP 5: PATCH (manual corrections)
|
|
543
|
+
Claude Opus: cache pricing fix
|
|
544
|
+
GPT-5.4: context window override
|
|
545
|
+
Kimi K2.5: hardcoded pricing
|
|
546
|
+
│
|
|
547
|
+
▼
|
|
548
|
+
STEP 6: GENERATE TypeScript
|
|
549
|
+
→ models.generated.ts (~330KB)
|
|
550
|
+
→ 200+ models with full type safety
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Each generated model entry looks like:
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
{
|
|
557
|
+
id: "claude-opus-4-6",
|
|
558
|
+
name: "Claude Opus 4.6",
|
|
559
|
+
api: "anthropic-messages",
|
|
560
|
+
provider: "anthropic",
|
|
561
|
+
baseUrl: "https://api.anthropic.com",
|
|
562
|
+
reasoning: true,
|
|
563
|
+
input: ["text", "image"],
|
|
564
|
+
cost: {
|
|
565
|
+
input: 15, // $15/M tokens
|
|
566
|
+
output: 75, // $75/M tokens
|
|
567
|
+
cacheRead: 1.5, // prompt cache hit
|
|
568
|
+
cacheWrite: 18.75, // prompt cache write
|
|
569
|
+
},
|
|
570
|
+
contextWindow: 200_000,
|
|
571
|
+
maxTokens: 32_000,
|
|
572
|
+
} satisfies Model<"anthropic-messages">
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
When a new model is released (e.g., Gemini 3.0), it appears in models.dev/OpenRouter → the script captures it → a new Pi version is published → Noosphere updates its dependency.
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
#### 4 API Protocols — How Pi Talks to Every Provider
|
|
580
|
+
|
|
581
|
+
Pi abstracts all LLM providers into 4 wire protocols. Each protocol handles the differences in request format, streaming format, auth headers, and response parsing:
|
|
582
|
+
|
|
583
|
+
| Protocol | Providers | Key Differences |
|
|
584
|
+
|---|---|---|
|
|
585
|
+
| `anthropic-messages` | Anthropic, AWS Bedrock | `system` as top-level field, content as `[{type:"text", text:"..."}]` blocks, `x-api-key` auth, `anthropic-beta` headers |
|
|
586
|
+
| `openai-completions` | OpenAI, xAI, Groq, Cerebras, OpenRouter, Ollama, vLLM | `system` as message with `role:"system"`, content as string, `Authorization: Bearer` auth, `tool_calls` array |
|
|
587
|
+
| `openai-responses` | OpenAI (reasoning models) | New Responses API with server-side context, `store: true`, reasoning summaries |
|
|
588
|
+
| `google-generative-ai` | Google Gemini, Vertex AI | `systemInstruction.parts[{text}]`, role `"model"` instead of `"assistant"`, `functionCall` instead of `tool_calls`, `thinkingConfig` |
|
|
589
|
+
|
|
590
|
+
The core function `streamSimple()` detects which protocol to use based on `model.api` and handles all the formatting/parsing transparently:
|
|
484
591
|
|
|
485
|
-
**Tool Use / Function Calling:**
|
|
486
592
|
```typescript
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
593
|
+
// What happens inside Pi when you call Noosphere's chat():
|
|
594
|
+
async function* streamSimple(
|
|
595
|
+
model: Model, // includes model.api to determine protocol
|
|
596
|
+
context: Context, // { systemPrompt, messages, tools }
|
|
597
|
+
options?: StreamOptions // { signal, onPayload, thinkingLevel, ... }
|
|
598
|
+
): AsyncIterable<AssistantMessageEvent> {
|
|
599
|
+
// 1. Format request according to model.api protocol
|
|
600
|
+
// 2. Open SSE/WebSocket stream
|
|
601
|
+
// 3. Parse provider-specific chunks
|
|
602
|
+
// 4. Emit normalized events:
|
|
603
|
+
// → text_delta, thinking_delta, tool_call, message_end
|
|
493
604
|
}
|
|
494
605
|
```
|
|
495
606
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
#### Agentic Capabilities
|
|
610
|
+
|
|
611
|
+
These are the capabilities people get access to through the Pi-AI engine:
|
|
612
|
+
|
|
613
|
+
##### 1. Tool Use / Function Calling
|
|
614
|
+
|
|
615
|
+
Full structured tool calling supported across **all major providers**. Tool definitions use TypeBox schemas with runtime validation via AJV:
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
import { type Tool, StringEnum } from '@mariozechner/pi-ai';
|
|
619
|
+
import { Type } from '@sinclair/typebox';
|
|
620
|
+
|
|
621
|
+
// Define a tool with typed parameters
|
|
622
|
+
const searchTool: Tool = {
|
|
623
|
+
name: 'web_search',
|
|
624
|
+
description: 'Search the web for information',
|
|
625
|
+
parameters: Type.Object({
|
|
626
|
+
query: Type.String({ description: 'Search query' }),
|
|
627
|
+
maxResults: Type.Optional(Type.Number({ default: 5 })),
|
|
628
|
+
type: StringEnum(['web', 'images', 'news'], { description: 'Search type' }),
|
|
629
|
+
}),
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// Pass tools in context — Pi handles the rest
|
|
633
|
+
const context = {
|
|
634
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
635
|
+
messages: [{ role: 'user', content: 'Search for recent AI news' }],
|
|
636
|
+
tools: [searchTool],
|
|
637
|
+
};
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
**How tool calling works internally:**
|
|
641
|
+
|
|
642
|
+
```
|
|
643
|
+
User prompt → LLM → "I need to call web_search"
|
|
644
|
+
│
|
|
645
|
+
▼
|
|
646
|
+
Pi validates arguments with AJV
|
|
647
|
+
against the TypeBox schema
|
|
648
|
+
│
|
|
649
|
+
┌─────┴─────┐
|
|
650
|
+
│ Valid? │
|
|
651
|
+
├─Yes───────┤
|
|
652
|
+
│ Execute │
|
|
653
|
+
│ tool │
|
|
654
|
+
├───────────┤
|
|
655
|
+
│ No │
|
|
656
|
+
│ Return │
|
|
657
|
+
│ validation│
|
|
658
|
+
│ error to │
|
|
659
|
+
│ LLM │
|
|
660
|
+
└───────────┘
|
|
661
|
+
│
|
|
662
|
+
▼
|
|
663
|
+
Tool result → back into context → LLM continues
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**Provider-specific tool_choice control:**
|
|
667
|
+
- **Anthropic:** `"auto" | "any" | "none" | { type: "tool", name: "specific_tool" }`
|
|
668
|
+
- **OpenAI:** `"auto" | "none" | "required" | { type: "function", function: { name: "..." } }`
|
|
669
|
+
- **Google:** `"auto" | "none" | "any"`
|
|
670
|
+
|
|
671
|
+
**Partial JSON streaming:** During streaming, Pi parses tool call arguments incrementally using partial JSON parsing. This means you can see tool arguments being built in real-time, not just after the tool call completes.
|
|
672
|
+
|
|
673
|
+
##### 2. Reasoning / Extended Thinking
|
|
674
|
+
|
|
675
|
+
Pi provides **unified thinking support** across all providers that support it. Thinking blocks are automatically extracted, separated from regular text, and streamed as distinct events:
|
|
676
|
+
|
|
677
|
+
| Provider | Models | Control Parameters | How It Works |
|
|
678
|
+
|---|---|---|---|
|
|
679
|
+
| **Anthropic** | Claude Opus, Sonnet 4+ | `thinkingEnabled: boolean`, `thinkingBudgetTokens: number` | Extended thinking blocks in response, separate `thinking` content type |
|
|
680
|
+
| **OpenAI** | o1, o3, o4, GPT-5 | `reasoningEffort: "minimal" \| "low" \| "medium" \| "high"` | Reasoning via Responses API, `reasoningSummary: "auto" \| "detailed" \| "concise"` |
|
|
681
|
+
| **Google** | Gemini 2.5 Flash/Pro | `thinking.enabled: boolean`, `thinking.budgetTokens: number` | Thinking via `thinkingConfig`, mapped to effort levels |
|
|
682
|
+
| **xAI** | Grok-4, Grok-3-mini | Native reasoning | Automatic when model supports it |
|
|
683
|
+
|
|
684
|
+
**Cross-provider thinking portability:** When switching models mid-conversation, Pi converts thinking blocks between formats. Anthropic thinking blocks become `<thinking>` tagged text when sent to OpenAI/Google, and vice versa.
|
|
502
685
|
|
|
503
|
-
**Vision / Multimodal Input:**
|
|
504
686
|
```typescript
|
|
505
|
-
//
|
|
506
|
-
{
|
|
507
|
-
|
|
508
|
-
content:
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
687
|
+
// Thinking is automatically extracted in Noosphere responses:
|
|
688
|
+
const result = await ai.chat({
|
|
689
|
+
model: 'claude-opus-4-6',
|
|
690
|
+
messages: [{ role: 'user', content: 'Solve this step by step: 15! / 13!' }],
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
console.log(result.thinking); // "Let me work through this... 15! = 15 × 14 × 13!..."
|
|
694
|
+
console.log(result.content); // "15! / 13! = 15 × 14 = 210"
|
|
695
|
+
|
|
696
|
+
// During streaming, thinking arrives as separate events:
|
|
697
|
+
const stream = ai.stream({ messages: [...] });
|
|
698
|
+
for await (const event of stream) {
|
|
699
|
+
if (event.type === 'thinking_delta') console.log('[THINKING]', event.delta);
|
|
700
|
+
if (event.type === 'text_delta') console.log('[RESPONSE]', event.delta);
|
|
512
701
|
}
|
|
513
702
|
```
|
|
514
703
|
|
|
515
|
-
|
|
704
|
+
##### 3. Vision / Multimodal Input
|
|
705
|
+
|
|
706
|
+
Models with `input: ["text", "image"]` accept images alongside text. Pi handles the encoding and format differences per provider:
|
|
707
|
+
|
|
708
|
+
```typescript
|
|
709
|
+
// Send images to vision-capable models
|
|
710
|
+
const messages = [{
|
|
711
|
+
role: 'user',
|
|
712
|
+
content: [
|
|
713
|
+
{ type: 'text', text: 'What is in this image?' },
|
|
714
|
+
{ type: 'image', data: base64PngString, mimeType: 'image/png' },
|
|
715
|
+
],
|
|
716
|
+
}];
|
|
717
|
+
|
|
718
|
+
// Supported MIME types: image/png, image/jpeg, image/gif, image/webp
|
|
719
|
+
// Images are silently ignored when sent to non-vision models
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
**Vision-capable models include:** All Claude models, all GPT-4o/GPT-5 models, Gemini models, Grok-2-vision, Grok-4, and select Groq models.
|
|
723
|
+
|
|
724
|
+
##### 4. Agent Loop — Autonomous Tool Execution
|
|
725
|
+
|
|
726
|
+
The `@mariozechner/pi-agent-core` package provides a complete agent loop that automatically cycles through `prompt → LLM → tool call → result → repeat` until the task is done:
|
|
727
|
+
|
|
516
728
|
```typescript
|
|
517
|
-
// Built-in agentic execution loop with automatic tool calling
|
|
518
729
|
import { agentLoop } from '@mariozechner/pi-ai';
|
|
519
730
|
|
|
520
|
-
const events = agentLoop(
|
|
521
|
-
|
|
522
|
-
|
|
731
|
+
const events = agentLoop(userMessage, agentContext, {
|
|
732
|
+
model: getModel('anthropic', 'claude-opus-4-6'),
|
|
733
|
+
tools: [searchTool, readFileTool, writeFileTool],
|
|
734
|
+
signal: abortController.signal,
|
|
523
735
|
});
|
|
524
736
|
|
|
525
737
|
for await (const event of events) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
738
|
+
switch (event.type) {
|
|
739
|
+
case 'agent_start': // Agent begins
|
|
740
|
+
case 'turn_start': // New LLM turn begins
|
|
741
|
+
case 'message_start': // LLM starts responding
|
|
742
|
+
case 'message_update': // Text/thinking delta received
|
|
743
|
+
case 'tool_execution_start': // About to execute a tool
|
|
744
|
+
case 'tool_execution_end': // Tool finished, result available
|
|
745
|
+
case 'message_end': // LLM finished this message
|
|
746
|
+
case 'turn_end': // Turn complete (may loop if tools were called)
|
|
747
|
+
case 'agent_end': // All done, final messages available
|
|
748
|
+
}
|
|
529
749
|
}
|
|
530
750
|
```
|
|
531
751
|
|
|
532
|
-
**
|
|
752
|
+
**The agent loop state machine:**
|
|
753
|
+
|
|
754
|
+
```
|
|
755
|
+
[User sends prompt]
|
|
756
|
+
│
|
|
757
|
+
▼
|
|
758
|
+
┌─[Build Context]──▶ [Check Queues]──▶ [Stream LLM]◄── streamFn()
|
|
759
|
+
│ │
|
|
760
|
+
│ ┌─────┴──────┐
|
|
761
|
+
│ │ │
|
|
762
|
+
│ text tool_call
|
|
763
|
+
│ │ │
|
|
764
|
+
│ ▼ ▼
|
|
765
|
+
│ [Done] [Execute Tool]
|
|
766
|
+
│ │
|
|
767
|
+
│ tool result
|
|
768
|
+
│ │
|
|
769
|
+
└──────────────────────────────────────────────────┘
|
|
770
|
+
(loops back to Stream LLM)
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
**Key design decisions:**
|
|
774
|
+
- Tools execute **sequentially** by default (parallelism can be added on top)
|
|
775
|
+
- The `streamFn` is **injectable** — you can wrap it with middleware to modify requests per-provider
|
|
776
|
+
- Tool arguments are **validated at runtime** using TypeBox + AJV before execution
|
|
777
|
+
- Aborted/failed responses preserve partial content and usage data
|
|
778
|
+
- Tool results are automatically added to the conversation context
|
|
779
|
+
|
|
780
|
+
##### 5. The `streamFn` Pattern — Injectable Middleware
|
|
781
|
+
|
|
782
|
+
This is Pi's most powerful architectural feature. The `streamFn` is the function that actually talks to the LLM, and it can be **wrapped with middleware** like Express.js request handlers:
|
|
783
|
+
|
|
784
|
+
```typescript
|
|
785
|
+
import type { StreamFn } from '@mariozechner/pi-agent-core';
|
|
786
|
+
import { streamSimple } from '@mariozechner/pi-ai';
|
|
787
|
+
|
|
788
|
+
// Start with Pi's base streaming function
|
|
789
|
+
let fn: StreamFn = streamSimple;
|
|
790
|
+
|
|
791
|
+
// Wrap it with middleware that modifies requests per-provider
|
|
792
|
+
fn = createMyCustomWrapper(fn, {
|
|
793
|
+
// Add custom headers for Anthropic
|
|
794
|
+
onPayload: (payload) => {
|
|
795
|
+
if (model.provider === 'anthropic') {
|
|
796
|
+
payload.headers['anthropic-beta'] = 'fine-grained-tool-streaming-2025-05-14';
|
|
797
|
+
}
|
|
798
|
+
},
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
// Each wrapper calls the previous one, forming a chain:
|
|
802
|
+
// request → wrapper3 → wrapper2 → wrapper1 → streamSimple → API
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
This pattern is what allows projects like OpenClaw to stack **16 provider-specific wrappers** on top of Pi's base streaming — adding beta headers for Anthropic, WebSocket transport for OpenAI, thinking sanitization for Google, reasoning effort headers for OpenRouter, and more — without modifying Pi's source code.
|
|
806
|
+
|
|
807
|
+
##### 6. Session Management (via pi-coding-agent)
|
|
808
|
+
|
|
809
|
+
The `@mariozechner/pi-coding-agent` package provides persistent session management with JSONL-based storage:
|
|
810
|
+
|
|
533
811
|
```typescript
|
|
534
|
-
|
|
812
|
+
import { createAgentSession, SessionManager } from '@mariozechner/pi-coding-agent';
|
|
813
|
+
|
|
814
|
+
// Create a session with full persistence
|
|
815
|
+
const session = await createAgentSession({
|
|
816
|
+
model: 'claude-opus-4-6',
|
|
817
|
+
tools: myTools,
|
|
818
|
+
sessionManager, // handles JSONL persistence
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
const result = await session.run('Build a REST API');
|
|
822
|
+
// Session is automatically saved to:
|
|
823
|
+
// ~/.pi/agent/sessions/session_abc123.jsonl
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
**Session file format (append-only JSONL):**
|
|
827
|
+
```jsonl
|
|
828
|
+
{"role":"user","content":"Build a REST API","timestamp":1710000000}
|
|
829
|
+
{"role":"assistant","content":"I'll create...","model":"claude-opus-4-6","usage":{...}}
|
|
830
|
+
{"role":"toolResult","toolCallId":"tc_001","toolName":"bash","content":"OK"}
|
|
831
|
+
{"type":"compaction","summary":"The user asked to build...","preservedMessages":[...]}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
**Session operations:**
|
|
835
|
+
- `create()` — new session
|
|
836
|
+
- `open(id)` — restore existing session
|
|
837
|
+
- `continueRecent()` — continue the most recent session
|
|
838
|
+
- `forkFrom(id)` — create a branch (new JSONL referencing parent)
|
|
839
|
+
- `inMemory()` — RAM-only session (for SDK/testing)
|
|
840
|
+
|
|
841
|
+
##### 7. Context Compaction — Automatic Context Window Management
|
|
842
|
+
|
|
843
|
+
When the conversation approaches the model's context window limit, Pi automatically **compacts** the history:
|
|
844
|
+
|
|
845
|
+
```
|
|
846
|
+
1. DETECT: Calculate inputTokens + outputTokens vs model.contextWindow
|
|
847
|
+
2. TRIGGER: Proactively before overflow, or as recovery after overflow error
|
|
848
|
+
3. SUMMARIZE: Send history to LLM with a compaction prompt
|
|
849
|
+
4. WRITE: Append compaction entry to JSONL:
|
|
850
|
+
{"type":"compaction","summary":"...","preservedMessages":[last N messages]}
|
|
851
|
+
5. CONTINUE: Context is now summary + recent messages instead of full history
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
The JSONL file is **never rewritten** — compaction entries are appended, maintaining a complete audit trail.
|
|
855
|
+
|
|
856
|
+
##### 8. Cost Tracking — Cache-Aware Pricing
|
|
857
|
+
|
|
858
|
+
Pi tracks costs per-request with cache-aware pricing for providers that support prompt caching:
|
|
859
|
+
|
|
860
|
+
```typescript
|
|
861
|
+
// Every model has 4 cost dimensions:
|
|
535
862
|
{
|
|
536
|
-
input:
|
|
537
|
-
output:
|
|
538
|
-
cacheRead:
|
|
539
|
-
cacheWrite:
|
|
863
|
+
input: 15, // $15 per 1M input tokens
|
|
864
|
+
output: 75, // $75 per 1M output tokens
|
|
865
|
+
cacheRead: 1.5, // $1.50 per 1M cached prompt tokens (read)
|
|
866
|
+
cacheWrite: 18.75, // $18.75 per 1M cached prompt tokens (write)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Usage tracking on every response:
|
|
870
|
+
{
|
|
871
|
+
input: 1500, // tokens consumed as input
|
|
872
|
+
output: 800, // tokens generated
|
|
873
|
+
cacheRead: 5000, // prompt cache hits
|
|
874
|
+
cacheWrite: 1500, // prompt cache writes
|
|
875
|
+
cost: {
|
|
876
|
+
total: 0.082, // total cost in USD
|
|
877
|
+
input: 0.0225,
|
|
878
|
+
output: 0.06,
|
|
879
|
+
cacheRead: 0.0075,
|
|
880
|
+
cacheWrite: 0.028,
|
|
881
|
+
},
|
|
540
882
|
}
|
|
541
883
|
```
|
|
542
884
|
|
|
885
|
+
**Anthropic and OpenAI** support prompt caching. For providers without caching, `cacheRead` and `cacheWrite` are always 0.
|
|
886
|
+
|
|
887
|
+
##### 9. Extension System (via pi-coding-agent)
|
|
888
|
+
|
|
889
|
+
Pi supports a plugin system where extensions can register tools, commands, and lifecycle hooks:
|
|
890
|
+
|
|
891
|
+
```typescript
|
|
892
|
+
// Extensions are TypeScript modules loaded at runtime via jiti
|
|
893
|
+
export default function(api: ExtensionAPI) {
|
|
894
|
+
// Register a custom tool
|
|
895
|
+
api.registerTool('my_tool', {
|
|
896
|
+
description: 'Does something useful',
|
|
897
|
+
parameters: { /* TypeBox schema */ },
|
|
898
|
+
execute: async (args) => 'result',
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
// Register a slash command
|
|
902
|
+
api.registerCommand('/mycommand', {
|
|
903
|
+
handler: async (args) => { /* ... */ },
|
|
904
|
+
description: 'Custom command',
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
// Hook into the agent lifecycle
|
|
908
|
+
api.on('before_agent_start', async (context) => {
|
|
909
|
+
context.systemPrompt += '\nExtra instructions';
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
api.on('tool_execution_end', async (event) => {
|
|
913
|
+
// Post-process tool results
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
**Resource discovery chain (priority):**
|
|
919
|
+
1. Project `.pi/` directory (highest)
|
|
920
|
+
2. User `~/.pi/agent/`
|
|
921
|
+
3. npm packages with Pi metadata
|
|
922
|
+
4. Built-in defaults
|
|
923
|
+
|
|
924
|
+
##### 10. The Anti-MCP Philosophy — Why Pi Uses CLI Instead
|
|
925
|
+
|
|
926
|
+
Pi explicitly **rejects MCP** (Model Context Protocol). Mario Zechner's argument, backed by benchmarks:
|
|
927
|
+
|
|
928
|
+
**The token cost problem:**
|
|
929
|
+
|
|
930
|
+
| Approach | Tools | Tokens Consumed | % of Claude's Context |
|
|
931
|
+
|---|---|---|---|
|
|
932
|
+
| Playwright MCP | 21 tools | 13,700 tokens | 6.8% |
|
|
933
|
+
| Chrome DevTools MCP | 26 tools | 18,000 tokens | 9.0% |
|
|
934
|
+
| Pi CLI + README | N/A | 225 tokens | ~0.1% |
|
|
935
|
+
|
|
936
|
+
That's a **60-80x reduction** in token consumption. With 5 MCP servers, you lose ~55,000 tokens before doing any work.
|
|
937
|
+
|
|
938
|
+
**Benchmark results (120 evaluations):**
|
|
939
|
+
|
|
940
|
+
| Approach | Avg Cost | Success Rate |
|
|
941
|
+
|---|---|---|
|
|
942
|
+
| CLI (tmux) | $0.37 | 100% |
|
|
943
|
+
| CLI (terminalcp) | $0.39 | 100% |
|
|
944
|
+
| MCP (terminalcp) | $0.48 | 100% |
|
|
945
|
+
|
|
946
|
+
Same success rate, MCP costs **30% more**.
|
|
947
|
+
|
|
948
|
+
**Pi's alternative: Progressive Disclosure via CLI tools + READMEs**
|
|
949
|
+
|
|
950
|
+
Instead of loading all tool definitions upfront, Pi's agent has `bash` as a built-in tool and discovers CLI tools only when needed:
|
|
951
|
+
|
|
952
|
+
```
|
|
953
|
+
MCP approach: Pi approach:
|
|
954
|
+
───────────── ──────────
|
|
955
|
+
Session start → Session start →
|
|
956
|
+
Load 21 Playwright tools Load 4 tools: read, write, edit, bash
|
|
957
|
+
Load 26 Chrome DevTools tools (225 tokens)
|
|
958
|
+
Load N more MCP tools
|
|
959
|
+
(~55,000 tokens wasted)
|
|
960
|
+
|
|
961
|
+
When browser needed: When browser needed:
|
|
962
|
+
Tools already loaded Agent reads SKILL.md (225 tokens)
|
|
963
|
+
(but context is polluted) Runs: browser-start.js
|
|
964
|
+
Runs: browser-nav.js https://...
|
|
965
|
+
Runs: browser-screenshot.js
|
|
966
|
+
|
|
967
|
+
When browser NOT needed: When browser NOT needed:
|
|
968
|
+
Tools still consume context 0 tokens wasted
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
**The 4 built-in tools** (what Pi argues is sufficient):
|
|
972
|
+
|
|
973
|
+
| Tool | What It Does | Why It's Enough |
|
|
974
|
+
|---|---|---|
|
|
975
|
+
| `read` | Read files (text + images) | Supports offset/limit for large files |
|
|
976
|
+
| `write` | Create/overwrite files | Creates directories automatically |
|
|
977
|
+
| `edit` | Replace text (oldText→newText) | Surgical edits, like a diff |
|
|
978
|
+
| `bash` | Execute any shell command | **bash can do everything else** — replaces MCP entirely |
|
|
979
|
+
|
|
980
|
+
The key insight: `bash` replaces MCP. Any CLI tool, API call, database query, or system operation can be invoked through bash. The agent reads the tool's README only when it needs it, paying tokens on-demand instead of upfront.
|
|
981
|
+
|
|
543
982
|
---
|
|
544
983
|
|
|
545
984
|
### FAL — Media Generation (867+ endpoints)
|