@cydm/pie 0.1.0 → 1.0.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
@@ -15,7 +15,9 @@ npm install -g @cydm/pie
15
15
  设置 API Key:
16
16
 
17
17
  ```bash
18
- export KIMI_API_KEY="your-api-key"
18
+ export KIMI_API_KEY="your-kimi-api-key"
19
+ # 或
20
+ export BIGMODEL_API_KEY="your-bigmodel-api-key"
19
21
  ```
20
22
 
21
23
  或创建配置文件 `~/.pie/config.json`:
@@ -23,11 +25,12 @@ export KIMI_API_KEY="your-api-key"
23
25
  ```json
24
26
  {
25
27
  "provider": "kimi-cn",
26
- "modelId": "kimi-k2.5",
27
- "apiKey": "your-api-key"
28
+ "modelId": "kimi-k2.5"
28
29
  }
29
30
  ```
30
31
 
32
+ 也可以在 `~/.pie/models.json` 的 `providers.<provider>.apiKey` 中为特定 provider 配置 API Key。
33
+
31
34
  ### 命令
32
35
 
33
36
  #### Print 模式(默认)
@@ -203,11 +203,11 @@ var TypeBoxError = class extends Error {
203
203
  };
204
204
 
205
205
  // ../../node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
206
- var TransformKind = /* @__PURE__ */ Symbol.for("TypeBox.Transform");
207
- var ReadonlyKind = /* @__PURE__ */ Symbol.for("TypeBox.Readonly");
208
- var OptionalKind = /* @__PURE__ */ Symbol.for("TypeBox.Optional");
209
- var Hint = /* @__PURE__ */ Symbol.for("TypeBox.Hint");
210
- var Kind = /* @__PURE__ */ Symbol.for("TypeBox.Kind");
206
+ var TransformKind = Symbol.for("TypeBox.Transform");
207
+ var ReadonlyKind = Symbol.for("TypeBox.Readonly");
208
+ var OptionalKind = Symbol.for("TypeBox.Optional");
209
+ var Hint = Symbol.for("TypeBox.Hint");
210
+ var Kind = Symbol.for("TypeBox.Kind");
211
211
 
212
212
  // ../../node_modules/@sinclair/typebox/build/esm/type/guard/kind.mjs
213
213
  function IsReadonly(value) {
@@ -0,0 +1,144 @@
1
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+
3
+ // builtin/extensions/document-attachments/index.ts
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { spawnSync } from "node:child_process";
7
+ var MAX_TEXT_CHARS = 24e3;
8
+ var PYTHON_CANDIDATES = [
9
+ process.env.PYTHON,
10
+ "/opt/anaconda3/bin/python3",
11
+ "python3"
12
+ ].filter((value) => Boolean(value));
13
+ function isTextCapable(model) {
14
+ return (model.input ?? []).includes("text");
15
+ }
16
+ function isSupportedDocument(attachment) {
17
+ return attachment.mimeType === "application/pdf" || attachment.mimeType === "text/html" || attachment.mimeType === "text/plain" || attachment.mimeType === "text/markdown" || attachment.mimeType === "application/json" || attachment.name.endsWith(".html") || attachment.name.endsWith(".htm") || attachment.name.endsWith(".md") || attachment.name.endsWith(".txt") || attachment.name.endsWith(".json") || attachment.name.endsWith(".pdf");
18
+ }
19
+ function truncateText(text) {
20
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
21
+ if (normalized.length <= MAX_TEXT_CHARS) return normalized;
22
+ return `${normalized.slice(0, MAX_TEXT_CHARS)}
23
+
24
+ [Truncated after ${MAX_TEXT_CHARS} characters]`;
25
+ }
26
+ function runPythonScript(script, fallbackMessage) {
27
+ const failures = [];
28
+ for (const pythonBin of PYTHON_CANDIDATES) {
29
+ const result = spawnSync(pythonBin, ["-c", script], { encoding: "utf8" });
30
+ if (result.status === 0) {
31
+ return result.stdout;
32
+ }
33
+ const detail = (result.stderr || result.stdout || `exit ${result.status}`).trim();
34
+ failures.push(`${pythonBin}: ${detail}`);
35
+ }
36
+ throw new Error(`${fallbackMessage}
37
+ ${failures.join("\n")}`);
38
+ }
39
+ function extractHtmlText(filePath) {
40
+ const script = `
41
+ from pathlib import Path
42
+ from html.parser import HTMLParser
43
+
44
+ class Extractor(HTMLParser):
45
+ def __init__(self):
46
+ super().__init__()
47
+ self.parts = []
48
+ self.skip_depth = 0
49
+ def handle_starttag(self, tag, attrs):
50
+ if tag in ("script", "style"):
51
+ self.skip_depth += 1
52
+ def handle_endtag(self, tag):
53
+ if tag in ("script", "style") and self.skip_depth > 0:
54
+ self.skip_depth -= 1
55
+ def handle_data(self, data):
56
+ if self.skip_depth:
57
+ return
58
+ data = data.strip()
59
+ if data:
60
+ self.parts.append(data)
61
+
62
+ p = Path(${JSON.stringify(filePath)})
63
+ parser = Extractor()
64
+ parser.feed(p.read_text("utf-8"))
65
+ print("\\n".join(parser.parts))
66
+ `;
67
+ return runPythonScript(script, "Failed to extract HTML text");
68
+ }
69
+ function extractPdfText(filePath) {
70
+ const script = `
71
+ reader = None
72
+ errors = []
73
+
74
+ for module_name in ("pypdf", "PyPDF2"):
75
+ try:
76
+ module = __import__(module_name, fromlist=["PdfReader"])
77
+ reader = module.PdfReader(${JSON.stringify(filePath)})
78
+ break
79
+ except Exception as exc:
80
+ errors.append(f"{module_name}: {exc}")
81
+
82
+ if reader is None:
83
+ raise RuntimeError(" | ".join(errors) or "No supported PDF reader module found")
84
+
85
+ parts = []
86
+ for page in reader.pages:
87
+ try:
88
+ text = page.extract_text() or ""
89
+ except Exception:
90
+ text = ""
91
+ if text.strip():
92
+ parts.append(text)
93
+ print("\\n\\n".join(parts))
94
+ `;
95
+ return runPythonScript(script, "Failed to extract PDF text");
96
+ }
97
+ function extractPlainText(filePath) {
98
+ return fs.readFileSync(filePath, "utf8");
99
+ }
100
+ function extractDocumentText(attachment) {
101
+ const filePath = attachment.path;
102
+ if (!filePath || !fs.existsSync(filePath)) {
103
+ throw new Error(`Local attachment path is unavailable for ${attachment.name}`);
104
+ }
105
+ const ext = path.extname(filePath).toLowerCase();
106
+ if (attachment.mimeType === "application/pdf" || ext === ".pdf") {
107
+ return extractPdfText(filePath);
108
+ }
109
+ if (attachment.mimeType === "text/html" || ext === ".html" || ext === ".htm") {
110
+ return extractHtmlText(filePath);
111
+ }
112
+ return extractPlainText(filePath);
113
+ }
114
+ function documentAttachmentsExtension(ctx) {
115
+ ctx.log("Document attachment extension loaded");
116
+ ctx.registerAttachmentHandler({
117
+ name: "local-document-preprocess",
118
+ priority: 20,
119
+ matches(model, attachment) {
120
+ return isTextCapable(model) && isSupportedDocument(attachment) && Boolean(attachment.path);
121
+ },
122
+ async prepare(_model, attachment, prepareCtx) {
123
+ const extracted = extractDocumentText(attachment);
124
+ const text = truncateText(extracted);
125
+ prepareCtx.log(`Document handler extracted ${text.length} chars from ${attachment.name}`);
126
+ const prefix = [
127
+ `[Document: ${attachment.name}]`,
128
+ `MIME: ${attachment.mimeType}`,
129
+ ""
130
+ ].join("\n");
131
+ return {
132
+ items: [
133
+ {
134
+ type: "text",
135
+ text: `${prefix}${text}`
136
+ }
137
+ ]
138
+ };
139
+ }
140
+ });
141
+ }
142
+ export {
143
+ documentAttachmentsExtension as default
144
+ };
@@ -0,0 +1,46 @@
1
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+
3
+ // builtin/extensions/kimi-attachments/index.ts
4
+ function isKimiProvider(provider) {
5
+ return provider === "kimi-cn" || provider === "kimi-api" || provider === "kimi";
6
+ }
7
+ function getModality(mimeType) {
8
+ if (mimeType.startsWith("image/")) return "image";
9
+ if (mimeType.startsWith("video/")) return "video";
10
+ return "file";
11
+ }
12
+ function kimiAttachmentsExtension(ctx) {
13
+ ctx.log("Kimi attachment extension loaded");
14
+ ctx.registerAttachmentHandler({
15
+ name: "kimi-native-attachments",
16
+ priority: 100,
17
+ matches(model, attachment) {
18
+ return isKimiProvider(model.provider) && Boolean(attachment.fileId) && getModality(attachment.mimeType) !== "file";
19
+ },
20
+ async prepare(_model, attachment, prepareCtx) {
21
+ const fileId = attachment.fileId;
22
+ if (!fileId) {
23
+ return {
24
+ items: [{ type: "text", text: `[${attachment.id}]` }],
25
+ warnings: ["\u9644\u4EF6\u7F3A\u5C11 fileId\uFF0C\u65E0\u6CD5\u8D70 Kimi \u539F\u751F\u5F15\u7528"]
26
+ };
27
+ }
28
+ const modality = getModality(attachment.mimeType);
29
+ const url = `ms://${fileId}`;
30
+ prepareCtx.log(`Kimi handler prepared ${modality} reference for ${attachment.id}`);
31
+ return {
32
+ items: [
33
+ {
34
+ type: "file-ref",
35
+ mimeType: attachment.mimeType,
36
+ url,
37
+ modality
38
+ }
39
+ ]
40
+ };
41
+ }
42
+ });
43
+ }
44
+ export {
45
+ kimiAttachmentsExtension as default
46
+ };
@@ -203,11 +203,11 @@ var TypeBoxError = class extends Error {
203
203
  };
204
204
 
205
205
  // ../../node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
206
- var TransformKind = /* @__PURE__ */ Symbol.for("TypeBox.Transform");
207
- var ReadonlyKind = /* @__PURE__ */ Symbol.for("TypeBox.Readonly");
208
- var OptionalKind = /* @__PURE__ */ Symbol.for("TypeBox.Optional");
209
- var Hint = /* @__PURE__ */ Symbol.for("TypeBox.Hint");
210
- var Kind = /* @__PURE__ */ Symbol.for("TypeBox.Kind");
206
+ var TransformKind = Symbol.for("TypeBox.Transform");
207
+ var ReadonlyKind = Symbol.for("TypeBox.Readonly");
208
+ var OptionalKind = Symbol.for("TypeBox.Optional");
209
+ var Hint = Symbol.for("TypeBox.Hint");
210
+ var Kind = Symbol.for("TypeBox.Kind");
211
211
 
212
212
  // ../../node_modules/@sinclair/typebox/build/esm/type/guard/kind.mjs
213
213
  function IsReadonly(value) {