@computesdk/pi-morph 1.0.2 → 1.0.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 +1 -1
- package/index.ts +25 -12
- package/package.json +3 -1
- package/tools/api.ts +22 -3
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
pi install npm:@computesdk/pi-morph # or: pi -e /path/to/pi-extensions/packages/pi-morph
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Run `/login morph` and paste your key, or set `MORPH_API_KEY` in your environment; one credential covers both halves (the models and the tools), and the environment wins when both are set.
|
|
10
10
|
|
|
11
11
|
## Models
|
|
12
12
|
|
package/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Morph — open-weight coding models on an OpenAI-compatible API, plus specialized apply/grep/compact models.
|
|
2
|
-
// Base URL: https://api.morphllm.com/v1 · Auth:
|
|
2
|
+
// Base URL: https://api.morphllm.com/v1 · Auth: `/login morph` (stored in auth.json) or MORPH_API_KEY in the environment.
|
|
3
3
|
// Docs: https://docs.morphllm.com · Models: https://docs.morphllm.com/sdk/components/fast-models
|
|
4
4
|
//
|
|
5
5
|
// This is a Pi provider extension: `pi.registerProvider` adds the endpoint and its model catalog, and Pi's
|
|
@@ -14,11 +14,15 @@
|
|
|
14
14
|
// Prices are USD per 1M tokens from https://www.morphllm.com/pricing (live at /api/models/json). Update them
|
|
15
15
|
// there when they change.
|
|
16
16
|
|
|
17
|
-
import type { ExtensionAPI
|
|
17
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import { createProvider, envApiKeyAuth, type Model, type ThinkingLevelMap } from "@earendil-works/pi-ai";
|
|
19
|
+
import { openAICompletionsApi } from "@earendil-works/pi-ai/api/openai-completions.lazy";
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
const ID = "morph";
|
|
22
|
+
const BASE_URL = "https://api.morphllm.com/v1";
|
|
23
|
+
|
|
24
|
+
/** Pi's chat-model shape for the one API Morph speaks. */
|
|
25
|
+
type ChatModel = Model<"openai-completions">;
|
|
22
26
|
// Pi thinking levels (off/minimal/low/medium/high/xhigh/max) → the provider's `reasoning_effort` value.
|
|
23
27
|
// Morph documents the ladder for DeepSeek: none | low | high | xhigh | max (`medium`→high, `minimal`→low).
|
|
24
28
|
// For the other models `off` is not documented, so it is marked unsupported (`null`) rather than sent.
|
|
@@ -35,6 +39,9 @@ const model = (id: string, name: string, { input = ["text"], cost, effort = EFFO
|
|
|
35
39
|
type: "chat",
|
|
36
40
|
id,
|
|
37
41
|
name,
|
|
42
|
+
provider: ID,
|
|
43
|
+
api: "openai-completions",
|
|
44
|
+
baseUrl: BASE_URL,
|
|
38
45
|
reasoning: true,
|
|
39
46
|
thinkingLevelMap: effort,
|
|
40
47
|
input,
|
|
@@ -54,11 +61,17 @@ export const MODELS: ChatModel[] = [
|
|
|
54
61
|
];
|
|
55
62
|
|
|
56
63
|
export default function morphProvider(pi: ExtensionAPI) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
// A complete provider rather than the name+config form, because that is what carries native
|
|
65
|
+
// auth: envApiKeyAuth gives `/login morph` a key prompt, stores the credential in auth.json,
|
|
66
|
+
// and falls back to MORPH_API_KEY in the environment when nothing is stored.
|
|
67
|
+
pi.registerProvider(
|
|
68
|
+
createProvider({
|
|
69
|
+
id: ID,
|
|
70
|
+
name: "Morph",
|
|
71
|
+
baseUrl: BASE_URL,
|
|
72
|
+
auth: { apiKey: envApiKeyAuth("Morph API key", ["MORPH_API_KEY"]) },
|
|
73
|
+
models: MODELS,
|
|
74
|
+
api: openAICompletionsApi(),
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
64
77
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@computesdk/pi-morph",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Morph for Pi: open-weight coding models (Kimi K3, GLM 5.3, DeepSeek V4) on Morph's OpenAI-compatible API, plus Fast Apply, Compact, and WarpGrep as tools.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -47,10 +47,12 @@
|
|
|
47
47
|
"test": "tsc --noEmit && node test.ts"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
+
"@earendil-works/pi-ai": "*",
|
|
50
51
|
"@earendil-works/pi-coding-agent": "*",
|
|
51
52
|
"typebox": "*"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
55
|
+
"@earendil-works/pi-ai": "catalog:",
|
|
54
56
|
"@earendil-works/pi-coding-agent": "catalog:",
|
|
55
57
|
"@types/node": "catalog:",
|
|
56
58
|
"typebox": "catalog:",
|
package/tools/api.ts
CHANGED
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
* Fast Apply, Compact, and WarpGrep have bespoke request shapes, so the tools call them directly.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
|
|
8
12
|
export const BASE_URL = "https://api.morphllm.com/v1";
|
|
9
13
|
|
|
10
14
|
/** Morph reports OpenAI-style token counts. */
|
|
@@ -41,13 +45,28 @@ export function usageFrom(usage: MorphUsage | undefined): Usage {
|
|
|
41
45
|
};
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* One credential covers models and tools: `MORPH_API_KEY` in the environment, or the key
|
|
50
|
+
* `/login morph` stored in the agent directory's auth.json (the provider resolves the same two,
|
|
51
|
+
* stored first; here the env var wins so the shell can override a stored key).
|
|
52
|
+
*/
|
|
45
53
|
export function apiKey(): string {
|
|
46
|
-
const key = process.env.MORPH_API_KEY?.trim();
|
|
47
|
-
if (!key) throw new Error("MORPH_API_KEY is not set
|
|
54
|
+
const key = process.env.MORPH_API_KEY?.trim() || storedKey();
|
|
55
|
+
if (!key) throw new Error("MORPH_API_KEY is not set and no Morph credential is stored; set it or run /login morph");
|
|
48
56
|
return key;
|
|
49
57
|
}
|
|
50
58
|
|
|
59
|
+
/** Read the key `/login morph` wrote. Only literal keys: a "!command" credential is Pi-side resolution. */
|
|
60
|
+
function storedKey(): string | undefined {
|
|
61
|
+
try {
|
|
62
|
+
const dir = process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
|
|
63
|
+
const key = JSON.parse(fs.readFileSync(path.join(dir, "auth.json"), "utf8"))?.morph?.key;
|
|
64
|
+
return typeof key === "string" && !key.startsWith("!") ? key : undefined;
|
|
65
|
+
} catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
/** POST JSON to Morph. A non-2xx response becomes a tool error. */
|
|
52
71
|
export async function post<T>(path: string, body: unknown, signal?: AbortSignal): Promise<T> {
|
|
53
72
|
const res = await fetch(`${BASE_URL}${path}`, {
|