deepccc 0.2.24 → 0.2.25

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.
@@ -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,98 @@
1
+ /** Opt-in stateless completion transport for integrations owning their complete prompts. */
2
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
3
+ import { Output, streamText } from 'ai';
4
+ const protectedKeys = new Set(['messages', 'model', 'stream', 'tools', 'tool_choice', 'response_format', 'temperature', 'max_tokens', 'max_completion_tokens']);
5
+ function positive(n) { if (!Number.isSafeInteger(n) || n < 1)
6
+ throw new Error('Positive integer budget required'); return n; }
7
+ function raceAbort(work, signal) {
8
+ return new Promise((resolve, reject) => {
9
+ const abort = () => reject(new Error('Interrupted'));
10
+ signal.addEventListener('abort', abort, { once: true });
11
+ Promise.resolve(work).then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));
12
+ if (signal.aborted)
13
+ abort();
14
+ });
15
+ }
16
+ export class CompletionSession {
17
+ config;
18
+ constructor(config) {
19
+ this.config = config;
20
+ if (!config.apiKey || !config.model)
21
+ throw new Error('Model credentials required');
22
+ }
23
+ async complete(request, externalSignal) {
24
+ const started = performance.now(), timeout = positive(request.timeoutMs);
25
+ if (timeout > 2147483647)
26
+ throw new Error('Deadline exceeds timer capacity');
27
+ const messages = JSON.parse(JSON.stringify(request.messages));
28
+ if (JSON.stringify(messages).length > positive(request.maxContextChars ?? 1_000_000))
29
+ throw new Error('Context budget exceeded');
30
+ const extensions = JSON.parse(JSON.stringify(request.extensions ?? {}));
31
+ if (Object.keys(extensions).some(key => protectedKeys.has(key)) || JSON.stringify(extensions).length > 8192)
32
+ throw new Error('Invalid request extension');
33
+ const responseLimit = positive(request.maxResponseChars ?? 1_000_000);
34
+ if (request.maxOutputTokens !== undefined)
35
+ positive(request.maxOutputTokens);
36
+ const controller = new AbortController();
37
+ let expired = false;
38
+ const cancel = () => controller.abort();
39
+ externalSignal?.addEventListener('abort', cancel, { once: true });
40
+ if (externalSignal?.aborted)
41
+ cancel();
42
+ const timer = setTimeout(() => { expired = true; controller.abort(); }, timeout);
43
+ let calls = 0;
44
+ const metrics = () => ({ durationMs: performance.now() - started, modelCalls: calls });
45
+ try {
46
+ if (controller.signal.aborted)
47
+ throw new Error('Interrupted');
48
+ const client = createOpenAICompatible({ name: 'completion', ...this.config, includeUsage: true,
49
+ fetch: async (url, init) => fetch(url, { ...init, body: JSON.stringify({ ...JSON.parse(String(init?.body)), ...extensions }) }) });
50
+ calls++;
51
+ const result = streamText({ model: client(this.config.model), messages, allowSystemInMessages: true, temperature: request.temperature, maxOutputTokens: request.maxOutputTokens,
52
+ ...(request.json ? { output: Output.json() } : {}), maxRetries: 0, abortSignal: controller.signal, onError: () => { } });
53
+ const iterator = result.fullStream[Symbol.asyncIterator]();
54
+ let content = '', reasoning = '', finish;
55
+ try {
56
+ while (true) {
57
+ const next = await raceAbort(iterator.next(), controller.signal);
58
+ if (controller.signal.aborted)
59
+ throw new Error('Interrupted');
60
+ if (next.done)
61
+ break;
62
+ const part = next.value;
63
+ if (part.type === 'error') {
64
+ const status = part.error?.statusCode;
65
+ return { status: 'error', diagnostic: { code: 'provider_error', ...(typeof status === 'number' && Number.isInteger(status) && status >= 100 && status <= 599 ? { httpStatus: status } : {}) }, metrics: metrics() };
66
+ }
67
+ if (part.type === 'text-delta')
68
+ content += part.text;
69
+ if (part.type === 'reasoning-delta')
70
+ reasoning += part.text;
71
+ if (part.type === 'finish')
72
+ finish = part.finishReason;
73
+ if (content.length + reasoning.length > responseLimit)
74
+ return { status: 'invalid_output', diagnostic: { code: 'response_limit' }, metrics: metrics() };
75
+ }
76
+ if (controller.signal.aborted)
77
+ throw new Error('Interrupted');
78
+ if (finish !== 'stop')
79
+ return { status: 'invalid_output', diagnostic: { code: finish === 'length' ? 'output_truncated' : 'incomplete_response' }, metrics: metrics() };
80
+ return { status: 'success', content, reasoning, metrics: metrics() };
81
+ }
82
+ finally {
83
+ try {
84
+ void Promise.resolve(iterator.return?.()).catch(() => { });
85
+ }
86
+ catch { /* best effort */ }
87
+ }
88
+ }
89
+ catch {
90
+ return { status: expired ? 'timed_out' : controller.signal.aborted ? 'cancelled' : 'error', diagnostic: { code: expired ? 'deadline_exceeded' : controller.signal.aborted ? 'cancelled' : 'transport_error' }, metrics: metrics() };
91
+ }
92
+ finally {
93
+ clearTimeout(timer);
94
+ externalSignal?.removeEventListener('abort', cancel);
95
+ controller.abort();
96
+ }
97
+ }
98
+ }
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,36 @@
1
+ # Caller-owned completions
2
+
3
+ `CompletionSession` is an opt-in stateless OpenAI-compatible transport. Pass the
4
+ entire `messages` array; no system guidance, workspace files, skills, tools,
5
+ history or compaction is injected. This is useful when comparing integrations
6
+ without rewriting their established prompt builders.
7
+
8
+ ```js
9
+ import {CompletionSession} from 'deepccc';
10
+ const session = new CompletionSession({
11
+ apiKey: process.env.DEEPCCC_API_KEY,
12
+ baseURL: process.env.DEEPCCC_BASE_URL,
13
+ model: process.env.DEEPCCC_MODEL,
14
+ });
15
+ const result = await session.complete({
16
+ messages: [{role: 'system', content: 'Return JSON only.'},
17
+ {role: 'user', content: 'Return an object with ok=true.'}],
18
+ json: true, timeoutMs: 5000, maxOutputTokens: 256, temperature: 0.1,
19
+ });
20
+ if (result.status === 'success') console.log(result.content);
21
+ ```
22
+
23
+ JSON mode sends `response_format: {type: 'json_object'}`; callers still parse,
24
+ validate and authorize results. No local syntax repair or automatic retry is
25
+ performed here. Completion is accepted only after a normal stop, never a length
26
+ limit or interrupted stream. Text mode preserves plain-text output. Parallel
27
+ calls do not share mutable history. `timeoutMs` covers stream consumption;
28
+ optional cancellation is the second argument. Context/response character limits
29
+ default to one million each. Oversize input and invalid options throw before
30
+ network activity; runtime failures return fixed diagnostics without raw errors.
31
+
32
+ Optional `extensions` passes caller-supplied provider-specific JSON options.
33
+ It cannot override messages, model, streaming, tool settings, response format,
34
+ temperature or token limits. Keep credentials in config, not extensions. No
35
+ messages, headers or generated content are logged or persisted by this class.
36
+ Metrics include duration and request count. Remote cancellation is best effort.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepccc",
3
- "version": "0.2.24",
3
+ "version": "0.2.25",
4
4
  "description": "A lightweight coding agent with OpenAI-compatible and Anthropic Messages API support.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [