deepccc 0.2.24 → 0.2.26
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 +3 -3
- package/bin/deepccc-cli.mjs +17 -17
- package/bin/deepccc.mjs +32 -32
- package/dist/completion-session.d.ts +38 -0
- package/dist/completion-session.js +100 -0
- package/dist/decision-session.js +5 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/json-mode-fetch.d.ts +2 -0
- package/dist/json-mode-fetch.js +12 -0
- package/dist/web-page.js +411 -411
- package/docs/completion-session.md +40 -0
- package/docs/decision-session.md +196 -192
- package/docs/workspace-understanding.md +41 -41
- package/os-prompts/darwin.md +8 -8
- package/os-prompts/linux.md +8 -8
- package/os-prompts/win32.md +11 -11
- package/package.json +68 -68
package/README.md
CHANGED
|
@@ -57,9 +57,9 @@ npm run dev
|
|
|
57
57
|
|
|
58
58
|

|
|
59
59
|
|
|
60
|
-
## 核心能力
|
|
61
|
-
|
|
62
|
-
- 有界结构化决策:程序化 `DecisionSession` 支持外部管理上下文、显式工具白名单、运行时结果校验和整轮截止时间;不改变默认聊天行为。详见 [接入文档](docs/decision-session.md)。
|
|
60
|
+
## 核心能力
|
|
61
|
+
|
|
62
|
+
- 有界结构化决策:程序化 `DecisionSession` 支持外部管理上下文、显式工具白名单、运行时结果校验和整轮截止时间;不改变默认聊天行为。详见 [接入文档](docs/decision-session.md)。
|
|
63
63
|
|
|
64
64
|
- Web-first:多会话、持久化历史、实时流式过程、停止和恢复
|
|
65
65
|
- 工具时间线:ChatCCC 风格 emoji 摘要、参数/结果折叠、省略行展开和状态记忆
|
package/bin/deepccc-cli.mjs
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
6
|
-
|
|
7
|
-
const require = createRequire(import.meta.url);
|
|
8
|
-
const pkgRoot = dirname(require.resolve("../package.json"));
|
|
9
|
-
const distCli = join(pkgRoot, "dist", "cli.js");
|
|
10
|
-
let args;
|
|
11
|
-
if (existsSync(distCli)) {
|
|
12
|
-
args = [distCli, ...process.argv.slice(2)];
|
|
13
|
-
} else {
|
|
14
|
-
args = [require.resolve("tsx/cli"), join(pkgRoot, "src", "cli.ts"), ...process.argv.slice(2)];
|
|
15
|
-
}
|
|
16
|
-
const result = spawnSync(process.execPath, args, { stdio: "inherit", cwd: process.cwd(), env: process.env });
|
|
17
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const pkgRoot = dirname(require.resolve("../package.json"));
|
|
9
|
+
const distCli = join(pkgRoot, "dist", "cli.js");
|
|
10
|
+
let args;
|
|
11
|
+
if (existsSync(distCli)) {
|
|
12
|
+
args = [distCli, ...process.argv.slice(2)];
|
|
13
|
+
} else {
|
|
14
|
+
args = [require.resolve("tsx/cli"), join(pkgRoot, "src", "cli.ts"), ...process.argv.slice(2)];
|
|
15
|
+
}
|
|
16
|
+
const result = spawnSync(process.execPath, args, { stdio: "inherit", cwd: process.cwd(), env: process.env });
|
|
17
|
+
process.exit(result.status === null ? 1 : result.status);
|
package/bin/deepccc.mjs
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
6
|
-
|
|
7
|
-
const require = createRequire(import.meta.url);
|
|
8
|
-
const pkgRoot = dirname(require.resolve("../package.json"));
|
|
9
|
-
const rawArgs = process.argv.slice(2);
|
|
10
|
-
const distWebEntry = join(pkgRoot, "dist", "web-entry.js");
|
|
11
|
-
const distCli = join(pkgRoot, "dist", "cli.js");
|
|
12
|
-
const sourceMode = !existsSync(distWebEntry);
|
|
13
|
-
const isLegacy = rawArgs.length > 0 && rawArgs[0] !== "web" && !["--reuse-existing", "--port", "--no-open", "--help", "-h"].includes(rawArgs[0]);
|
|
14
|
-
|
|
15
|
-
if (isLegacy) {
|
|
16
|
-
console.error("[DeepCCC] CLI commands have moved to deepccc-cli; forwarding this invocation for compatibility.");
|
|
17
|
-
const args = sourceMode
|
|
18
|
-
? [require.resolve("tsx/cli"), join(pkgRoot, "src", "cli.ts"), ...rawArgs]
|
|
19
|
-
: [distCli, ...rawArgs];
|
|
20
|
-
const result = spawnSync(process.execPath, args, { stdio: "inherit", cwd: process.cwd(), env: process.env });
|
|
21
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (sourceMode) {
|
|
25
|
-
const result = spawnSync(process.execPath, [require.resolve("tsx/cli"), join(pkgRoot, "src", "web-entry.ts"), ...rawArgs], {
|
|
26
|
-
stdio: "inherit", cwd: process.cwd(), env: process.env,
|
|
27
|
-
});
|
|
28
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const { runWebEntry } = await import(new URL("../dist/web-entry.js", import.meta.url));
|
|
32
|
-
await runWebEntry(rawArgs);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const pkgRoot = dirname(require.resolve("../package.json"));
|
|
9
|
+
const rawArgs = process.argv.slice(2);
|
|
10
|
+
const distWebEntry = join(pkgRoot, "dist", "web-entry.js");
|
|
11
|
+
const distCli = join(pkgRoot, "dist", "cli.js");
|
|
12
|
+
const sourceMode = !existsSync(distWebEntry);
|
|
13
|
+
const isLegacy = rawArgs.length > 0 && rawArgs[0] !== "web" && !["--reuse-existing", "--port", "--no-open", "--help", "-h"].includes(rawArgs[0]);
|
|
14
|
+
|
|
15
|
+
if (isLegacy) {
|
|
16
|
+
console.error("[DeepCCC] CLI commands have moved to deepccc-cli; forwarding this invocation for compatibility.");
|
|
17
|
+
const args = sourceMode
|
|
18
|
+
? [require.resolve("tsx/cli"), join(pkgRoot, "src", "cli.ts"), ...rawArgs]
|
|
19
|
+
: [distCli, ...rawArgs];
|
|
20
|
+
const result = spawnSync(process.execPath, args, { stdio: "inherit", cwd: process.cwd(), env: process.env });
|
|
21
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (sourceMode) {
|
|
25
|
+
const result = spawnSync(process.execPath, [require.resolve("tsx/cli"), join(pkgRoot, "src", "web-entry.ts"), ...rawArgs], {
|
|
26
|
+
stdio: "inherit", cwd: process.cwd(), env: process.env,
|
|
27
|
+
});
|
|
28
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { runWebEntry } = await import(new URL("../dist/web-entry.js", import.meta.url));
|
|
32
|
+
await runWebEntry(rawArgs);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ModelMessage } from 'ai';
|
|
2
|
+
export interface CompletionConfig {
|
|
3
|
+
baseURL: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
model: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CompletionRequest {
|
|
8
|
+
messages: ModelMessage[];
|
|
9
|
+
timeoutMs: number;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
temperature?: number;
|
|
12
|
+
maxOutputTokens?: number;
|
|
13
|
+
/** Provider-specific JSON options; cannot replace transport, messages or tools. */
|
|
14
|
+
extensions?: Record<string, unknown>;
|
|
15
|
+
maxContextChars?: number;
|
|
16
|
+
maxResponseChars?: number;
|
|
17
|
+
}
|
|
18
|
+
export type CompletionResult = {
|
|
19
|
+
metrics: {
|
|
20
|
+
durationMs: number;
|
|
21
|
+
modelCalls: number;
|
|
22
|
+
};
|
|
23
|
+
} & ({
|
|
24
|
+
status: 'success';
|
|
25
|
+
content: string;
|
|
26
|
+
reasoning: string;
|
|
27
|
+
} | {
|
|
28
|
+
status: 'timed_out' | 'cancelled' | 'invalid_output' | 'error';
|
|
29
|
+
diagnostic: {
|
|
30
|
+
code: string;
|
|
31
|
+
httpStatus?: number;
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
export declare class CompletionSession {
|
|
35
|
+
private readonly config;
|
|
36
|
+
constructor(config: CompletionConfig);
|
|
37
|
+
complete(request: CompletionRequest, externalSignal?: AbortSignal): Promise<CompletionResult>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** Opt-in stateless completion transport for integrations owning their complete prompts. */
|
|
2
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
3
|
+
import { streamText } from 'ai';
|
|
4
|
+
import { jsonObjectFetch } from './json-mode-fetch.js';
|
|
5
|
+
const protectedKeys = new Set(['messages', 'model', 'stream', 'tools', 'tool_choice', 'response_format', 'temperature', 'max_tokens', 'max_completion_tokens']);
|
|
6
|
+
function positive(n) { if (!Number.isSafeInteger(n) || n < 1)
|
|
7
|
+
throw new Error('Positive integer budget required'); return n; }
|
|
8
|
+
function raceAbort(work, signal) {
|
|
9
|
+
return new Promise((resolve, reject) => {
|
|
10
|
+
const abort = () => reject(new Error('Interrupted'));
|
|
11
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
12
|
+
Promise.resolve(work).then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));
|
|
13
|
+
if (signal.aborted)
|
|
14
|
+
abort();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export class CompletionSession {
|
|
18
|
+
config;
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.config = config;
|
|
21
|
+
if (!config.apiKey || !config.model)
|
|
22
|
+
throw new Error('Model credentials required');
|
|
23
|
+
}
|
|
24
|
+
async complete(request, externalSignal) {
|
|
25
|
+
const started = performance.now(), timeout = positive(request.timeoutMs);
|
|
26
|
+
if (timeout > 2147483647)
|
|
27
|
+
throw new Error('Deadline exceeds timer capacity');
|
|
28
|
+
const messages = JSON.parse(JSON.stringify(request.messages));
|
|
29
|
+
if (JSON.stringify(messages).length > positive(request.maxContextChars ?? 1_000_000))
|
|
30
|
+
throw new Error('Context budget exceeded');
|
|
31
|
+
const extensions = JSON.parse(JSON.stringify(request.extensions ?? {}));
|
|
32
|
+
if (Object.keys(extensions).some(key => protectedKeys.has(key)) || JSON.stringify(extensions).length > 8192)
|
|
33
|
+
throw new Error('Invalid request extension');
|
|
34
|
+
const responseLimit = positive(request.maxResponseChars ?? 1_000_000);
|
|
35
|
+
if (request.maxOutputTokens !== undefined)
|
|
36
|
+
positive(request.maxOutputTokens);
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
let expired = false;
|
|
39
|
+
const cancel = () => controller.abort();
|
|
40
|
+
externalSignal?.addEventListener('abort', cancel, { once: true });
|
|
41
|
+
if (externalSignal?.aborted)
|
|
42
|
+
cancel();
|
|
43
|
+
const timer = setTimeout(() => { expired = true; controller.abort(); }, timeout);
|
|
44
|
+
let calls = 0;
|
|
45
|
+
const metrics = () => ({ durationMs: performance.now() - started, modelCalls: calls });
|
|
46
|
+
try {
|
|
47
|
+
if (controller.signal.aborted)
|
|
48
|
+
throw new Error('Interrupted');
|
|
49
|
+
const transport = request.json ? jsonObjectFetch() : globalThis.fetch;
|
|
50
|
+
const client = createOpenAICompatible({ name: 'completion', ...this.config, includeUsage: true,
|
|
51
|
+
fetch: async (url, init) => transport(url, { ...init, body: JSON.stringify({ ...JSON.parse(String(init?.body)), ...extensions }) }) });
|
|
52
|
+
calls++;
|
|
53
|
+
const result = streamText({ model: client(this.config.model), messages, allowSystemInMessages: true, temperature: request.temperature, maxOutputTokens: request.maxOutputTokens,
|
|
54
|
+
maxRetries: 0, abortSignal: controller.signal, onError: () => { } });
|
|
55
|
+
const iterator = result.fullStream[Symbol.asyncIterator]();
|
|
56
|
+
let content = '', reasoning = '', finish;
|
|
57
|
+
try {
|
|
58
|
+
while (true) {
|
|
59
|
+
const next = await raceAbort(iterator.next(), controller.signal);
|
|
60
|
+
if (controller.signal.aborted)
|
|
61
|
+
throw new Error('Interrupted');
|
|
62
|
+
if (next.done)
|
|
63
|
+
break;
|
|
64
|
+
const part = next.value;
|
|
65
|
+
if (part.type === 'error') {
|
|
66
|
+
const status = part.error?.statusCode;
|
|
67
|
+
return { status: 'error', diagnostic: { code: 'provider_error', ...(typeof status === 'number' && Number.isInteger(status) && status >= 100 && status <= 599 ? { httpStatus: status } : {}) }, metrics: metrics() };
|
|
68
|
+
}
|
|
69
|
+
if (part.type === 'text-delta')
|
|
70
|
+
content += part.text;
|
|
71
|
+
if (part.type === 'reasoning-delta')
|
|
72
|
+
reasoning += part.text;
|
|
73
|
+
if (part.type === 'finish')
|
|
74
|
+
finish = part.finishReason;
|
|
75
|
+
if (content.length + reasoning.length > responseLimit)
|
|
76
|
+
return { status: 'invalid_output', diagnostic: { code: 'response_limit' }, metrics: metrics() };
|
|
77
|
+
}
|
|
78
|
+
if (controller.signal.aborted)
|
|
79
|
+
throw new Error('Interrupted');
|
|
80
|
+
if (finish !== 'stop')
|
|
81
|
+
return { status: 'invalid_output', diagnostic: { code: finish === 'length' ? 'output_truncated' : 'incomplete_response' }, metrics: metrics() };
|
|
82
|
+
return { status: 'success', content, reasoning, metrics: metrics() };
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
try {
|
|
86
|
+
void Promise.resolve(iterator.return?.()).catch(() => { });
|
|
87
|
+
}
|
|
88
|
+
catch { /* best effort */ }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { status: expired ? 'timed_out' : controller.signal.aborted ? 'cancelled' : 'error', diagnostic: { code: expired ? 'deadline_exceeded' : controller.signal.aborted ? 'cancelled' : 'transport_error' }, metrics: metrics() };
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
externalSignal?.removeEventListener('abort', cancel);
|
|
97
|
+
controller.abort();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
package/dist/decision-session.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** Bounded, text-only decisions with caller-owned context and explicit capabilities. */
|
|
2
2
|
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
3
3
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
4
|
-
import { asSchema,
|
|
4
|
+
import { asSchema, streamText, tool } from "ai";
|
|
5
|
+
import { jsonObjectFetch } from "./json-mode-fetch.js";
|
|
5
6
|
import { parseJsonResponse } from "./json-response.js";
|
|
6
7
|
import { performance } from "node:perf_hooks";
|
|
7
8
|
import { config, normalizeDeepCccProvider } from "./config.js";
|
|
@@ -92,7 +93,8 @@ export class DecisionSession {
|
|
|
92
93
|
const baseURL = overrides.baseURL ?? config.baseURL;
|
|
93
94
|
const client = provider === "anthropic"
|
|
94
95
|
? createAnthropic({ apiKey, baseURL: baseURL.trim().replace(/\/+$/, "") })
|
|
95
|
-
: createOpenAICompatible({ name: "deepccc", apiKey, baseURL, includeUsage: true
|
|
96
|
+
: createOpenAICompatible({ name: "deepccc", apiKey, baseURL, includeUsage: true,
|
|
97
|
+
...(this.outputMode === "json" ? { fetch: jsonObjectFetch() } : {}) });
|
|
96
98
|
this.model = client(overrides.model ?? config.model);
|
|
97
99
|
const effort = (overrides.effort ?? config.effort ?? "").trim();
|
|
98
100
|
this.providerOptions = effort ? provider === "anthropic"
|
|
@@ -240,7 +242,7 @@ export class DecisionSession {
|
|
|
240
242
|
const began = performance.now();
|
|
241
243
|
metrics.modelCalls++;
|
|
242
244
|
const result = streamText({ model: this.model, system: this.system, messages: [...messages],
|
|
243
|
-
...(this.outputMode === "json" ? {
|
|
245
|
+
...(this.outputMode === "json" ? {} : { tools: this.modelTools, toolChoice: "required" }),
|
|
244
246
|
maxRetries: 0, abortSignal: signal, maxOutputTokens: this.maxOutputTokens,
|
|
245
247
|
// The SDK default logs raw errors. Return allowlisted diagnostics instead.
|
|
246
248
|
onError: () => { },
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
import { type DeepCccProvider } from "./config.js";
|
|
7
7
|
import { type PermissionMode, type PermissionResolver } from "./permissions.js";
|
|
8
8
|
export { DecisionSession } from "./decision-session.js";
|
|
9
|
+
export { CompletionSession } from "./completion-session.js";
|
|
10
|
+
export type { CompletionConfig, CompletionRequest, CompletionResult } from "./completion-session.js";
|
|
9
11
|
export type { DecisionModelConfig, DecisionSessionOptions, DecisionRequest, DecisionResult, DecisionMetrics, DecisionFailureStatus, DecisionDiagnostic, DecisionTool, DecisionToolContext } from "./decision-session.js";
|
|
10
12
|
export declare const DEFAULT_COMPACTION_TIMEOUT_MS: number;
|
|
11
13
|
/**
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import { applyPrivacy, applyPrivacyToJson } from "./privacy.js";
|
|
|
21
21
|
import { buildWorkspaceMap, needsWorkspaceOrientation } from "./workspace-map.js";
|
|
22
22
|
import { compactToolLoopMessages } from "./turn-context.js";
|
|
23
23
|
export { DecisionSession } from "./decision-session.js";
|
|
24
|
+
export { CompletionSession } from "./completion-session.js";
|
|
24
25
|
// ---------------------------------------------------------------------------
|
|
25
26
|
// 系统提示词 — 编译期冻结常量(DeepCCC 英文品牌)
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Protocol JSON mode without client-side parsing of partial objects. */
|
|
2
|
+
export function jsonObjectFetch(transport = globalThis.fetch) {
|
|
3
|
+
return async (input, init) => {
|
|
4
|
+
if (typeof init?.body !== "string")
|
|
5
|
+
throw new Error("JSON mode requires a serialized request body");
|
|
6
|
+
const body = JSON.parse(init.body);
|
|
7
|
+
return transport(input, {
|
|
8
|
+
...init,
|
|
9
|
+
body: JSON.stringify({ ...body, response_format: { type: "json_object" } }),
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
}
|