@pixelbyte-software/pixcode 1.33.3 → 1.33.5
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/dist/assets/{index-BrB-LHeY.js → index-B2vlFsqI.js} +5 -5
- package/dist/index.html +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +46 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +2 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/opencode-cli.js +22 -6
- package/dist-server/server/opencode-cli.js.map +1 -1
- package/dist-server/server/opencode-response-handler.js +19 -9
- package/dist-server/server/opencode-response-handler.js.map +1 -1
- package/dist-server/server/services/provider-models.js +117 -3
- package/dist-server/server/services/provider-models.js.map +1 -1
- package/dist-server/shared/modelConstants.js +30 -11
- package/dist-server/shared/modelConstants.js.map +1 -1
- package/package.json +178 -178
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +39 -1
- package/server/modules/providers/provider.routes.ts +2 -1
- package/server/opencode-cli.js +417 -403
- package/server/opencode-response-handler.js +100 -92
- package/server/services/provider-models.js +114 -3
- package/shared/modelConstants.js +34 -11
|
@@ -1,92 +1,100 @@
|
|
|
1
|
-
// OpenCode Response Handler — `opencode run --format json` parser.
|
|
2
|
-
//
|
|
3
|
-
// The JSON format streams one event per line (NDJSON). Event shapes follow
|
|
4
|
-
// the OpenAPI contract exposed by `opencode serve`. We treat the stream
|
|
5
|
-
// permissively: lines that don't parse as JSON are passed through as plain
|
|
6
|
-
// text deltas (covers OpenCode's pre-stream banner output and any debug
|
|
7
|
-
// noise the CLI emits to stdout).
|
|
8
|
-
import { sessionsService } from './modules/providers/services/sessions.service.js';
|
|
9
|
-
|
|
10
|
-
class OpencodeResponseHandler {
|
|
11
|
-
constructor(ws, options = {}) {
|
|
12
|
-
this.ws = ws;
|
|
13
|
-
this.buffer = '';
|
|
14
|
-
this.onContentFragment = options.onContentFragment || null;
|
|
15
|
-
this.onInit = options.onInit || null;
|
|
16
|
-
this.onToolUse = options.onToolUse || null;
|
|
17
|
-
this.onToolResult = options.onToolResult || null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
processData(data) {
|
|
21
|
-
this.buffer += data;
|
|
22
|
-
|
|
23
|
-
const lines = this.buffer.split('\n');
|
|
24
|
-
this.buffer = lines.pop() || '';
|
|
25
|
-
|
|
26
|
-
for (const line of lines) {
|
|
27
|
-
const trimmed = line.trim();
|
|
28
|
-
if (!trimmed) continue;
|
|
29
|
-
try {
|
|
30
|
-
const event = JSON.parse(trimmed);
|
|
31
|
-
this.handleEvent(event);
|
|
32
|
-
} catch {
|
|
33
|
-
// Non-JSON line — surface as plain text delta so the user sees CLI
|
|
34
|
-
// banners / status messages instead of swallowing them silently.
|
|
35
|
-
if (this.onContentFragment) this.onContentFragment(trimmed + '\n');
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
handleEvent(event) {
|
|
41
|
-
const sid = typeof this.ws.getSessionId === 'function' ? this.ws.getSessionId() : null;
|
|
42
|
-
|
|
43
|
-
if (event.type === 'init' || event.type === 'session.start') {
|
|
44
|
-
if (this.onInit) this.onInit(event);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// OpenCode emits
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (this.
|
|
64
|
-
tool_id:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
1
|
+
// OpenCode Response Handler — `opencode run --format json` parser.
|
|
2
|
+
//
|
|
3
|
+
// The JSON format streams one event per line (NDJSON). Event shapes follow
|
|
4
|
+
// the OpenAPI contract exposed by `opencode serve`. We treat the stream
|
|
5
|
+
// permissively: lines that don't parse as JSON are passed through as plain
|
|
6
|
+
// text deltas (covers OpenCode's pre-stream banner output and any debug
|
|
7
|
+
// noise the CLI emits to stdout).
|
|
8
|
+
import { sessionsService } from './modules/providers/services/sessions.service.js';
|
|
9
|
+
|
|
10
|
+
class OpencodeResponseHandler {
|
|
11
|
+
constructor(ws, options = {}) {
|
|
12
|
+
this.ws = ws;
|
|
13
|
+
this.buffer = '';
|
|
14
|
+
this.onContentFragment = options.onContentFragment || null;
|
|
15
|
+
this.onInit = options.onInit || null;
|
|
16
|
+
this.onToolUse = options.onToolUse || null;
|
|
17
|
+
this.onToolResult = options.onToolResult || null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
processData(data) {
|
|
21
|
+
this.buffer += data;
|
|
22
|
+
|
|
23
|
+
const lines = this.buffer.split('\n');
|
|
24
|
+
this.buffer = lines.pop() || '';
|
|
25
|
+
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const trimmed = line.trim();
|
|
28
|
+
if (!trimmed) continue;
|
|
29
|
+
try {
|
|
30
|
+
const event = JSON.parse(trimmed);
|
|
31
|
+
this.handleEvent(event);
|
|
32
|
+
} catch {
|
|
33
|
+
// Non-JSON line — surface as plain text delta so the user sees CLI
|
|
34
|
+
// banners / status messages instead of swallowing them silently.
|
|
35
|
+
if (this.onContentFragment) this.onContentFragment(trimmed + '\n');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
handleEvent(event) {
|
|
41
|
+
const sid = typeof this.ws.getSessionId === 'function' ? this.ws.getSessionId() : null;
|
|
42
|
+
|
|
43
|
+
if (event.type === 'init' || event.type === 'session.start') {
|
|
44
|
+
if (this.onInit) this.onInit(event);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// OpenCode emits assistant text as either a top-level `text` event
|
|
49
|
+
// (current `--format json` shape: `{ type: "text", part: { text } }`)
|
|
50
|
+
// or as `message`/`part` legacy shapes from earlier builds. Cover all
|
|
51
|
+
// three so we don't drop tokens on a CLI version we haven't matched.
|
|
52
|
+
if (event.type === 'text' && event.part && typeof event.part.text === 'string') {
|
|
53
|
+
if (this.onContentFragment && event.part.text) this.onContentFragment(event.part.text);
|
|
54
|
+
} else if (event.type === 'message' && event.role === 'assistant') {
|
|
55
|
+
const content = event.content || event.text || '';
|
|
56
|
+
if (this.onContentFragment && content) this.onContentFragment(content);
|
|
57
|
+
} else if (event.type === 'part' && event.part_type === 'text') {
|
|
58
|
+
const content = event.text || event.content || '';
|
|
59
|
+
if (this.onContentFragment && content) this.onContentFragment(content);
|
|
60
|
+
} else if (event.type === 'tool_use' || event.type === 'tool-use' || event.type === 'tool.start') {
|
|
61
|
+
// Tool-use shape on `--format json`: `{ type:"tool_use", part:{ callID, tool, state:{ input } } }`
|
|
62
|
+
const part = event.part || event;
|
|
63
|
+
if (this.onToolUse) this.onToolUse({
|
|
64
|
+
tool_id: part.callID || part.id || event.tool_id,
|
|
65
|
+
tool_name: part.tool || part.name || event.tool_name,
|
|
66
|
+
parameters: part.state?.input || part.input || event.parameters || {},
|
|
67
|
+
});
|
|
68
|
+
} else if (event.type === 'tool_result' || event.type === 'tool-result' || event.type === 'tool.end') {
|
|
69
|
+
const part = event.part || event;
|
|
70
|
+
const state = part.state || {};
|
|
71
|
+
if (this.onToolResult) this.onToolResult({
|
|
72
|
+
tool_id: part.callID || part.id || event.tool_id,
|
|
73
|
+
output: state.output ?? part.output ?? event.output ?? event.result ?? '',
|
|
74
|
+
status: state.status || event.status || (event.isError ? 'error' : 'ok'),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const normalized = sessionsService.normalizeMessage('opencode', event, sid);
|
|
79
|
+
for (const msg of normalized) {
|
|
80
|
+
this.ws.send(msg);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
forceFlush() {
|
|
85
|
+
if (this.buffer.trim()) {
|
|
86
|
+
try {
|
|
87
|
+
const event = JSON.parse(this.buffer);
|
|
88
|
+
this.handleEvent(event);
|
|
89
|
+
} catch {
|
|
90
|
+
if (this.onContentFragment) this.onContentFragment(this.buffer);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
destroy() {
|
|
96
|
+
this.buffer = '';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default OpencodeResponseHandler;
|
|
@@ -71,7 +71,9 @@ function normalizeList(list) {
|
|
|
71
71
|
seen.add(value);
|
|
72
72
|
const label = typeof item.label === 'string' && item.label.trim() ? item.label.trim() : value;
|
|
73
73
|
const source = item.source === 'api' ? 'api' : 'static';
|
|
74
|
-
|
|
74
|
+
const entry = { value, label, source };
|
|
75
|
+
if (typeof item.free === 'boolean') entry.free = item.free;
|
|
76
|
+
out.push(entry);
|
|
75
77
|
}
|
|
76
78
|
return out;
|
|
77
79
|
}
|
|
@@ -123,6 +125,101 @@ async function discoverOpenAiCompat(apiKey, baseUrl, fallbackBase) {
|
|
|
123
125
|
}));
|
|
124
126
|
}
|
|
125
127
|
|
|
128
|
+
/**
|
|
129
|
+
* OpenCode is multi-provider — its "model" picker isn't a single API list,
|
|
130
|
+
* it's the union of every provider it can route to (Anthropic, OpenAI,
|
|
131
|
+
* Google, xAI, OpenRouter, OpenCode Zen, Ollama, etc.). The canonical
|
|
132
|
+
* catalog lives at https://models.dev/api.json (no auth, ~1.8 MB JSON, 115
|
|
133
|
+
* providers as of 2026-04). We pull that, filter to providers the user
|
|
134
|
+
* has authenticated with (read `~/.local/share/opencode/auth.json`) plus
|
|
135
|
+
* always include the OpenCode Zen tier (works without explicit auth on
|
|
136
|
+
* the free models), drop deprecated entries, and tag free models.
|
|
137
|
+
*/
|
|
138
|
+
async function discoverOpencode() {
|
|
139
|
+
const url = process.env.OPENCODE_MODELS_URL || 'https://models.dev/api.json';
|
|
140
|
+
const response = await fetch(url, {
|
|
141
|
+
// OpenCode itself caches this for hours; we cache for 6h via the
|
|
142
|
+
// outer wrapper so a single 7s fetch on cold start is acceptable.
|
|
143
|
+
signal: AbortSignal.timeout(15000),
|
|
144
|
+
});
|
|
145
|
+
if (!response.ok) throw new Error(`models.dev/api.json returned ${response.status}`);
|
|
146
|
+
const data = await response.json();
|
|
147
|
+
if (!data || typeof data !== 'object') throw new Error('models.dev returned a non-object payload');
|
|
148
|
+
|
|
149
|
+
// Read OpenCode's auth.json to know which providers the user can
|
|
150
|
+
// actually call. Missing file → only show always-free Zen.
|
|
151
|
+
const authedProviders = new Set(['opencode']);
|
|
152
|
+
try {
|
|
153
|
+
const authPath = path.join(os.homedir(), '.local', 'share', 'opencode', 'auth.json');
|
|
154
|
+
const raw = await fs.readFile(authPath, 'utf8');
|
|
155
|
+
const auth = JSON.parse(raw);
|
|
156
|
+
if (auth && typeof auth === 'object') {
|
|
157
|
+
for (const k of Object.keys(auth)) authedProviders.add(k);
|
|
158
|
+
}
|
|
159
|
+
} catch { /* no auth.json → only Zen free models surface */ }
|
|
160
|
+
|
|
161
|
+
// Common env-var providers OpenCode picks up automatically. If the user
|
|
162
|
+
// exported one in their shell, surface those models too even without
|
|
163
|
+
// auth.json. Mirrors the env list in opencode-auth.provider.ts.
|
|
164
|
+
const envProviderHints = {
|
|
165
|
+
anthropic: ['ANTHROPIC_API_KEY'],
|
|
166
|
+
openai: ['OPENAI_API_KEY'],
|
|
167
|
+
google: ['GOOGLE_GENERATIVE_AI_API_KEY', 'GEMINI_API_KEY'],
|
|
168
|
+
'google-vertex': ['GOOGLE_APPLICATION_CREDENTIALS'],
|
|
169
|
+
xai: ['XAI_API_KEY'],
|
|
170
|
+
groq: ['GROQ_API_KEY'],
|
|
171
|
+
cerebras: ['CEREBRAS_API_KEY'],
|
|
172
|
+
openrouter: ['OPENROUTER_API_KEY'],
|
|
173
|
+
};
|
|
174
|
+
for (const [providerId, envVars] of Object.entries(envProviderHints)) {
|
|
175
|
+
if (envVars.some((v) => process.env[v]?.trim())) authedProviders.add(providerId);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const out = [];
|
|
179
|
+
for (const [providerId, providerCfg] of Object.entries(data)) {
|
|
180
|
+
if (!authedProviders.has(providerId)) continue;
|
|
181
|
+
if (!providerCfg || typeof providerCfg !== 'object') continue;
|
|
182
|
+
const models = providerCfg.models;
|
|
183
|
+
if (!models || typeof models !== 'object') continue;
|
|
184
|
+
|
|
185
|
+
const providerName = typeof providerCfg.name === 'string' && providerCfg.name.trim()
|
|
186
|
+
? providerCfg.name
|
|
187
|
+
: providerId;
|
|
188
|
+
|
|
189
|
+
for (const [modelId, modelCfg] of Object.entries(models)) {
|
|
190
|
+
if (!modelCfg || typeof modelCfg !== 'object') continue;
|
|
191
|
+
// Skip deprecated entries from the default list — users can
|
|
192
|
+
// still hand-type them if they really need to.
|
|
193
|
+
if (modelCfg.status === 'deprecated') continue;
|
|
194
|
+
const cost = modelCfg.cost && typeof modelCfg.cost === 'object' ? modelCfg.cost : null;
|
|
195
|
+
const free = !cost || (Number(cost.input) === 0 && Number(cost.output) === 0);
|
|
196
|
+
const ctx = modelCfg.limit?.context;
|
|
197
|
+
const ctxLabel = typeof ctx === 'number' && ctx > 0
|
|
198
|
+
? ` · ${ctx >= 1_000_000 ? `${(ctx / 1_000_000).toFixed(1)}M` : `${Math.round(ctx / 1000)}K`}`
|
|
199
|
+
: '';
|
|
200
|
+
const freeLabel = free ? ' · Free' : '';
|
|
201
|
+
const modelName = typeof modelCfg.name === 'string' && modelCfg.name.trim()
|
|
202
|
+
? modelCfg.name
|
|
203
|
+
: modelId;
|
|
204
|
+
|
|
205
|
+
out.push({
|
|
206
|
+
value: `${providerId}/${modelId}`,
|
|
207
|
+
label: `${providerName} · ${modelName}${ctxLabel}${freeLabel}`,
|
|
208
|
+
source: 'api',
|
|
209
|
+
free,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Sort: free first (handy when the user is unauthed), then by label.
|
|
215
|
+
out.sort((a, b) => {
|
|
216
|
+
if (a.free !== b.free) return a.free ? -1 : 1;
|
|
217
|
+
return a.label.localeCompare(b.label);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
|
|
126
223
|
async function discoverGoogle(apiKey) {
|
|
127
224
|
// Google Generative Language API — public models list, API key as query.
|
|
128
225
|
const endpoint = `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(apiKey)}`;
|
|
@@ -165,6 +262,22 @@ export async function getProviderModels(provider, opts = {}) {
|
|
|
165
262
|
};
|
|
166
263
|
}
|
|
167
264
|
|
|
265
|
+
// OpenCode is the odd one out: its catalog is models.dev, not a per-key
|
|
266
|
+
// API endpoint. Skip the credential plumbing and dispatch straight.
|
|
267
|
+
let liveModels = [];
|
|
268
|
+
let error;
|
|
269
|
+
if (provider === 'opencode') {
|
|
270
|
+
try {
|
|
271
|
+
liveModels = await discoverOpencode();
|
|
272
|
+
} catch (err) {
|
|
273
|
+
error = err?.message || String(err);
|
|
274
|
+
}
|
|
275
|
+
const merged = mergeCatalogs(normalizeList(liveModels), staticCatalog);
|
|
276
|
+
const entry = { models: merged, error };
|
|
277
|
+
await saveCacheEntry(provider, entry).catch(() => { /* non-fatal */ });
|
|
278
|
+
return { models: merged, fetchedAt: new Date().toISOString(), error, fromCache: false };
|
|
279
|
+
}
|
|
280
|
+
|
|
168
281
|
// Pick up credentials from Pixcode's UI store first, then fall back to
|
|
169
282
|
// the native env vars so a user who already exported ANTHROPIC_API_KEY
|
|
170
283
|
// (or authenticated Claude Code via OAuth — the SDK writes the key into
|
|
@@ -185,8 +298,6 @@ export async function getProviderModels(provider, opts = {}) {
|
|
|
185
298
|
const apiKey = creds?.apiKey || envKey;
|
|
186
299
|
const baseUrl = creds?.baseUrl || envBase || undefined;
|
|
187
300
|
|
|
188
|
-
let liveModels = [];
|
|
189
|
-
let error;
|
|
190
301
|
if (!apiKey) {
|
|
191
302
|
// Be explicit so the UI can surface a useful hint rather than just
|
|
192
303
|
// showing the static baseline with no reason given.
|
package/shared/modelConstants.js
CHANGED
|
@@ -95,25 +95,48 @@ export const QWEN_MODELS = {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
* OpenCode Models
|
|
98
|
+
* OpenCode Models — STATIC FALLBACK ONLY.
|
|
99
99
|
*
|
|
100
|
-
* OpenCode is multi-provider
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
100
|
+
* OpenCode is multi-provider and the live model catalog rotates often
|
|
101
|
+
* (Zen free models come and go; new Anthropic/OpenAI/Google models ship
|
|
102
|
+
* every few weeks). The runtime fetches the live catalog from
|
|
103
|
+
* `https://models.dev/api.json` via server/services/provider-models.js
|
|
104
|
+
* and merges it on top of this list — these entries only show when the
|
|
105
|
+
* fetch fails (offline install, firewalled host).
|
|
106
|
+
*
|
|
107
|
+
* Curated current free Zen tier + canonical paid picks. Update when the
|
|
108
|
+
* fallback feels stale, but the live fetch is the source of truth.
|
|
105
109
|
*/
|
|
106
110
|
export const OPENCODE_MODELS = {
|
|
107
111
|
OPTIONS: [
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
// OpenCode Zen — free tier (no charge, may rate-limit). The "limited
|
|
113
|
+
// time" Zen freebies rotate, so this is the safest small set.
|
|
114
|
+
{ value: "opencode/big-pickle", label: "OpenCode Zen · Big Pickle (Free)", free: true },
|
|
115
|
+
{ value: "opencode/minimax-m2.5-free", label: "OpenCode Zen · MiniMax M2.5 (Free)", free: true },
|
|
116
|
+
{ value: "opencode/hy3-preview-free", label: "OpenCode Zen · Hy3 Preview (Free)", free: true },
|
|
117
|
+
{ value: "opencode/ling-2.6-flash-free", label: "OpenCode Zen · Ling 2.6 Flash (Free)", free: true },
|
|
118
|
+
{ value: "opencode/nemotron-3-super-free", label: "OpenCode Zen · Nemotron 3 Super (Free)", free: true },
|
|
119
|
+
{ value: "opencode/gpt-5-nano", label: "OpenCode Zen · GPT-5 Nano (Free)", free: true },
|
|
120
|
+
|
|
121
|
+
// Anthropic — current flagship + cheap.
|
|
122
|
+
{ value: "anthropic/claude-opus-4-7", label: "Claude Opus 4.7 (Anthropic)" },
|
|
110
123
|
{ value: "anthropic/claude-sonnet-4-6", label: "Claude Sonnet 4.6 (Anthropic)" },
|
|
111
|
-
{ value: "anthropic/claude-
|
|
124
|
+
{ value: "anthropic/claude-haiku-4-5", label: "Claude Haiku 4.5 (Anthropic)" },
|
|
125
|
+
|
|
126
|
+
// OpenAI — current GPT-5 family.
|
|
112
127
|
{ value: "openai/gpt-5.4", label: "GPT-5.4 (OpenAI)" },
|
|
113
|
-
{ value: "
|
|
128
|
+
{ value: "openai/gpt-5.1-codex", label: "GPT-5.1 Codex (OpenAI)" },
|
|
129
|
+
|
|
130
|
+
// Google — current Gemini 3 family.
|
|
131
|
+
{ value: "google/gemini-3.1-pro-preview", label: "Gemini 3.1 Pro (Google)" },
|
|
132
|
+
{ value: "google/gemini-3-flash-preview", label: "Gemini 3 Flash (Google)" },
|
|
133
|
+
|
|
134
|
+
// xAI — fast & cheap coding model.
|
|
135
|
+
{ value: "xai/grok-code-fast-1", label: "Grok Code Fast 1 (xAI)" },
|
|
114
136
|
],
|
|
115
137
|
|
|
116
|
-
|
|
138
|
+
// Free Zen freebie that historically works for unauthed installs.
|
|
139
|
+
DEFAULT: "opencode/big-pickle",
|
|
117
140
|
};
|
|
118
141
|
|
|
119
142
|
/**
|