@rebon/cli-win32-x64 1.2.0 → 1.4.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/LICENSE +202 -0
- package/NOTICE +10 -0
- package/README.md +203 -144
- package/THIRD_PARTY_NOTICES.txt +1232 -128
- package/bin/rebon.js +22 -22
- package/lib/windows-managed.js +390 -390
- package/package.json +9 -2
- package/payload/compose-runtime/package.json +17 -17
- package/payload/compose-runtime/payload/compose/shims/dsh-anonymous-user-id.js +10 -10
- package/payload/compose-runtime/payload/compose/shims/dsh-credentials.js +13 -13
- package/payload/compose-runtime/payload/compose/shims/dsh-launch-environment.js +21 -21
- package/payload/compose-runtime/payload/compose/shims/dsh-llm.js +313 -313
- package/payload/compose-runtime/payload/compose/shims/dsh-settings.js +43 -43
- package/payload/compose-runtime/payload/compose/shims/dsh-tools.js +20 -20
- package/payload/compose-runtime/payload/compose/shims/dsh-web.js +10 -10
- package/payload/compose-runtime/payload/compose/shims/llm-adapter.js +32 -32
- package/payload/compose-runtime/payload/compose/shims/zod-lite.js +49 -49
- package/payload/compose-runtime/payload/vendor/cordis/LICENSE +21 -0
- package/payload/compose-runtime/payload/vendor/cordis/index.js +1530 -1530
- package/payload/compose-runtime/payload/vendor/cosmokit/LICENSE +21 -21
- package/payload/compose-runtime/payload/vendor/cosmokit/index.mjs +357 -357
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-agent-loop +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-core +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-llm-deepseek +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-logger-console +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-scope +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-session +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-system-prompt +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timeout +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-timer +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-todo +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tool-web +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-tools-schema +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown +21 -0
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-turndown-plugin-gfm +21 -0
- package/payload/compose-runtime/payload/vendor/dsh/LICENSE-web-search-exa +21 -21
- package/payload/compose-runtime/payload/vendor/dsh/agent-loop.js +1193 -1193
- package/payload/compose-runtime/payload/vendor/dsh/agent.js +714 -714
- package/payload/compose-runtime/payload/vendor/dsh/llm-core.js +269 -269
- package/payload/compose-runtime/payload/vendor/dsh/llm-deepseek.js +672 -672
- package/payload/compose-runtime/payload/vendor/dsh/logger-console.js +83 -83
- package/payload/compose-runtime/payload/vendor/dsh/scope.js +287 -287
- package/payload/compose-runtime/payload/vendor/dsh/session.js +1668 -1668
- package/payload/compose-runtime/payload/vendor/dsh/system-prompt.js +309 -309
- package/payload/compose-runtime/payload/vendor/dsh/timeout.js +100 -100
- package/payload/compose-runtime/payload/vendor/dsh/timer.js +128 -128
- package/payload/compose-runtime/payload/vendor/dsh/tool-todo.js +143 -143
- package/payload/compose-runtime/payload/vendor/dsh/tool-web.js +1527 -1527
- package/payload/compose-runtime/payload/vendor/dsh/tools-schema.js +849 -849
- package/payload/compose-runtime/payload/vendor/dsh/web-search-exa.js +122 -122
- package/payload/compose-runtime/payload/vendor/eventsource-parser/LICENSE +21 -21
- package/payload/compose-runtime/payload/vendor/eventsource-parser/index.js +177 -177
- package/payload/compose-runtime/payload/vendor/eventsource-parser/stream.js +48 -48
- package/payload/compose-runtime/payload/vendor/schemastery/LICENSE +21 -0
- package/payload/compose-runtime/payload/vendor/schemastery/index.mjs +656 -656
- package/payload/compose-runtime/payload-manifests.json +48 -48
- package/payload/compose-runtime/src/bridge.mjs +193 -195
- package/payload/compose-runtime/src/credentials-runtime.mjs +50 -50
- package/payload/compose-runtime/src/eventsource-stream.mjs +48 -48
- package/payload/compose-runtime/src/index.mjs +7 -7
- package/payload/compose-runtime/src/llm-runtime.mjs +326 -326
- package/payload/compose-runtime/src/loader.mjs +78 -78
- package/payload/compose-runtime/src/loop-assembly.mjs +237 -237
- package/payload/compose-runtime/src/payload.mjs +49 -49
- package/payload/compose-runtime/src/plugin.mjs +59 -59
- package/payload/compose-runtime/src/realm.mjs +223 -223
- package/payload/compose-runtime/src/registry.mjs +191 -191
- package/payload/compose-runtime/src/resolve.mjs +115 -115
- package/payload/compose-runtime/src/systemprompt-runtime.mjs +67 -67
- package/payload/compose-runtime/src/tools-runtime.mjs +239 -239
- package/payload/compose-runtime/src/web-runtime.mjs +209 -209
- package/payload/plugin-host/package.json +11 -11
- package/payload/plugin-host/src/bridge.mjs +64 -64
- package/payload/plugin-host/src/cli.mjs +147 -147
- package/payload/plugin-host/src/framing.mjs +113 -113
- package/payload/plugin-host/src/host.mjs +691 -691
- package/payload/plugin-host/src/json.mjs +208 -208
- package/payload/plugin-host/src/lifecycle.mjs +114 -114
- package/payload/plugin-host/src/loader.mjs +183 -183
- package/payload/plugin-host/src/methods.mjs +377 -377
- package/payload/plugin-host/src/ownership.mjs +57 -57
- package/payload/plugin-host/src/protocol.mjs +129 -129
- package/payload/plugin-host/src/sdk.mjs +7 -7
- package/payload/rebon-boa-helper.exe +0 -0
- package/payload/rebon-browser-mcp.exe +0 -0
- package/payload/rebon-computer-use.exe +0 -0
- package/payload/rebon-lsp-mcp.exe +0 -0
- package/payload/rebon.exe +2 -2
- package/payload/sandbox-win.exe +0 -0
- package/scripts/postinstall.js +112 -112
- package/payload/compose-runtime/payload/compose/credentials-runtime.js +0 -38
- package/payload/compose-runtime/payload/compose/llm-runtime.js +0 -330
- package/payload/compose-runtime/payload/compose/loop-assembly.js +0 -149
- package/payload/compose-runtime/payload/compose/serve-dispatch.js +0 -82
- package/payload/compose-runtime/payload/compose/systemprompt-runtime.js +0 -74
- package/payload/compose-runtime/payload/compose/tools-runtime.js +0 -228
- package/payload/compose-runtime/payload/compose/web-runtime.js +0 -204
|
@@ -1,313 +1,313 @@
|
|
|
1
|
-
// Embedded-runtime shim of `@deepseek-ai/dsh-llm` — exactly the runtime
|
|
2
|
-
// symbols dsh provider plugins consume, with semantics transcribed from the
|
|
3
|
-
// dsh sources (packages/llm/llm/src). The service half of the real package
|
|
4
|
-
// (LlmRuntime) is NOT here: the composition host mounts its own `ctx.llm`
|
|
5
|
-
// (llm-runtime.js), and this module re-exports the shared LlmAdapter base
|
|
6
|
-
// class (llm-adapter.js) so both import paths hand plugins the same one.
|
|
7
|
-
import z from 'schemastery';
|
|
8
|
-
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout';
|
|
9
|
-
|
|
10
|
-
export { LlmAdapter } from './llm-adapter.js';
|
|
11
|
-
|
|
12
|
-
// ---- vendored helper core (loop round) ----
|
|
13
|
-
// The real dsh-llm helper surface the agent loop consumes — BlockAssembler,
|
|
14
|
-
// message factories, call-config marking, error-chain rendering — bundled
|
|
15
|
-
// verbatim from the dsh sources (vendor/dsh/llm-core.js) and re-exported
|
|
16
|
-
// here so every `@deepseek-ai/dsh-llm` import keeps resolving to this one
|
|
17
|
-
// module (single class/WeakSet identity for instanceof and mark checks).
|
|
18
|
-
export {
|
|
19
|
-
BlockAssembler,
|
|
20
|
-
createMessage,
|
|
21
|
-
freezeMessage,
|
|
22
|
-
createUserMessage,
|
|
23
|
-
createAssistantMessage,
|
|
24
|
-
createToolResultMessage,
|
|
25
|
-
deepFreeze,
|
|
26
|
-
markAgentLoopRequest,
|
|
27
|
-
isAgentLoopRequest,
|
|
28
|
-
callConfigEquals,
|
|
29
|
-
errorChain,
|
|
30
|
-
} from '../../vendor/dsh/llm-core.js';
|
|
31
|
-
|
|
32
|
-
// ---- error taxonomy (dsh error.ts) ----
|
|
33
|
-
|
|
34
|
-
/** Base class for all harness errors: stable machine `code` + cause chain. */
|
|
35
|
-
export class HarnessError extends Error {
|
|
36
|
-
constructor(message, code, options) {
|
|
37
|
-
super(message, options);
|
|
38
|
-
this.code = code;
|
|
39
|
-
this.name = new.target.name;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const CONTEXT_WINDOW_EXCEEDED_CODE = 'CONTEXT_WINDOW_EXCEEDED';
|
|
44
|
-
export const QUOTA_EXCEEDED_CODE = 'QUOTA';
|
|
45
|
-
export const EMPTY_RESPONSE_CODE = 'EMPTY_RESPONSE';
|
|
46
|
-
export const INVALID_CREDENTIAL_CODE = 'INVALID_CREDENTIAL';
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Exhaustiveness helper (dsh never.ts, transcribed verbatim): a value that
|
|
50
|
-
* escaped its closed union throws with diagnostics at runtime. Consumed by
|
|
51
|
-
* the vendored dsh-tools schema layer.
|
|
52
|
-
*/
|
|
53
|
-
export function assertNever(value, context) {
|
|
54
|
-
const rendered = JSON.stringify(value) ?? String(value);
|
|
55
|
-
throw new Error(`unreachable variant${context ? ` in ${context}` : ''}: ${rendered}`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const STRUCTURED_CONTEXT_OVERFLOW = new RegExp(
|
|
59
|
-
String.raw`(?:^|[^a-z0-9])context[\s_-](?:length|window)[\s_-]`
|
|
60
|
-
+ String.raw`(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\s_-]exceeded)(?:$|[^a-z0-9])`,
|
|
61
|
-
'i',
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const TOO_LARGE_FOR_CONTEXT = new RegExp(
|
|
65
|
-
String.raw`\b(?:request|prompt|input|messages?)\s+(?:is\s+|are\s+)?`
|
|
66
|
-
+ String.raw`too\s+(?:large|long)\s+for\s+(?:(?:this|the)\s+)?`
|
|
67
|
-
+ String.raw`(?:model(?:'s)?\s+)?context(?:\s+window)?\b`,
|
|
68
|
-
'i',
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
const EXCEEDS_MODEL_CONTEXT = new RegExp(
|
|
72
|
-
String.raw`\b(?:input|prompt|request|messages?)\b.{0,40}`
|
|
73
|
-
+ String.raw`\b(?:exceed(?:s|ed)?|overflows?|is\s+larger\s+than)\b.{0,40}`
|
|
74
|
-
+ String.raw`\b(?:the\s+)?(?:model(?:'s)?\s+)?context(?:\s+(?:length|window))?\b`,
|
|
75
|
-
'i',
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
/** Recognize context-overflow wording (code/type/message joined into one string). */
|
|
79
|
-
export function isContextWindowExceededError(detail) {
|
|
80
|
-
return STRUCTURED_CONTEXT_OVERFLOW.test(detail)
|
|
81
|
-
|| /\b(?:maximum|max)(?:\s+(?:allowed|supported))?\s+context\s+(?:length|window)\b/i.test(detail)
|
|
82
|
-
|| TOO_LARGE_FOR_CONTEXT.test(detail)
|
|
83
|
-
|| /\b(?:input|prompt|request)\s+(?:is\s+)?too\s+(?:long|large)\s+for\s+(?:this|the)\s+model\b/i.test(detail)
|
|
84
|
-
|| EXCEEDS_MODEL_CONTEXT.test(detail);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Recognize terminal quota/balance wording, as opposed to a transient rate limit. */
|
|
88
|
-
export function isQuotaExceededError(detail) {
|
|
89
|
-
return /\binsufficient[\s_-]+(?:quota|balance|credits?)\b/i.test(detail)
|
|
90
|
-
|| /\b(?:quota|usage[\s_-]+limit)[\s_-]+(?:exceeded|exhausted|reached)\b/i.test(detail)
|
|
91
|
-
|| /\bexceed(?:ed|s)?[\s_-]+(?:(?:your|the)[\s_-]+)?(?:current[\s_-]+)?quota\b/i.test(detail)
|
|
92
|
-
|| /\b(?:balance|credits?)[\s_-]+(?:exhausted|depleted)\b/i.test(detail)
|
|
93
|
-
|| /\bout[\s_-]+of[\s_-]+(?:credits?|budget)\b/i.test(detail);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Typed error for LLM failures: validated provider facts beside the live
|
|
98
|
-
* Error (dsh index.ts, constructor contract preserved — bad arguments throw).
|
|
99
|
-
*/
|
|
100
|
-
export class LlmError extends HarnessError {
|
|
101
|
-
constructor(message, code, options) {
|
|
102
|
-
if (typeof message !== 'string' || message.length === 0) {
|
|
103
|
-
throw new Error('LlmError message must be a non-empty string');
|
|
104
|
-
}
|
|
105
|
-
if (typeof code !== 'string' || code.length === 0) {
|
|
106
|
-
throw new Error('LlmError code must be a non-empty string');
|
|
107
|
-
}
|
|
108
|
-
if (options?.status !== undefined
|
|
109
|
-
&& (!Number.isInteger(options.status) || options.status < 100 || options.status > 599)) {
|
|
110
|
-
throw new Error('LlmError status must be an integer from 100 through 599');
|
|
111
|
-
}
|
|
112
|
-
if (options?.providerRetryAfterMs !== undefined
|
|
113
|
-
&& (!Number.isFinite(options.providerRetryAfterMs) || options.providerRetryAfterMs <= 0)) {
|
|
114
|
-
throw new Error('LlmError providerRetryAfterMs must be a positive finite number');
|
|
115
|
-
}
|
|
116
|
-
if (options?.requestId !== undefined
|
|
117
|
-
&& (typeof options.requestId !== 'string' || options.requestId.length === 0)) {
|
|
118
|
-
throw new Error('LlmError requestId must be a non-empty string');
|
|
119
|
-
}
|
|
120
|
-
super(message, code, options);
|
|
121
|
-
this.name = 'LlmError';
|
|
122
|
-
this.failure = Object.freeze({
|
|
123
|
-
message,
|
|
124
|
-
code,
|
|
125
|
-
...(options?.status === undefined ? {} : { status: options.status }),
|
|
126
|
-
...(options?.providerRetryAfterMs === undefined
|
|
127
|
-
? {}
|
|
128
|
-
: { providerRetryAfterMs: options.providerRetryAfterMs }),
|
|
129
|
-
...(options?.requestId === undefined ? {} : { requestId: options.requestId }),
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ---- api keys (dsh api-key.ts + assertUsableApiKey) ----
|
|
135
|
-
|
|
136
|
-
const LEGAL_API_KEY = /^[\x21-\x7E]+$/;
|
|
137
|
-
|
|
138
|
-
/** Judge one supplied API key, trimming surrounding whitespace first. */
|
|
139
|
-
export function normalizeApiKey(raw) {
|
|
140
|
-
const value = raw.trim();
|
|
141
|
-
if (value.length === 0) return { ok: false, reason: 'empty' };
|
|
142
|
-
if (!LEGAL_API_KEY.test(value)) return { ok: false, reason: 'illegalCharacters' };
|
|
143
|
-
return { ok: true, value };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Accept one supplied credential or refuse it as unusable (never echoes the value). */
|
|
147
|
-
export function assertUsableApiKey(raw, pkg, ref) {
|
|
148
|
-
const checked = normalizeApiKey(raw);
|
|
149
|
-
if (checked.ok) return checked.value;
|
|
150
|
-
throw new LlmError(
|
|
151
|
-
checked.reason === 'empty'
|
|
152
|
-
? `${pkg}: the API key resolved from ${ref} is blank; set ${ref} to the raw key`
|
|
153
|
-
+ ' (the web Models page writes it) or export it in the launching environment'
|
|
154
|
-
: `${pkg}: the API key resolved from ${ref} contains characters no HTTP header can carry;`
|
|
155
|
-
+ ` set ${ref} to the raw key alone (the web Models page writes it)`,
|
|
156
|
-
INVALID_CREDENTIAL_CODE,
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// ---- attribution (dsh attribution.ts; embedded identity, fixed version) ----
|
|
161
|
-
|
|
162
|
-
export const APP_IDENTITY = {
|
|
163
|
-
product: 'deepseek-harness',
|
|
164
|
-
version: '0.0.0-rebon-embedded',
|
|
165
|
-
url: 'https://github.com/deepseek-ai/deepseek-harness',
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
export function userAgent(identity = APP_IDENTITY) {
|
|
169
|
-
return `${identity.product}/${identity.version} (+${identity.url})`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
export function attributionHeaders(identity = APP_IDENTITY) {
|
|
173
|
-
return { 'user-agent': userAgent(identity) };
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ---- branded ids (dsh brand.ts: brand = identity at runtime) ----
|
|
177
|
-
|
|
178
|
-
export function MessageId(id) {
|
|
179
|
-
return id;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function CallId(id) {
|
|
183
|
-
return id;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function ProviderRequestId(id) {
|
|
187
|
-
return id;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export function ReasoningEffortId(id) {
|
|
191
|
-
return id;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ---- content helpers (dsh content.ts) ----
|
|
195
|
-
|
|
196
|
-
/** True when typed model content contains an image block, walking tool results. */
|
|
197
|
-
export function contentHasImage(content) {
|
|
198
|
-
return content.some((block) => block.type === 'image'
|
|
199
|
-
|| (block.type === 'tool-result' && contentHasImage(block.content)));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// ---- retry policy (dsh retry-policy.ts, transcribed) ----
|
|
203
|
-
|
|
204
|
-
const DEFAULT_MAX_RETRIES = 2;
|
|
205
|
-
const DEFAULT_INITIAL_DELAY_MS = 500;
|
|
206
|
-
const DEFAULT_MAX_DELAY_MS = 10_000;
|
|
207
|
-
const DEFAULT_JITTER_RATIO = 0.1;
|
|
208
|
-
const DEFAULT_RETRYABLE_CODES = Object.freeze([
|
|
209
|
-
EMPTY_RESPONSE_CODE,
|
|
210
|
-
'RATE_LIMIT',
|
|
211
|
-
'SERVER',
|
|
212
|
-
'TIMEOUT',
|
|
213
|
-
'TRANSPORT',
|
|
214
|
-
]);
|
|
215
|
-
|
|
216
|
-
const backoffSchema = z.object({
|
|
217
|
-
initialDelayMs: z.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_INITIAL_DELAY_MS),
|
|
218
|
-
maxDelayMs: z.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_MAX_DELAY_MS),
|
|
219
|
-
jitterRatio: z.number().min(0).max(1).default(DEFAULT_JITTER_RATIO),
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const normalPolicySchema = z.object({
|
|
223
|
-
mode: z.const('normal').required(),
|
|
224
|
-
maxRetries: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_RETRIES),
|
|
225
|
-
retryableCodes: z.array(z.string()).default([...DEFAULT_RETRYABLE_CODES]),
|
|
226
|
-
backoff: backoffSchema,
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
const alwaysPolicySchema = z.object({
|
|
230
|
-
mode: z.const('always').required(),
|
|
231
|
-
backoff: backoffSchema,
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
/** Cordis schema embedded by each concrete provider configuration. */
|
|
235
|
-
export const RetryPolicySchema = z.union([normalPolicySchema, alwaysPolicySchema]);
|
|
236
|
-
|
|
237
|
-
const NORMAL_POLICY_KEYS = new Set(['mode', 'maxRetries', 'retryableCodes', 'backoff']);
|
|
238
|
-
const ALWAYS_POLICY_KEYS = new Set(['mode', 'backoff']);
|
|
239
|
-
const BACKOFF_KEYS = new Set(['initialDelayMs', 'maxDelayMs', 'jitterRatio']);
|
|
240
|
-
|
|
241
|
-
function validateKeys(value, allowed, path) {
|
|
242
|
-
for (const key of Object.keys(value)) {
|
|
243
|
-
if (!allowed.has(key)) throw new Error(`${path}: unknown key "${key}"`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function resolveBackoff(config, path) {
|
|
248
|
-
if (config !== undefined) validateKeys(config, BACKOFF_KEYS, path);
|
|
249
|
-
const initialDelayMs = config?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
|
|
250
|
-
const maxDelayMs = config?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
251
|
-
const jitterRatio = config?.jitterRatio ?? DEFAULT_JITTER_RATIO;
|
|
252
|
-
|
|
253
|
-
if (!Number.isFinite(initialDelayMs) || initialDelayMs <= 0 || initialDelayMs > MAX_TIMER_DELAY_MS) {
|
|
254
|
-
throw new Error(`${path}.initialDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
255
|
-
}
|
|
256
|
-
if (!Number.isFinite(maxDelayMs) || maxDelayMs <= 0 || maxDelayMs > MAX_TIMER_DELAY_MS) {
|
|
257
|
-
throw new Error(`${path}.maxDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
258
|
-
}
|
|
259
|
-
if (initialDelayMs > maxDelayMs) {
|
|
260
|
-
throw new Error(`${path}.initialDelayMs must be less than or equal to maxDelayMs`);
|
|
261
|
-
}
|
|
262
|
-
if (!Number.isFinite(jitterRatio) || jitterRatio < 0 || jitterRatio > 1) {
|
|
263
|
-
throw new Error(`${path}.jitterRatio must be between 0 and 1`);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
return Object.freeze({ initialDelayMs, maxDelayMs, jitterRatio });
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/** Validate, default, and detach one provider-owned retry policy. */
|
|
270
|
-
export function resolveRetryPolicy(config, path) {
|
|
271
|
-
if (config === undefined) {
|
|
272
|
-
return Object.freeze({
|
|
273
|
-
mode: 'normal',
|
|
274
|
-
maxRetries: DEFAULT_MAX_RETRIES,
|
|
275
|
-
retryableCodes: DEFAULT_RETRYABLE_CODES,
|
|
276
|
-
...resolveBackoff(undefined, `${path}.backoff`),
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
switch (config.mode) {
|
|
281
|
-
case 'normal': {
|
|
282
|
-
validateKeys(config, NORMAL_POLICY_KEYS, path);
|
|
283
|
-
const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
284
|
-
const retryableCodes = config.retryableCodes ?? [...DEFAULT_RETRYABLE_CODES];
|
|
285
|
-
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0) {
|
|
286
|
-
throw new Error(`${path}.maxRetries must be a non-negative safe integer`);
|
|
287
|
-
}
|
|
288
|
-
if (retryableCodes.length === 0) {
|
|
289
|
-
throw new Error(`${path}.retryableCodes must not be empty`);
|
|
290
|
-
}
|
|
291
|
-
if (retryableCodes.some((code) => typeof code !== 'string' || code.length === 0)) {
|
|
292
|
-
throw new Error(`${path}.retryableCodes must contain only non-empty strings`);
|
|
293
|
-
}
|
|
294
|
-
if (new Set(retryableCodes).size !== retryableCodes.length) {
|
|
295
|
-
throw new Error(`${path}.retryableCodes must not contain duplicates`);
|
|
296
|
-
}
|
|
297
|
-
return Object.freeze({
|
|
298
|
-
mode: 'normal',
|
|
299
|
-
maxRetries,
|
|
300
|
-
retryableCodes: Object.freeze([...retryableCodes]),
|
|
301
|
-
...resolveBackoff(config.backoff, `${path}.backoff`),
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
case 'always':
|
|
305
|
-
validateKeys(config, ALWAYS_POLICY_KEYS, path);
|
|
306
|
-
return Object.freeze({
|
|
307
|
-
mode: 'always',
|
|
308
|
-
...resolveBackoff(config.backoff, `${path}.backoff`),
|
|
309
|
-
});
|
|
310
|
-
default:
|
|
311
|
-
throw new Error(`${path}.mode must be "normal" or "always"`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
1
|
+
// Embedded-runtime shim of `@deepseek-ai/dsh-llm` — exactly the runtime
|
|
2
|
+
// symbols dsh provider plugins consume, with semantics transcribed from the
|
|
3
|
+
// dsh sources (packages/llm/llm/src). The service half of the real package
|
|
4
|
+
// (LlmRuntime) is NOT here: the composition host mounts its own `ctx.llm`
|
|
5
|
+
// (llm-runtime.js), and this module re-exports the shared LlmAdapter base
|
|
6
|
+
// class (llm-adapter.js) so both import paths hand plugins the same one.
|
|
7
|
+
import z from 'schemastery';
|
|
8
|
+
import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout';
|
|
9
|
+
|
|
10
|
+
export { LlmAdapter } from './llm-adapter.js';
|
|
11
|
+
|
|
12
|
+
// ---- vendored helper core (loop round) ----
|
|
13
|
+
// The real dsh-llm helper surface the agent loop consumes — BlockAssembler,
|
|
14
|
+
// message factories, call-config marking, error-chain rendering — bundled
|
|
15
|
+
// verbatim from the dsh sources (vendor/dsh/llm-core.js) and re-exported
|
|
16
|
+
// here so every `@deepseek-ai/dsh-llm` import keeps resolving to this one
|
|
17
|
+
// module (single class/WeakSet identity for instanceof and mark checks).
|
|
18
|
+
export {
|
|
19
|
+
BlockAssembler,
|
|
20
|
+
createMessage,
|
|
21
|
+
freezeMessage,
|
|
22
|
+
createUserMessage,
|
|
23
|
+
createAssistantMessage,
|
|
24
|
+
createToolResultMessage,
|
|
25
|
+
deepFreeze,
|
|
26
|
+
markAgentLoopRequest,
|
|
27
|
+
isAgentLoopRequest,
|
|
28
|
+
callConfigEquals,
|
|
29
|
+
errorChain,
|
|
30
|
+
} from '../../vendor/dsh/llm-core.js';
|
|
31
|
+
|
|
32
|
+
// ---- error taxonomy (dsh error.ts) ----
|
|
33
|
+
|
|
34
|
+
/** Base class for all harness errors: stable machine `code` + cause chain. */
|
|
35
|
+
export class HarnessError extends Error {
|
|
36
|
+
constructor(message, code, options) {
|
|
37
|
+
super(message, options);
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.name = new.target.name;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const CONTEXT_WINDOW_EXCEEDED_CODE = 'CONTEXT_WINDOW_EXCEEDED';
|
|
44
|
+
export const QUOTA_EXCEEDED_CODE = 'QUOTA';
|
|
45
|
+
export const EMPTY_RESPONSE_CODE = 'EMPTY_RESPONSE';
|
|
46
|
+
export const INVALID_CREDENTIAL_CODE = 'INVALID_CREDENTIAL';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Exhaustiveness helper (dsh never.ts, transcribed verbatim): a value that
|
|
50
|
+
* escaped its closed union throws with diagnostics at runtime. Consumed by
|
|
51
|
+
* the vendored dsh-tools schema layer.
|
|
52
|
+
*/
|
|
53
|
+
export function assertNever(value, context) {
|
|
54
|
+
const rendered = JSON.stringify(value) ?? String(value);
|
|
55
|
+
throw new Error(`unreachable variant${context ? ` in ${context}` : ''}: ${rendered}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const STRUCTURED_CONTEXT_OVERFLOW = new RegExp(
|
|
59
|
+
String.raw`(?:^|[^a-z0-9])context[\s_-](?:length|window)[\s_-]`
|
|
60
|
+
+ String.raw`(?:exceed(?:ed|s)?|overflow(?:ed)?|limit[\s_-]exceeded)(?:$|[^a-z0-9])`,
|
|
61
|
+
'i',
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const TOO_LARGE_FOR_CONTEXT = new RegExp(
|
|
65
|
+
String.raw`\b(?:request|prompt|input|messages?)\s+(?:is\s+|are\s+)?`
|
|
66
|
+
+ String.raw`too\s+(?:large|long)\s+for\s+(?:(?:this|the)\s+)?`
|
|
67
|
+
+ String.raw`(?:model(?:'s)?\s+)?context(?:\s+window)?\b`,
|
|
68
|
+
'i',
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const EXCEEDS_MODEL_CONTEXT = new RegExp(
|
|
72
|
+
String.raw`\b(?:input|prompt|request|messages?)\b.{0,40}`
|
|
73
|
+
+ String.raw`\b(?:exceed(?:s|ed)?|overflows?|is\s+larger\s+than)\b.{0,40}`
|
|
74
|
+
+ String.raw`\b(?:the\s+)?(?:model(?:'s)?\s+)?context(?:\s+(?:length|window))?\b`,
|
|
75
|
+
'i',
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
/** Recognize context-overflow wording (code/type/message joined into one string). */
|
|
79
|
+
export function isContextWindowExceededError(detail) {
|
|
80
|
+
return STRUCTURED_CONTEXT_OVERFLOW.test(detail)
|
|
81
|
+
|| /\b(?:maximum|max)(?:\s+(?:allowed|supported))?\s+context\s+(?:length|window)\b/i.test(detail)
|
|
82
|
+
|| TOO_LARGE_FOR_CONTEXT.test(detail)
|
|
83
|
+
|| /\b(?:input|prompt|request)\s+(?:is\s+)?too\s+(?:long|large)\s+for\s+(?:this|the)\s+model\b/i.test(detail)
|
|
84
|
+
|| EXCEEDS_MODEL_CONTEXT.test(detail);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Recognize terminal quota/balance wording, as opposed to a transient rate limit. */
|
|
88
|
+
export function isQuotaExceededError(detail) {
|
|
89
|
+
return /\binsufficient[\s_-]+(?:quota|balance|credits?)\b/i.test(detail)
|
|
90
|
+
|| /\b(?:quota|usage[\s_-]+limit)[\s_-]+(?:exceeded|exhausted|reached)\b/i.test(detail)
|
|
91
|
+
|| /\bexceed(?:ed|s)?[\s_-]+(?:(?:your|the)[\s_-]+)?(?:current[\s_-]+)?quota\b/i.test(detail)
|
|
92
|
+
|| /\b(?:balance|credits?)[\s_-]+(?:exhausted|depleted)\b/i.test(detail)
|
|
93
|
+
|| /\bout[\s_-]+of[\s_-]+(?:credits?|budget)\b/i.test(detail);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Typed error for LLM failures: validated provider facts beside the live
|
|
98
|
+
* Error (dsh index.ts, constructor contract preserved — bad arguments throw).
|
|
99
|
+
*/
|
|
100
|
+
export class LlmError extends HarnessError {
|
|
101
|
+
constructor(message, code, options) {
|
|
102
|
+
if (typeof message !== 'string' || message.length === 0) {
|
|
103
|
+
throw new Error('LlmError message must be a non-empty string');
|
|
104
|
+
}
|
|
105
|
+
if (typeof code !== 'string' || code.length === 0) {
|
|
106
|
+
throw new Error('LlmError code must be a non-empty string');
|
|
107
|
+
}
|
|
108
|
+
if (options?.status !== undefined
|
|
109
|
+
&& (!Number.isInteger(options.status) || options.status < 100 || options.status > 599)) {
|
|
110
|
+
throw new Error('LlmError status must be an integer from 100 through 599');
|
|
111
|
+
}
|
|
112
|
+
if (options?.providerRetryAfterMs !== undefined
|
|
113
|
+
&& (!Number.isFinite(options.providerRetryAfterMs) || options.providerRetryAfterMs <= 0)) {
|
|
114
|
+
throw new Error('LlmError providerRetryAfterMs must be a positive finite number');
|
|
115
|
+
}
|
|
116
|
+
if (options?.requestId !== undefined
|
|
117
|
+
&& (typeof options.requestId !== 'string' || options.requestId.length === 0)) {
|
|
118
|
+
throw new Error('LlmError requestId must be a non-empty string');
|
|
119
|
+
}
|
|
120
|
+
super(message, code, options);
|
|
121
|
+
this.name = 'LlmError';
|
|
122
|
+
this.failure = Object.freeze({
|
|
123
|
+
message,
|
|
124
|
+
code,
|
|
125
|
+
...(options?.status === undefined ? {} : { status: options.status }),
|
|
126
|
+
...(options?.providerRetryAfterMs === undefined
|
|
127
|
+
? {}
|
|
128
|
+
: { providerRetryAfterMs: options.providerRetryAfterMs }),
|
|
129
|
+
...(options?.requestId === undefined ? {} : { requestId: options.requestId }),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---- api keys (dsh api-key.ts + assertUsableApiKey) ----
|
|
135
|
+
|
|
136
|
+
const LEGAL_API_KEY = /^[\x21-\x7E]+$/;
|
|
137
|
+
|
|
138
|
+
/** Judge one supplied API key, trimming surrounding whitespace first. */
|
|
139
|
+
export function normalizeApiKey(raw) {
|
|
140
|
+
const value = raw.trim();
|
|
141
|
+
if (value.length === 0) return { ok: false, reason: 'empty' };
|
|
142
|
+
if (!LEGAL_API_KEY.test(value)) return { ok: false, reason: 'illegalCharacters' };
|
|
143
|
+
return { ok: true, value };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Accept one supplied credential or refuse it as unusable (never echoes the value). */
|
|
147
|
+
export function assertUsableApiKey(raw, pkg, ref) {
|
|
148
|
+
const checked = normalizeApiKey(raw);
|
|
149
|
+
if (checked.ok) return checked.value;
|
|
150
|
+
throw new LlmError(
|
|
151
|
+
checked.reason === 'empty'
|
|
152
|
+
? `${pkg}: the API key resolved from ${ref} is blank; set ${ref} to the raw key`
|
|
153
|
+
+ ' (the web Models page writes it) or export it in the launching environment'
|
|
154
|
+
: `${pkg}: the API key resolved from ${ref} contains characters no HTTP header can carry;`
|
|
155
|
+
+ ` set ${ref} to the raw key alone (the web Models page writes it)`,
|
|
156
|
+
INVALID_CREDENTIAL_CODE,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ---- attribution (dsh attribution.ts; embedded identity, fixed version) ----
|
|
161
|
+
|
|
162
|
+
export const APP_IDENTITY = {
|
|
163
|
+
product: 'deepseek-harness',
|
|
164
|
+
version: '0.0.0-rebon-embedded',
|
|
165
|
+
url: 'https://github.com/deepseek-ai/deepseek-harness',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export function userAgent(identity = APP_IDENTITY) {
|
|
169
|
+
return `${identity.product}/${identity.version} (+${identity.url})`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function attributionHeaders(identity = APP_IDENTITY) {
|
|
173
|
+
return { 'user-agent': userAgent(identity) };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---- branded ids (dsh brand.ts: brand = identity at runtime) ----
|
|
177
|
+
|
|
178
|
+
export function MessageId(id) {
|
|
179
|
+
return id;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function CallId(id) {
|
|
183
|
+
return id;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function ProviderRequestId(id) {
|
|
187
|
+
return id;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function ReasoningEffortId(id) {
|
|
191
|
+
return id;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---- content helpers (dsh content.ts) ----
|
|
195
|
+
|
|
196
|
+
/** True when typed model content contains an image block, walking tool results. */
|
|
197
|
+
export function contentHasImage(content) {
|
|
198
|
+
return content.some((block) => block.type === 'image'
|
|
199
|
+
|| (block.type === 'tool-result' && contentHasImage(block.content)));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ---- retry policy (dsh retry-policy.ts, transcribed) ----
|
|
203
|
+
|
|
204
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
205
|
+
const DEFAULT_INITIAL_DELAY_MS = 500;
|
|
206
|
+
const DEFAULT_MAX_DELAY_MS = 10_000;
|
|
207
|
+
const DEFAULT_JITTER_RATIO = 0.1;
|
|
208
|
+
const DEFAULT_RETRYABLE_CODES = Object.freeze([
|
|
209
|
+
EMPTY_RESPONSE_CODE,
|
|
210
|
+
'RATE_LIMIT',
|
|
211
|
+
'SERVER',
|
|
212
|
+
'TIMEOUT',
|
|
213
|
+
'TRANSPORT',
|
|
214
|
+
]);
|
|
215
|
+
|
|
216
|
+
const backoffSchema = z.object({
|
|
217
|
+
initialDelayMs: z.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_INITIAL_DELAY_MS),
|
|
218
|
+
maxDelayMs: z.number().max(MAX_TIMER_DELAY_MS).default(DEFAULT_MAX_DELAY_MS),
|
|
219
|
+
jitterRatio: z.number().min(0).max(1).default(DEFAULT_JITTER_RATIO),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const normalPolicySchema = z.object({
|
|
223
|
+
mode: z.const('normal').required(),
|
|
224
|
+
maxRetries: z.number().step(1).min(0).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_MAX_RETRIES),
|
|
225
|
+
retryableCodes: z.array(z.string()).default([...DEFAULT_RETRYABLE_CODES]),
|
|
226
|
+
backoff: backoffSchema,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const alwaysPolicySchema = z.object({
|
|
230
|
+
mode: z.const('always').required(),
|
|
231
|
+
backoff: backoffSchema,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
/** Cordis schema embedded by each concrete provider configuration. */
|
|
235
|
+
export const RetryPolicySchema = z.union([normalPolicySchema, alwaysPolicySchema]);
|
|
236
|
+
|
|
237
|
+
const NORMAL_POLICY_KEYS = new Set(['mode', 'maxRetries', 'retryableCodes', 'backoff']);
|
|
238
|
+
const ALWAYS_POLICY_KEYS = new Set(['mode', 'backoff']);
|
|
239
|
+
const BACKOFF_KEYS = new Set(['initialDelayMs', 'maxDelayMs', 'jitterRatio']);
|
|
240
|
+
|
|
241
|
+
function validateKeys(value, allowed, path) {
|
|
242
|
+
for (const key of Object.keys(value)) {
|
|
243
|
+
if (!allowed.has(key)) throw new Error(`${path}: unknown key "${key}"`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function resolveBackoff(config, path) {
|
|
248
|
+
if (config !== undefined) validateKeys(config, BACKOFF_KEYS, path);
|
|
249
|
+
const initialDelayMs = config?.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
|
|
250
|
+
const maxDelayMs = config?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
|
|
251
|
+
const jitterRatio = config?.jitterRatio ?? DEFAULT_JITTER_RATIO;
|
|
252
|
+
|
|
253
|
+
if (!Number.isFinite(initialDelayMs) || initialDelayMs <= 0 || initialDelayMs > MAX_TIMER_DELAY_MS) {
|
|
254
|
+
throw new Error(`${path}.initialDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
255
|
+
}
|
|
256
|
+
if (!Number.isFinite(maxDelayMs) || maxDelayMs <= 0 || maxDelayMs > MAX_TIMER_DELAY_MS) {
|
|
257
|
+
throw new Error(`${path}.maxDelayMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
|
|
258
|
+
}
|
|
259
|
+
if (initialDelayMs > maxDelayMs) {
|
|
260
|
+
throw new Error(`${path}.initialDelayMs must be less than or equal to maxDelayMs`);
|
|
261
|
+
}
|
|
262
|
+
if (!Number.isFinite(jitterRatio) || jitterRatio < 0 || jitterRatio > 1) {
|
|
263
|
+
throw new Error(`${path}.jitterRatio must be between 0 and 1`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return Object.freeze({ initialDelayMs, maxDelayMs, jitterRatio });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Validate, default, and detach one provider-owned retry policy. */
|
|
270
|
+
export function resolveRetryPolicy(config, path) {
|
|
271
|
+
if (config === undefined) {
|
|
272
|
+
return Object.freeze({
|
|
273
|
+
mode: 'normal',
|
|
274
|
+
maxRetries: DEFAULT_MAX_RETRIES,
|
|
275
|
+
retryableCodes: DEFAULT_RETRYABLE_CODES,
|
|
276
|
+
...resolveBackoff(undefined, `${path}.backoff`),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
switch (config.mode) {
|
|
281
|
+
case 'normal': {
|
|
282
|
+
validateKeys(config, NORMAL_POLICY_KEYS, path);
|
|
283
|
+
const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
284
|
+
const retryableCodes = config.retryableCodes ?? [...DEFAULT_RETRYABLE_CODES];
|
|
285
|
+
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0) {
|
|
286
|
+
throw new Error(`${path}.maxRetries must be a non-negative safe integer`);
|
|
287
|
+
}
|
|
288
|
+
if (retryableCodes.length === 0) {
|
|
289
|
+
throw new Error(`${path}.retryableCodes must not be empty`);
|
|
290
|
+
}
|
|
291
|
+
if (retryableCodes.some((code) => typeof code !== 'string' || code.length === 0)) {
|
|
292
|
+
throw new Error(`${path}.retryableCodes must contain only non-empty strings`);
|
|
293
|
+
}
|
|
294
|
+
if (new Set(retryableCodes).size !== retryableCodes.length) {
|
|
295
|
+
throw new Error(`${path}.retryableCodes must not contain duplicates`);
|
|
296
|
+
}
|
|
297
|
+
return Object.freeze({
|
|
298
|
+
mode: 'normal',
|
|
299
|
+
maxRetries,
|
|
300
|
+
retryableCodes: Object.freeze([...retryableCodes]),
|
|
301
|
+
...resolveBackoff(config.backoff, `${path}.backoff`),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
case 'always':
|
|
305
|
+
validateKeys(config, ALWAYS_POLICY_KEYS, path);
|
|
306
|
+
return Object.freeze({
|
|
307
|
+
mode: 'always',
|
|
308
|
+
...resolveBackoff(config.backoff, `${path}.backoff`),
|
|
309
|
+
});
|
|
310
|
+
default:
|
|
311
|
+
throw new Error(`${path}.mode must be "normal" or "always"`);
|
|
312
|
+
}
|
|
313
|
+
}
|