@jungtz/chat-kit 0.1.0 → 0.2.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 +15 -0
- package/dist/cli/claude.d.ts +25 -0
- package/dist/cli/create-cli-model.d.ts +14 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/isolated-cwd.d.ts +10 -0
- package/dist/cli/opencode.d.ts +27 -0
- package/dist/cli/prompt.d.ts +15 -0
- package/dist/cli/spawn.d.ts +28 -0
- package/dist/cli/types.d.ts +29 -0
- package/dist/cli/util.d.ts +10 -0
- package/docs/API.md +16 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
| `engine/` | `runTurn`(串流事件)/`completeTurn`(完整結果)、工具迴圈 | ✅ P2 |
|
|
9
9
|
| `prompt/` | 片段+上下文區塊組裝、固定順序、信任標記、佔位符檢查 | ✅ P2 |
|
|
10
10
|
| `guards/` | 輸入標記中和、輸出標記清洗、模型自我介紹過濾、自訂閘門介面 | ✅ P2 |
|
|
11
|
+
| `cli/` | 把本機 `claude`/`opencode` 包成 `LanguageModelV3`(子路徑 `@jungtz/chat-kit/cli`,不從根入口匯出) | ✅ |
|
|
11
12
|
| `knowledge/` | wiki-router 整合 | ⛔ 不建(P5 決定,見 PLAN §6.1) |
|
|
12
13
|
|
|
13
14
|
完整計畫見 `docs/CHAT_KIT_PLAN.md`,待執行事項見 `docs/NEXT_STEPS.md`,從發問到輸出的完整流程與接入範例見 `docs/FLOW.md`,介面簽名速查見 `docs/API.md`(隨 npm 包出貨)。
|
|
@@ -46,6 +47,20 @@ const result = streamText({
|
|
|
46
47
|
})
|
|
47
48
|
```
|
|
48
49
|
|
|
50
|
+
### 本地 CLI 當模型
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
import { createCliModel } from '@jungtz/chat-kit/cli'
|
|
54
|
+
|
|
55
|
+
const model = createCliModel({ command: 'claude', model: 'sonnet', effort: 'high' })
|
|
56
|
+
// 之後和任何 LanguageModel 一樣傳給引擎或 generateText/streamText
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- 純文字補全:`claude` 以 `--tools ""` 關掉內建工具與 MCP,引擎的 `tools` 須為空(否則拋錯)。
|
|
60
|
+
- `cwd` 省略時在系統暫存下的空目錄執行,不繼承目前行程的工作目錄,避免 CLI 讀到該處的 CLAUDE.md/AGENTS.md;要讓 CLI 看到某個專案才明確傳 `cwd`。
|
|
61
|
+
- `model`、`effort` 只接受 `[A-Za-z0-9._:/#@+=-]`(Windows 會經 shell 啟動)。
|
|
62
|
+
- 只能從 `@jungtz/chat-kit/cli` 匯入:根入口不含它,只用引擎的使用端不會載入 `node:child_process`。
|
|
63
|
+
|
|
49
64
|
不用 `config.json` 的專案可以完全不碰 `models/`:引擎只認 AI SDK 的 `LanguageModel`,
|
|
50
65
|
直接傳 `google('gemini-…')` 之類的 model 即可。
|
|
51
66
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-code 非互動呼叫:`claude -p --model <m> --output-format json [--effort X]`,
|
|
3
|
+
* prompt 經 stdin 餵入。stdout 是單一 JSON 物件。
|
|
4
|
+
*
|
|
5
|
+
* 引擎把它當純文字補全模型,所以要關掉 CLI 自帶的 agent 能力:終端使用者的聊天文字
|
|
6
|
+
* 會進到 prompt,若放著內建工具,prompt injection 就能讀伺服器上的檔案。
|
|
7
|
+
* - `--tools ""`:停用全部內建工具(Read、Bash、Edit…)
|
|
8
|
+
* - `--strict-mcp-config`:不載入使用者設定的 MCP 伺服器(沒給 `--mcp-config`,等於全不載入)
|
|
9
|
+
* - `--disable-slash-commands`:停用 skills
|
|
10
|
+
* - `--no-session-persistence`:聊天內容不寫進本機 session 歷史
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildClaudeArgs(model: string, effort?: string): string[];
|
|
13
|
+
export interface CliUsage {
|
|
14
|
+
/** 未命中快取的輸入 token */
|
|
15
|
+
inputTokens: number;
|
|
16
|
+
outputTokens: number;
|
|
17
|
+
/** 讀自快取的輸入 token;CLI 沒給時為 undefined */
|
|
18
|
+
cacheReadTokens?: number;
|
|
19
|
+
/** 寫入快取的輸入 token;CLI 沒給時為 undefined */
|
|
20
|
+
cacheWriteTokens?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function parseClaudeOutput(stdout: Buffer, stderr: string, code: number): {
|
|
23
|
+
text: string;
|
|
24
|
+
usage: CliUsage;
|
|
25
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地 CLI 模型:把 `claude`/`opencode` 包成 AI SDK `LanguageModelV3`。
|
|
3
|
+
*
|
|
4
|
+
* 引擎看到的只是普通 model,用法與 API 模型無異:
|
|
5
|
+
* `engine.completeTurn({ model: createCliModel({ command: 'claude', model: 'sonnet' }), ... })`
|
|
6
|
+
*
|
|
7
|
+
* 兩條硬限制(失敗時直接拋錯,不靜默降級):
|
|
8
|
+
* 1. 不支援引擎工具迴圈——CLI 自帶工具執行環境,與引擎的 `tools` 迴圈衝突,
|
|
9
|
+
* `Domain.tools` 須為空。
|
|
10
|
+
* 2. temperature 等微調參數 CLI 不認得,只記一條 warning,不假裝生效。
|
|
11
|
+
*/
|
|
12
|
+
import type { LanguageModelV3 } from '@ai-sdk/provider';
|
|
13
|
+
import type { CliModelOptions } from './types.js';
|
|
14
|
+
export declare function createCliModel(options: CliModelOptions): LanguageModelV3;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { CliCommand, CliModelOptions } from './types.js';
|
|
2
|
+
export { ALLOWED_CLI_COMMANDS, DEFAULT_CLI_TIMEOUT_MS } from './types.js';
|
|
3
|
+
export { createCliModel } from './create-cli-model.js';
|
|
4
|
+
export type { SpawnProcessFn, SpawnProcessOptions, SpawnProcessResult } from './spawn.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as t}from"node:child_process";import{mkdir as e}from"node:fs/promises";import{tmpdir as n}from"node:os";import{join as o}from"node:path";const r=new Set(["claude","opencode"]),i=12e4,s=/^[A-Za-z0-9._:/#@+=-]+$/;function u(t){return s.test(t)}function c(t,e=500){const n=t.trim();return n?n.length>e?`${n.slice(0,e)} …(已截斷)`:n:"(空輸出)"}function a(t,e,n){const o=n.trim();return new Error(`[chat-kit/cli] ${t} 執行失敗(exit ${e})${o?`:${c(o)}`:""}`)}function p(t){return new Error(`[chat-kit/cli] ${t} 未回傳任何文字內容`)}function f(e){try{if("win32"===process.platform&&void 0!==e.pid){t("taskkill",["/F","/T","/PID",String(e.pid)],{stdio:"ignore"}).on("error",()=>{})}else e.kill("SIGKILL")}catch{}}const l=(e,n,o)=>{const r=o.timeoutMs??12e4,i="win32"===process.platform,s=t(e,i?function(t){return t.map(t=>{if(""===t)return'""';if(!u(t))throw new Error(`[chat-kit/cli] 引數含不允許的字元:${JSON.stringify(t)}`);return t})}(n):n,{cwd:o.cwd,shell:i,stdio:["pipe","pipe","pipe"]});s.stdin.on("error",()=>{}),null!=o.input&&s.stdin.write(o.input,"utf8"),s.stdin.end();const c=[];s.stderr.on("data",t=>{c.push(Buffer.isBuffer(t)?t:Buffer.from(t))});let a=!1;const p=setTimeout(()=>{a=!0,f(s)},r);p.unref?.();const l=()=>{f(s)};o.signal?.aborted?f(s):o.signal?.addEventListener("abort",l,{once:!0});const d=new Promise((t,n)=>{s.on("error",t=>{clearTimeout(p),o.signal?.removeEventListener("abort",l),n(new Error(`[chat-kit/cli] 無法啟動 ${e}:${t.message||t}`))}),s.on("close",e=>{clearTimeout(p),o.signal?.removeEventListener("abort",l),t({code:e??1,stderr:Buffer.concat(c).toString("utf8"),timedOut:a})})});return d.catch(()=>{}),{stdout:async function*(){try{for await(const t of s.stdout)yield Buffer.isBuffer(t)?t:Buffer.from(t)}catch{return}}(),kill:()=>f(s),wait:()=>d}};function d(t){if("object"!=typeof t||null===t)return"";const e=t;if("text"===e.type&&"string"==typeof e.text)return e.text;if("reasoning"===e.type)return"";if("file"===e.type)return`[附件:${"string"==typeof e.mediaType?e.mediaType:"未知類型"},本地 CLI 無法讀取]`;try{return JSON.stringify(t)}catch{return"[unserializable part]"}}function h(t){const e=[];for(const n of t){if("system"===n.role){n.content&&e.push(`System:\n${n.content}`);continue}const t=n.content,o=Array.isArray(t)?t.map(d).join(""):String(t??"");if(!o)continue;const r="user"===n.role?"User":"assistant"===n.role?"Assistant":"Tool";e.push(`${r}:\n${o}`)}return e.join("\n\n")}function m(t){try{const e=JSON.parse(t);return"object"==typeof e&&null!==e?e:void 0}catch{return}}function y(t){return"number"==typeof t?t:void 0}function w(t,e,n){const o=t.toString("utf8");if(0!==n)throw a("claude",n,function(t,e){const n=m(t);return[("string"==typeof n?.result?n.result:n?"":t).trim(),e.trim()].filter(Boolean).join("\n")}(o,e));const r=m(o);if(!r)throw new Error(`[chat-kit/cli] claude 輸出非預期的 JSON:${c(o)}`);if(r.is_error){const t="string"==typeof r.result?r.result:JSON.stringify(r.result??"");throw new Error(`[chat-kit/cli] claude 回報錯誤:${c(t)}`)}if("string"!=typeof r.result||!r.result.trim()){const t="string"==typeof r.subtype?`(subtype:${r.subtype})`:"";throw new Error(`${p("claude").message}${t}`)}return{text:r.result,usage:{inputTokens:y(r.usage?.input_tokens)??0,outputTokens:y(r.usage?.output_tokens)??0,cacheReadTokens:y(r.usage?.cache_read_input_tokens),cacheWriteTokens:y(r.usage?.cache_creation_input_tokens)}}}const g={isEvent:!1,delta:""};function k(t,e){const n=t.trim();if(!n)return g;let o;try{o=JSON.parse(n)}catch{return g}if("object"!=typeof o||null===o)return g;const r=o;if("text"===r.type&&"string"==typeof r.part?.text)return{isEvent:!0,delta:r.part.text};if("step_finish"===r.type){const t=r.part?.tokens;"number"==typeof t?.input&&(e.inputTokens+=t.input),"number"==typeof t?.output&&(e.outputTokens+=t.output)}return{isEvent:!0,delta:""}}const b="cli-text";function S(t){const{cacheReadTokens:e,cacheWriteTokens:n}=t;return{inputTokens:{total:t.inputTokens+(e??0)+(n??0),noCache:t.inputTokens,cacheRead:e,cacheWrite:n},outputTokens:{total:t.outputTokens,text:t.outputTokens,reasoning:void 0}}}function v(t){const e=[];return void 0!==t.temperature&&e.push({type:"unsupported",feature:"temperature"}),void 0!==t.maxOutputTokens&&e.push({type:"unsupported",feature:"maxOutputTokens"}),void 0!==t.topP&&e.push({type:"unsupported",feature:"topP"}),void 0!==t.topK&&e.push({type:"unsupported",feature:"topK"}),void 0!==t.presencePenalty&&e.push({type:"unsupported",feature:"presencePenalty"}),void 0!==t.frequencyPenalty&&e.push({type:"unsupported",feature:"frequencyPenalty"}),void 0!==t.stopSequences&&t.stopSequences.length>0&&e.push({type:"unsupported",feature:"stopSequences"}),void 0!==t.responseFormat&&"json"===t.responseFormat.type&&e.push({type:"unsupported",feature:"responseFormat"}),e}function $(t){if(void 0!==t.tools&&t.tools.length>0)throw new Error("[chat-kit/cli] 本地 CLI 模型不支援引擎工具迴圈:Domain.tools 須為空(CLI 自帶執行環境與引擎迴圈衝突)")}function T(){return new Error("[chat-kit/cli] 已中止")}function x(t){if(null===t||"object"!=typeof t)throw new Error("[chat-kit/cli] options 須為物件");const{command:s}=t;if(!r.has(s))throw new Error(`[chat-kit/cli] 不支援的 CLI:${String(s)}(僅支援 claude、opencode)`);const f=(t.model??"").trim();if("claude"===s&&!f)throw new Error("[chat-kit/cli] claude 需要指定 model(例如 sonnet)");const d=(t.effort??"").trim()||void 0;if(f&&!u(f))throw new Error(`[chat-kit/cli] model 含不允許的字元:${JSON.stringify(f)}`);if(d&&!u(d))throw new Error(`[chat-kit/cli] effort 含不允許的字元:${JSON.stringify(d)}`);const m=t.timeoutMs??i;if(!Number.isFinite(m)||m<=0)throw new Error("[chat-kit/cli] timeoutMs 須為正數");const y=t.cwd,g=t.spawnProcess??l,x="claude"===s?function(t,e){const n=["-p","--model",t,"--output-format","json","--tools","","--strict-mcp-config","--disable-slash-commands","--no-session-persistence"];return e&&n.push("--effort",e),n}(f,d):function(t,e){const n=["run","--format","json"],o=(t??"").trim(),r=(e??"").trim();return o&&n.push("--model",o.includes("#")?o:r?`${o}#${r}`:o),n}(f||void 0,d);async function E(t,r,i){const u=y??await async function(){const t=o(n(),"chat-kit-cli");return await e(t,{recursive:!0}),t}();if(r.abortSignal?.aborted)throw T();const c=g(s,x,{input:t,cwd:u,timeoutMs:m,signal:r.abortSignal}),a=(async()=>{for await(const t of c.stdout){if(r.abortSignal?.aborted)throw T();i(t)}})();let p;try{[p]=await Promise.all([c.wait(),a])}catch(t){throw c.kill(),await a.catch(()=>{}),t}if(r.abortSignal?.aborted)throw T();if(p.timedOut)throw new Error(`[chat-kit/cli] ${s} 逾時(${m}ms)`);return p}async function O(t,e){const n=[],o=await E(t,e,t=>n.push(t)),r=Buffer.concat(n);return"claude"===s?w(r,o.stderr,o.code):function(t,e,n){if(0!==n)throw a("opencode",n,e);const o={inputTokens:0,outputTokens:0};let r="",i=!1;for(const e of t.toString("utf8").split("\n")){const t=k(e,o);t.isEvent&&(i=!0),r+=t.delta}if(!i)throw new Error(`[chat-kit/cli] opencode 未輸出可解析的 JSON 事件:${c(t.toString("utf8"))}`);if(!r.trim())throw p("opencode");return{text:r,usage:o}}(r,o.stderr,o.code)}function q(t){return{type:"finish",usage:S(t),finishReason:{unified:"stop",raw:"stop"}}}async function _(t,e,n,o){n.enqueue({type:"stream-start",warnings:o});const{text:r,usage:i}=await O(t,e);for(const t of function(t){return t?[{type:"text-start",id:b},{type:"text-delta",id:b,delta:t},{type:"text-end",id:b}]:[]}(r))n.enqueue(t);n.enqueue(q(i))}return{specificationVersion:"v3",provider:`chat-kit.cli.${s}`,modelId:f||s,supportedUrls:{},doGenerate(t){$(t);const e=h(t.prompt),n=v(t);return O(e,t).then(({text:t,usage:e})=>({content:[{type:"text",text:t}],finishReason:{unified:"stop",raw:"stop"},usage:S(e),warnings:n}))},doStream(t){$(t);const e=h(t.prompt),n=v(t),o=new AbortController,r=t.abortSignal;r?.aborted?o.abort():r?.addEventListener("abort",()=>o.abort(),{once:!0});const i={...t,abortSignal:o.signal};let u=!1;const c=new ReadableStream({async start(t){try{"claude"===s?await _(e,i,t,n):await async function(t,e,n,o){n.enqueue({type:"stream-start",warnings:o});const r={inputTokens:0,outputTokens:0};let i=Buffer.alloc(0),s=!1,u="";const c=t=>{const{delta:e}=k(t,r);e&&(s||(n.enqueue({type:"text-start",id:b}),s=!0),u+=e,n.enqueue({type:"text-delta",id:b,delta:e}))},f=await E(t,e,t=>{i=Buffer.concat([i,t]);let e=i.indexOf(10);for(;e>=0;)c(i.subarray(0,e).toString("utf8")),i=i.subarray(e+1),e=i.indexOf(10)});if(0!==f.code)throw a("opencode",f.code,f.stderr);if(c(i.toString("utf8")),!u.trim())throw p("opencode");n.enqueue({type:"text-end",id:b}),n.enqueue(q(r))}(e,i,t,n),t.close()}catch(e){if(u)return;t.enqueue({type:"error",error:e}),t.close()}},cancel(){u=!0,o.abort()}});return Promise.resolve({stream:c})}}}export{r as ALLOWED_CLI_COMMANDS,i as DEFAULT_CLI_TIMEOUT_MS,x as createCliModel};//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../.build/cli/types.js","../../.build/cli/util.js","../../.build/cli/spawn.js","../../.build/cli/prompt.js","../../.build/cli/claude.js","../../.build/cli/opencode.js","../../.build/cli/create-cli-model.js","../../.build/cli/isolated-cwd.js"],"names":["ALLOWED_CLI_COMMANDS","Set","DEFAULT_CLI_TIMEOUT_MS","SAFE_ARG","isSafeCliArg","value","test","excerpt","text","max","t","trim","length","slice","exitError","label","code","detail","note","Error","emptyReplyError","killTree","child","process","platform","undefined","pid","spawn","String","stdio","on","kill","defaultSpawnProcess","command","args","opts","timeoutMs","useShell","map","arg","JSON","stringify","toShellArgs","cwd","shell","stdin","input","write","end","errChunks","stderr","d","push","Buffer","isBuffer","from","timedOut","timer","setTimeout","unref","onAbort","signal","aborted","addEventListener","once","exited","Promise","resolve","reject","err","clearTimeout","removeEventListener","message","concat","toString","catch","stdout","async","chunk","wait","partText","part","p","type","mediaType","promptToText","prompt","sections","role","content","Array","isArray","join","tryParseJson","parse","numberOrUndefined","parseClaudeOutput","raw","json","result","filter","Boolean","failureDetail","is_error","subtype","usage","inputTokens","input_tokens","outputTokens","output_tokens","cacheReadTokens","cache_read_input_tokens","cacheWriteTokens","cache_creation_input_tokens","NOT_AN_EVENT","isEvent","delta","parseOpencodeLine","line","trimmed","obj","event","tokens","output","TEXT_ID","toUsage","cacheRead","cacheWrite","total","noCache","reasoning","collectWarnings","options","warnings","temperature","feature","maxOutputTokens","topP","topK","presencePenalty","frequencyPenalty","stopSequences","responseFormat","assertNoTools","tools","abortError","createCliModel","has","model","effort","Number","isFinite","spawnProcess","buildClaudeArgs","variant","cleanModel","cleanVariant","includes","buildOpencodeArgs","runCli","callOptions","onChunk","workDir","dir","tmpdir","mkdir","recursive","isolatedCwd","abortSignal","proc","draining","all","runOnce","chunks","sawEvent","split","parsed","parseOpencodeOutput","finishPart","finishReason","unified","doClaudeStream","controller","enqueue","id","textParts","specificationVersion","provider","modelId","supportedUrls","doGenerate","then","doStream","aborter","AbortController","upstream","abort","streamOptions","cancelled","stream","ReadableStream","start","pending","alloc","textStarted","handleLine","idx","indexOf","subarray","doOpencodeStream","close","error","cancel"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 沒指定 `cwd` 時給子行程用的空目錄。
|
|
3
|
+
*
|
|
4
|
+
* claude/opencode 都會從工作目錄(及其上層)讀 CLAUDE.md、AGENTS.md 這類專案指示。
|
|
5
|
+
* 繼承伺服器行程的 cwd,等於把伺服器所在專案的內部說明塞進每一則終端使用者的對話。
|
|
6
|
+
* 用固定的空目錄隔離;要讓 CLI 看到某個專案,由呼叫端明確傳 `cwd`。
|
|
7
|
+
*
|
|
8
|
+
* 每次呼叫都 `mkdir -p`(冪等、很便宜),不留模組層狀態。
|
|
9
|
+
*/
|
|
10
|
+
export declare function isolatedCwd(): Promise<string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opencode 非互動呼叫(v2 語法):`opencode run --format json [--model provider/model#variant]`,
|
|
3
|
+
* prompt 經 stdin 餵入。stdout 是 NDJSON:`text` 事件累加正文,`step_finish` 事件累加用量。
|
|
4
|
+
*
|
|
5
|
+
* 實測 v2.0.22 簡單回覆只吐 `step_start`+`text`、沒有 `step_finish`,此時用量為 0
|
|
6
|
+
*(CLI 本來就沒給,不是解析漏掉)。
|
|
7
|
+
*
|
|
8
|
+
* v1 的 `--variant` 獨立旗標已在 v2 移除,不支援;variant 一律併進 `--model` 的 `#` 後綴。
|
|
9
|
+
* 有 variant 但無 model 時 v2 無處可掛,沿用 opencode 自身預設(與 ai-pr-review 相同取捨)。
|
|
10
|
+
*/
|
|
11
|
+
import type { CliUsage } from './claude.js';
|
|
12
|
+
export declare function buildOpencodeArgs(model?: string, variant?: string): string[];
|
|
13
|
+
export interface OpencodeLineResult {
|
|
14
|
+
/** 本行是否為可解析的 JSON 事件物件 */
|
|
15
|
+
isEvent: boolean;
|
|
16
|
+
/** 本行帶出的正文增量(非 text 事件為空字串) */
|
|
17
|
+
delta: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 解析單一 NDJSON 行。呼叫端負責累加回傳的用量。
|
|
21
|
+
* 無法解析的行(空行、非 JSON)不算事件並忽略——與 ai-pr-review 相同。
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseOpencodeLine(line: string, usage: CliUsage): OpencodeLineResult;
|
|
24
|
+
export declare function parseOpencodeOutput(stdout: Buffer, stderr: string, code: number): {
|
|
25
|
+
text: string;
|
|
26
|
+
usage: CliUsage;
|
|
27
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把 AI SDK 的結構化 prompt 攤平成 CLI 吃的單一文字。
|
|
3
|
+
*
|
|
4
|
+
* CLI 沒有 roles/parts 的概念(ai-router 甚至只取最後一段 user),但引擎已把
|
|
5
|
+
* system、上下文區塊都排進 prompt,丟掉 system 會讓 server 組好的提示詞遺失,
|
|
6
|
+
* 故依序全拼並標註角色。
|
|
7
|
+
*
|
|
8
|
+
* 各 part 的處理:
|
|
9
|
+
* - text:原文
|
|
10
|
+
* - reasoning:略過(上一輪的隱藏推理不該回灌成 Assistant 的話)
|
|
11
|
+
* - file/圖片:二進位內容 CLI 吃不了,只留一行佔位說明;若序列化會是每個位元組一個 key 的巨型 JSON
|
|
12
|
+
* - 其餘(工具往返):以 JSON 保留資訊
|
|
13
|
+
*/
|
|
14
|
+
import type { LanguageModelV3Prompt } from '@ai-sdk/provider';
|
|
15
|
+
export declare function promptToText(prompt: LanguageModelV3Prompt): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 子行程管理:spawn+逾時+中止,stdout 一律以 Buffer 累積。
|
|
3
|
+
*
|
|
4
|
+
* 中文是多位元組 UTF-8,逐 chunk 轉字串會在邊界切出 `�`(ai-pr-review 的教訓)。
|
|
5
|
+
* 一次性呼叫在結束後一次 decode;串流解析只在 `\n`(0x0A)位元組處切片——
|
|
6
|
+
* UTF-8 續位元組恆大於等於 0x80,不會誤切。
|
|
7
|
+
*/
|
|
8
|
+
export interface SpawnProcessOptions {
|
|
9
|
+
/** 經 stdin 餵入的完整 prompt(必經 stdin,不放 argv:命令列長度上限約 32KB) */
|
|
10
|
+
input?: string;
|
|
11
|
+
cwd?: string;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
}
|
|
15
|
+
export interface SpawnProcessResult {
|
|
16
|
+
/** 行程結束碼;被訊號殺掉時為 null,呼叫端看到的會是正規化後的數字 */
|
|
17
|
+
code: number;
|
|
18
|
+
stderr: string;
|
|
19
|
+
timedOut: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** 可 `for await` 逐塊讀 stdout 的子行程 */
|
|
22
|
+
export interface CliProcess {
|
|
23
|
+
stdout: AsyncIterable<Buffer>;
|
|
24
|
+
wait(): Promise<SpawnProcessResult>;
|
|
25
|
+
kill(): void;
|
|
26
|
+
}
|
|
27
|
+
export type SpawnProcessFn = (command: string, args: string[], opts: SpawnProcessOptions) => CliProcess;
|
|
28
|
+
export declare const defaultSpawnProcess: SpawnProcessFn;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SpawnProcessFn } from './spawn.js';
|
|
2
|
+
/** 目前支援的本地 CLI。白名單制:避免設定注入帶起任意指令 */
|
|
3
|
+
export type CliCommand = 'claude' | 'opencode';
|
|
4
|
+
export declare const ALLOWED_CLI_COMMANDS: ReadonlySet<CliCommand>;
|
|
5
|
+
/** 預設逾時(與 ai-router 的 CLI 層一致) */
|
|
6
|
+
export declare const DEFAULT_CLI_TIMEOUT_MS = 120000;
|
|
7
|
+
export interface CliModelOptions {
|
|
8
|
+
/** 要呼叫哪個 CLI */
|
|
9
|
+
command: CliCommand;
|
|
10
|
+
/**
|
|
11
|
+
* claude:必填(例如 `sonnet` / `opus`)。
|
|
12
|
+
* opencode:選填(例如 `provider/model`;留空用 opencode 自身預設)。
|
|
13
|
+
*/
|
|
14
|
+
model?: string;
|
|
15
|
+
/**
|
|
16
|
+
* 推理強度,原樣轉交:claude 走 `--effort`,opencode 走 `--model` 的 `#variant` 後綴。
|
|
17
|
+
* 不在對方清單內的值不攔截,由該 CLI 自行報錯。
|
|
18
|
+
*/
|
|
19
|
+
effort?: string;
|
|
20
|
+
/** 逾時毫秒數,預設 120000 */
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* 子行程工作目錄。省略時用系統暫存下的空目錄(不繼承伺服器行程的 cwd),
|
|
24
|
+
* 避免 CLI 把該目錄的 CLAUDE.md/AGENTS.md 等專案指示帶進使用者的對話;要讓 CLI 看到某個專案才明確傳入。
|
|
25
|
+
*/
|
|
26
|
+
cwd?: string;
|
|
27
|
+
/** 測試用:替換子行程的建立方式 */
|
|
28
|
+
spawnProcess?: SpawnProcessFn;
|
|
29
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/ 內共用的小工具:argv 安全字元、錯誤訊息片段、統一的錯誤建構。
|
|
3
|
+
*/
|
|
4
|
+
export declare function isSafeCliArg(value: string): boolean;
|
|
5
|
+
/** 錯誤訊息只取片段,避免整份輸出灌爆終端 */
|
|
6
|
+
export declare function excerpt(text: string, max?: number): string;
|
|
7
|
+
/** CLI 非零退出;`detail` 為空則只報 exit code */
|
|
8
|
+
export declare function exitError(label: string, code: number, detail: string): Error;
|
|
9
|
+
/** CLI 正常結束卻沒有任何文字:視為失敗,不讓空回覆被當成成功送出 `done` */
|
|
10
|
+
export declare function emptyReplyError(label: string): Error;
|
package/docs/API.md
CHANGED
|
@@ -131,6 +131,22 @@ listModels(baseURL, apiKeys, options?): Promise<ListedModel[]>
|
|
|
131
131
|
|
|
132
132
|
設定形狀(`rawConfig`):`{ model: 'provider' 或 'provider/model', reasoningEffort?, backup? }`;主設定無效直接拋錯,備援無效只警告並略過。備援只沿用推理強度、不沿用模型名(改用備援自己的 `defaultModel`)。
|
|
133
133
|
|
|
134
|
+
## 5.1 cli/(本地 CLI 當模型,子路徑 `@jungtz/chat-kit/cli`)
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
createCliModel(options: CliModelOptions): LanguageModelV3
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| 欄位 | 說明 |
|
|
141
|
+
| :-- | :-- |
|
|
142
|
+
| `command` | `'claude'` | `'opencode'`(白名單) |
|
|
143
|
+
| `model` | claude 必填;opencode 選填。只接受 `[A-Za-z0-9._:/#@+=-]` |
|
|
144
|
+
| `effort` | 原樣轉交:claude 走 `--effort`,opencode 併進 `--model` 的 `#variant` |
|
|
145
|
+
| `timeoutMs` | 預設 120000 |
|
|
146
|
+
| `cwd` | 省略=系統暫存下的空目錄(不繼承目前行程,免得 CLI 讀到專案的 CLAUDE.md/AGENTS.md);要讓 CLI 看某專案才傳 |
|
|
147
|
+
|
|
148
|
+
限制:`tools` 須為空(否則拋錯);temperature 等微調參數只記 warning;claude 的內建工具與 MCP 一律關閉;失敗(非零退出、空回覆、逾時)拋錯,串流則以 error part 交給下游,不送 `done`。
|
|
149
|
+
|
|
134
150
|
## 6. 常見陷阱
|
|
135
151
|
|
|
136
152
|
| 陷阱 | 正確做法 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jungtz/chat-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "通用 Chat 引擎:AI SDK 模型層、金鑰輪替、主備援、提示詞組裝與閘門",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"./models": {
|
|
12
12
|
"types": "./dist/models/index.d.ts",
|
|
13
13
|
"import": "./dist/models/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./cli": {
|
|
16
|
+
"types": "./dist/cli/index.d.ts",
|
|
17
|
+
"import": "./dist/cli/index.js"
|
|
14
18
|
}
|
|
15
19
|
},
|
|
16
20
|
"files": [
|