@steve31415/baselib 3.40.0 → 3.41.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/dist/llm/complete.js +20 -0
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/models.d.ts +7 -0
- package/dist/llm/models.js +23 -0
- package/dist/llm/providers/anthropic.js +14 -1
- package/dist/llm/providers/gemini.js +8 -1
- package/dist/llm/providers/openai.js +14 -1
- package/dist/llm/types.d.ts +16 -0
- package/dist/log-core.js +18 -1
- package/package.json +1 -1
package/dist/llm/complete.js
CHANGED
|
@@ -39,6 +39,19 @@ export async function llmComplete(request) {
|
|
|
39
39
|
isRetryable: false,
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
+
// A request with images may only run on models that take them. The
|
|
43
|
+
// requested model not taking them is the caller's bug, so say so rather
|
|
44
|
+
// than silently answering from a fallback; fallbacks without image input
|
|
45
|
+
// are skipped in the loop below.
|
|
46
|
+
const hasImages = (request.images?.length ?? 0) > 0;
|
|
47
|
+
if (hasImages && !entry.supportsImages) {
|
|
48
|
+
throw new LLMError({
|
|
49
|
+
message: `Model ${request.model} does not accept image input. Image-capable models: ${Object.keys(MODEL_REGISTRY).filter((m) => MODEL_REGISTRY[m].supportsImages).join(', ')}`,
|
|
50
|
+
provider: entry.provider,
|
|
51
|
+
model: entry.apiModelId,
|
|
52
|
+
isRetryable: false,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
42
55
|
const maxTokens = request.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
43
56
|
const temperature = request.temperature ?? DEFAULT_TEMPERATURE;
|
|
44
57
|
const timeoutMs = request.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
@@ -75,6 +88,12 @@ export async function llmComplete(request) {
|
|
|
75
88
|
const modelEntry = MODEL_REGISTRY[modelName];
|
|
76
89
|
if (!modelEntry)
|
|
77
90
|
continue;
|
|
91
|
+
if (hasImages && !modelEntry.supportsImages) {
|
|
92
|
+
request.logger?.debug('llm.skip_provider', {
|
|
93
|
+
model: modelName, provider: modelEntry.provider, reason: 'no image input',
|
|
94
|
+
});
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
78
97
|
const apiKey = request.apiKeys[modelEntry.provider];
|
|
79
98
|
if (!apiKey) {
|
|
80
99
|
request.logger?.debug('llm.skip_provider', {
|
|
@@ -88,6 +107,7 @@ export async function llmComplete(request) {
|
|
|
88
107
|
const result = await provider.complete({
|
|
89
108
|
apiModelId: modelEntry.apiModelId,
|
|
90
109
|
prompt: request.prompt,
|
|
110
|
+
images: hasImages ? request.images : undefined,
|
|
91
111
|
systemPrompt: request.systemPrompt,
|
|
92
112
|
maxTokens,
|
|
93
113
|
temperature,
|
package/dist/llm/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { llmComplete } from './complete.js';
|
|
2
2
|
export { MODEL_REGISTRY } from './models.js';
|
|
3
3
|
export type { ModelEntry } from './models.js';
|
|
4
|
-
export { LLMError, EFFORT_LEVELS, type Effort, type LLMRequest, type LLMResponse, type FailedAttempt, type FinishCategory, type ProviderName, type ApiKeys, type JsonSchema, } from './types.js';
|
|
4
|
+
export { LLMError, EFFORT_LEVELS, type Effort, type LLMRequest, type LLMResponse, type FailedAttempt, type FinishCategory, type ProviderName, type ApiKeys, type JsonSchema, type LLMImage, type LLMImageMediaType, } from './types.js';
|
package/dist/llm/models.d.ts
CHANGED
|
@@ -59,6 +59,13 @@ export interface ModelEntry {
|
|
|
59
59
|
* `output_config.format` instead.
|
|
60
60
|
*/
|
|
61
61
|
supportsForcedToolChoice?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Whether this model accepts image input (`LLMRequest.images`). Defaults to
|
|
64
|
+
* `false`: a request with images is rejected for a model without it, and
|
|
65
|
+
* failover skips such fallbacks. Set only on models whose image handling
|
|
66
|
+
* was verified live (test/helpers/llm.ts records the probe).
|
|
67
|
+
*/
|
|
68
|
+
supportsImages?: boolean;
|
|
62
69
|
}
|
|
63
70
|
/**
|
|
64
71
|
* Output budget below which a `thinksByDefault` model is considered at risk of
|
package/dist/llm/models.js
CHANGED
|
@@ -26,10 +26,15 @@ export const MODEL_REGISTRY = {
|
|
|
26
26
|
// hidden reasoning tokens billed as output — hence `supportsTemperature:
|
|
27
27
|
// false` + `thinksByDefault: true` on each. (gpt-5.2 still accepts
|
|
28
28
|
// temperature; verified the same day.)
|
|
29
|
+
//
|
|
30
|
+
// `supportsImages`: every entry still served read a JPEG beside a
|
|
31
|
+
// jsonSchema in the live probe of 2026-10-29 (test/helpers/llm.ts);
|
|
32
|
+
// the shut-down gemini-2.5 pair is left unmarked.
|
|
29
33
|
// --- Fast tier ---
|
|
30
34
|
'gemini-3.5-flash-lite': {
|
|
31
35
|
provider: 'gemini',
|
|
32
36
|
apiModelId: 'gemini-3.5-flash-lite',
|
|
37
|
+
supportsImages: true,
|
|
33
38
|
fallbacks: ['gpt-5.6-luna', 'claude-haiku-4-5'],
|
|
34
39
|
// Default thinking_level is `minimal` — reasoning overhead is negligible,
|
|
35
40
|
// so no `thinksByDefault` headroom warning.
|
|
@@ -41,6 +46,7 @@ export const MODEL_REGISTRY = {
|
|
|
41
46
|
'gemini-3.6-flash': {
|
|
42
47
|
provider: 'gemini',
|
|
43
48
|
apiModelId: 'gemini-3.6-flash',
|
|
49
|
+
supportsImages: true,
|
|
44
50
|
fallbacks: ['gpt-5.6-luna', 'claude-haiku-4-5'],
|
|
45
51
|
// Thinks by default (thinking_level defaults to `medium`); reasoning
|
|
46
52
|
// tokens are billed as output and count against maxOutputTokens.
|
|
@@ -56,6 +62,7 @@ export const MODEL_REGISTRY = {
|
|
|
56
62
|
'gpt-5.6-luna': {
|
|
57
63
|
provider: 'openai',
|
|
58
64
|
apiModelId: 'gpt-5.6-luna',
|
|
65
|
+
supportsImages: true,
|
|
59
66
|
fallbacks: ['gemini-3.5-flash-lite', 'claude-haiku-4-5'],
|
|
60
67
|
supportsTemperature: false,
|
|
61
68
|
thinksByDefault: true,
|
|
@@ -63,6 +70,7 @@ export const MODEL_REGISTRY = {
|
|
|
63
70
|
'claude-haiku-4-5': {
|
|
64
71
|
provider: 'anthropic',
|
|
65
72
|
apiModelId: 'claude-haiku-4-5-20251001',
|
|
73
|
+
supportsImages: true,
|
|
66
74
|
fallbacks: ['gemini-3.5-flash-lite', 'gpt-5.6-luna'],
|
|
67
75
|
},
|
|
68
76
|
// Retiring 2026-10-16 — migrate callers to gemini-3.5-flash-lite.
|
|
@@ -74,12 +82,14 @@ export const MODEL_REGISTRY = {
|
|
|
74
82
|
'gemini-3.1-flash-lite': {
|
|
75
83
|
provider: 'gemini',
|
|
76
84
|
apiModelId: 'gemini-3.1-flash-lite',
|
|
85
|
+
supportsImages: true,
|
|
77
86
|
fallbacks: ['claude-sonnet-4-6'],
|
|
78
87
|
supportsEffort: true,
|
|
79
88
|
},
|
|
80
89
|
'gemini-3-flash-preview': {
|
|
81
90
|
provider: 'gemini',
|
|
82
91
|
apiModelId: 'gemini-3-flash-preview',
|
|
92
|
+
supportsImages: true,
|
|
83
93
|
fallbacks: ['claude-haiku-4-5', 'gpt-5.6-luna'],
|
|
84
94
|
supportsEffort: true,
|
|
85
95
|
thinkingLevels: ['minimal', 'low', 'medium', 'high'],
|
|
@@ -87,6 +97,7 @@ export const MODEL_REGISTRY = {
|
|
|
87
97
|
'gpt-4o-mini': {
|
|
88
98
|
provider: 'openai',
|
|
89
99
|
apiModelId: 'gpt-4o-mini',
|
|
100
|
+
supportsImages: true,
|
|
90
101
|
fallbacks: ['gemini-3.5-flash-lite', 'claude-haiku-4-5'],
|
|
91
102
|
},
|
|
92
103
|
// --- Mid tier ---
|
|
@@ -94,6 +105,7 @@ export const MODEL_REGISTRY = {
|
|
|
94
105
|
provider: 'anthropic',
|
|
95
106
|
supportsEffort: true,
|
|
96
107
|
apiModelId: 'claude-sonnet-5',
|
|
108
|
+
supportsImages: true,
|
|
97
109
|
fallbacks: ['gpt-5.6-terra', 'gemini-3.1-pro-preview'],
|
|
98
110
|
// Sonnet 5 rejects `temperature` (like Opus 4.7+/Fable 5). It supports
|
|
99
111
|
// adaptive thinking but does NOT think when the request omits `thinking`
|
|
@@ -103,6 +115,7 @@ export const MODEL_REGISTRY = {
|
|
|
103
115
|
'gpt-5.6-terra': {
|
|
104
116
|
provider: 'openai',
|
|
105
117
|
apiModelId: 'gpt-5.6-terra',
|
|
118
|
+
supportsImages: true,
|
|
106
119
|
fallbacks: ['claude-sonnet-5', 'gemini-3.1-pro-preview'],
|
|
107
120
|
supportsTemperature: false,
|
|
108
121
|
thinksByDefault: true,
|
|
@@ -111,6 +124,7 @@ export const MODEL_REGISTRY = {
|
|
|
111
124
|
provider: 'anthropic',
|
|
112
125
|
supportsEffort: true,
|
|
113
126
|
apiModelId: 'claude-sonnet-4-6',
|
|
127
|
+
supportsImages: true,
|
|
114
128
|
fallbacks: ['gpt-5.6-terra', 'gemini-3.1-pro-preview'],
|
|
115
129
|
},
|
|
116
130
|
// Legacy: consumer-retired 2026-02; API snapshots retire 2026-10 onward.
|
|
@@ -118,6 +132,7 @@ export const MODEL_REGISTRY = {
|
|
|
118
132
|
'gpt-4o': {
|
|
119
133
|
provider: 'openai',
|
|
120
134
|
apiModelId: 'gpt-4o',
|
|
135
|
+
supportsImages: true,
|
|
121
136
|
fallbacks: ['claude-sonnet-5', 'gemini-3.1-pro-preview'],
|
|
122
137
|
},
|
|
123
138
|
// --- Top tier ---
|
|
@@ -125,6 +140,7 @@ export const MODEL_REGISTRY = {
|
|
|
125
140
|
provider: 'anthropic',
|
|
126
141
|
supportsEffort: true,
|
|
127
142
|
apiModelId: 'claude-opus-5-5',
|
|
143
|
+
supportsImages: true,
|
|
128
144
|
fallbacks: ['gpt-5.6-sol', 'gemini-3.1-pro-preview'],
|
|
129
145
|
// All verified live 2026-09-22: rejects `temperature` and forced
|
|
130
146
|
// tool_choice (400 each); thinking is always on and its tokens count
|
|
@@ -139,6 +155,7 @@ export const MODEL_REGISTRY = {
|
|
|
139
155
|
provider: 'anthropic',
|
|
140
156
|
supportsEffort: true,
|
|
141
157
|
apiModelId: 'claude-opus-5',
|
|
158
|
+
supportsImages: true,
|
|
142
159
|
fallbacks: ['gpt-5.6-sol', 'gemini-3.1-pro-preview'],
|
|
143
160
|
// Opus 4.7+ rejects `temperature` outright (400 invalid_request_error).
|
|
144
161
|
supportsTemperature: false,
|
|
@@ -152,6 +169,7 @@ export const MODEL_REGISTRY = {
|
|
|
152
169
|
provider: 'anthropic',
|
|
153
170
|
supportsEffort: true,
|
|
154
171
|
apiModelId: 'claude-opus-4-8',
|
|
172
|
+
supportsImages: true,
|
|
155
173
|
fallbacks: ['gpt-5.6-sol', 'gemini-3.1-pro-preview'],
|
|
156
174
|
// Opus 4.7+ rejects `temperature` outright (400 invalid_request_error).
|
|
157
175
|
supportsTemperature: false,
|
|
@@ -160,6 +178,7 @@ export const MODEL_REGISTRY = {
|
|
|
160
178
|
provider: 'anthropic',
|
|
161
179
|
supportsEffort: true,
|
|
162
180
|
apiModelId: 'claude-opus-4-6',
|
|
181
|
+
supportsImages: true,
|
|
163
182
|
fallbacks: ['gpt-5.6-sol', 'gemini-3.1-pro-preview'],
|
|
164
183
|
},
|
|
165
184
|
// Released 2026-09-22; same limits as gpt-5.6-sol (1.05M / 128k). Verified
|
|
@@ -169,6 +188,7 @@ export const MODEL_REGISTRY = {
|
|
|
169
188
|
'gpt-6-sol': {
|
|
170
189
|
provider: 'openai',
|
|
171
190
|
apiModelId: 'gpt-6-sol',
|
|
191
|
+
supportsImages: true,
|
|
172
192
|
fallbacks: ['claude-opus-5-5', 'gemini-3.1-pro-preview'],
|
|
173
193
|
supportsTemperature: false,
|
|
174
194
|
thinksByDefault: true,
|
|
@@ -176,6 +196,7 @@ export const MODEL_REGISTRY = {
|
|
|
176
196
|
'gpt-5.6-sol': {
|
|
177
197
|
provider: 'openai',
|
|
178
198
|
apiModelId: 'gpt-5.6-sol',
|
|
199
|
+
supportsImages: true,
|
|
179
200
|
fallbacks: ['claude-opus-5-5', 'gemini-3.1-pro-preview'],
|
|
180
201
|
supportsTemperature: false,
|
|
181
202
|
thinksByDefault: true,
|
|
@@ -183,6 +204,7 @@ export const MODEL_REGISTRY = {
|
|
|
183
204
|
'gpt-5.2': {
|
|
184
205
|
provider: 'openai',
|
|
185
206
|
apiModelId: 'gpt-5.2',
|
|
207
|
+
supportsImages: true,
|
|
186
208
|
fallbacks: ['claude-opus-5-5', 'gemini-3.1-pro-preview'],
|
|
187
209
|
},
|
|
188
210
|
// Google's best Pro model as of 2026-08 — still Preview-labeled (no GA
|
|
@@ -191,6 +213,7 @@ export const MODEL_REGISTRY = {
|
|
|
191
213
|
'gemini-3.1-pro-preview': {
|
|
192
214
|
provider: 'gemini',
|
|
193
215
|
apiModelId: 'gemini-3.1-pro-preview',
|
|
216
|
+
supportsImages: true,
|
|
194
217
|
fallbacks: ['claude-opus-5-5', 'gpt-5.6-sol'],
|
|
195
218
|
// Thinks by default (thinking_level defaults to `high`).
|
|
196
219
|
thinksByDefault: true,
|
|
@@ -60,6 +60,18 @@ function toAnthropicStrictSchema(node) {
|
|
|
60
60
|
}
|
|
61
61
|
return out;
|
|
62
62
|
}
|
|
63
|
+
/** The user turn: the bare prompt string, or image blocks then the prompt as a text block. */
|
|
64
|
+
function userContent(request) {
|
|
65
|
+
if (!request.images)
|
|
66
|
+
return request.prompt;
|
|
67
|
+
return [
|
|
68
|
+
...request.images.map((image) => ({
|
|
69
|
+
type: 'image',
|
|
70
|
+
source: { type: 'base64', media_type: image.mediaType, data: image.data },
|
|
71
|
+
})),
|
|
72
|
+
{ type: 'text', text: request.prompt },
|
|
73
|
+
];
|
|
74
|
+
}
|
|
63
75
|
export const anthropicProvider = {
|
|
64
76
|
name: 'anthropic',
|
|
65
77
|
async complete(request) {
|
|
@@ -67,12 +79,13 @@ export const anthropicProvider = {
|
|
|
67
79
|
request.logger?.info('llm.request', {
|
|
68
80
|
provider: 'anthropic', model: request.apiModelId, endpoint,
|
|
69
81
|
...(request.callSite ? { callSite: request.callSite } : {}),
|
|
82
|
+
...(request.images ? { images: request.images.length } : {}),
|
|
70
83
|
});
|
|
71
84
|
const startTime = Date.now();
|
|
72
85
|
const body = {
|
|
73
86
|
model: request.apiModelId,
|
|
74
87
|
max_tokens: request.maxTokens,
|
|
75
|
-
messages: [{ role: 'user', content: request
|
|
88
|
+
messages: [{ role: 'user', content: userContent(request) }],
|
|
76
89
|
};
|
|
77
90
|
// Some models (Opus 4.7+, Sonnet 5, Fable 5, Opus 5.5) reject `temperature`
|
|
78
91
|
// outright with a 400 — omit the field entirely rather than sending it.
|
|
@@ -64,6 +64,7 @@ export const geminiProvider = {
|
|
|
64
64
|
request.logger?.info('llm.request', {
|
|
65
65
|
provider: 'gemini', model: request.apiModelId, endpoint,
|
|
66
66
|
...(request.callSite ? { callSite: request.callSite } : {}),
|
|
67
|
+
...(request.images ? { images: request.images.length } : {}),
|
|
67
68
|
});
|
|
68
69
|
const startTime = Date.now();
|
|
69
70
|
const generationConfig = {
|
|
@@ -95,7 +96,13 @@ export const geminiProvider = {
|
|
|
95
96
|
generationConfig.thinkingConfig = { thinkingBudget: request.thinkingBudget };
|
|
96
97
|
}
|
|
97
98
|
const body = {
|
|
98
|
-
|
|
99
|
+
// Images go as inline-data parts ahead of the prompt's text part.
|
|
100
|
+
contents: [{
|
|
101
|
+
parts: [
|
|
102
|
+
...(request.images ?? []).map((image) => ({ inlineData: { mimeType: image.mediaType, data: image.data } })),
|
|
103
|
+
{ text: request.prompt },
|
|
104
|
+
],
|
|
105
|
+
}],
|
|
99
106
|
generationConfig,
|
|
100
107
|
};
|
|
101
108
|
if (request.systemPrompt) {
|
|
@@ -90,13 +90,26 @@ export const openaiProvider = {
|
|
|
90
90
|
request.logger?.info('llm.request', {
|
|
91
91
|
provider: 'openai', model: request.apiModelId, endpoint,
|
|
92
92
|
...(request.callSite ? { callSite: request.callSite } : {}),
|
|
93
|
+
...(request.images ? { images: request.images.length } : {}),
|
|
93
94
|
});
|
|
94
95
|
const startTime = Date.now();
|
|
95
96
|
const messages = [];
|
|
96
97
|
if (request.systemPrompt) {
|
|
97
98
|
messages.push({ role: 'system', content: request.systemPrompt });
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
+
// Images go as data-URL `image_url` parts ahead of the prompt's text part.
|
|
101
|
+
messages.push({
|
|
102
|
+
role: 'user',
|
|
103
|
+
content: request.images
|
|
104
|
+
? [
|
|
105
|
+
...request.images.map((image) => ({
|
|
106
|
+
type: 'image_url',
|
|
107
|
+
image_url: { url: `data:${image.mediaType};base64,${image.data}` },
|
|
108
|
+
})),
|
|
109
|
+
{ type: 'text', text: request.prompt },
|
|
110
|
+
]
|
|
111
|
+
: request.prompt,
|
|
112
|
+
});
|
|
100
113
|
const body = {
|
|
101
114
|
model: request.apiModelId,
|
|
102
115
|
max_completion_tokens: request.maxTokens,
|
package/dist/llm/types.d.ts
CHANGED
|
@@ -14,12 +14,26 @@ export interface JsonSchema {
|
|
|
14
14
|
/** JSON Schema object. */
|
|
15
15
|
schema: Record<string, unknown>;
|
|
16
16
|
}
|
|
17
|
+
/** Media types every image-capable provider accepts (Anthropic, OpenAI and Gemini alike). */
|
|
18
|
+
export type LLMImageMediaType = 'image/jpeg' | 'image/png' | 'image/webp';
|
|
19
|
+
/** An image sent beside the prompt: raw base64, no `data:` URL prefix. */
|
|
20
|
+
export interface LLMImage {
|
|
21
|
+
mediaType: LLMImageMediaType;
|
|
22
|
+
data: string;
|
|
23
|
+
}
|
|
17
24
|
/** Request to llmComplete(). */
|
|
18
25
|
export interface LLMRequest {
|
|
19
26
|
/** Model name from the registry (e.g. 'claude-sonnet-4-6', 'gemini-2.5-flash'). */
|
|
20
27
|
model: string;
|
|
21
28
|
/** The user prompt. */
|
|
22
29
|
prompt: string;
|
|
30
|
+
/**
|
|
31
|
+
* Images for the model to look at beside the prompt, placed before the
|
|
32
|
+
* prompt text in the user turn. Only models the registry marks
|
|
33
|
+
* `supportsImages` are tried: the requested model must be one (else the
|
|
34
|
+
* call is rejected up front), and failover skips fallbacks that are not.
|
|
35
|
+
*/
|
|
36
|
+
images?: LLMImage[];
|
|
23
37
|
/** Optional system prompt. */
|
|
24
38
|
systemPrompt?: string;
|
|
25
39
|
/** Max output tokens. Default: 2048. */
|
|
@@ -174,6 +188,8 @@ export interface ProviderAdapter {
|
|
|
174
188
|
export interface ProviderCallRequest {
|
|
175
189
|
apiModelId: string;
|
|
176
190
|
prompt: string;
|
|
191
|
+
/** Present and non-empty only for a `supportsImages` model (gated in complete.ts). */
|
|
192
|
+
images?: LLMImage[];
|
|
177
193
|
systemPrompt?: string;
|
|
178
194
|
maxTokens: number;
|
|
179
195
|
temperature: number;
|
package/dist/log-core.js
CHANGED
|
@@ -22,6 +22,23 @@ function errorCode(err) {
|
|
|
22
22
|
return code;
|
|
23
23
|
return undefined;
|
|
24
24
|
}
|
|
25
|
+
/** A non-Error cause as text. A plain object (gaxios 7 sets a Google API
|
|
26
|
+
* error's cause to the AIP-193 body { code, message, status }) is rendered
|
|
27
|
+
* as JSON, which String() would flatten to "[object Object]" (watchdog task
|
|
28
|
+
* 4454314a, 2026-10-28); an object JSON cannot render falls back to String(). */
|
|
29
|
+
function causeText(cause) {
|
|
30
|
+
if (typeof cause === 'object' && cause !== null) {
|
|
31
|
+
try {
|
|
32
|
+
const json = JSON.stringify(cause);
|
|
33
|
+
if (typeof json === 'string')
|
|
34
|
+
return json;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// circular or a throwing toJSON: fall through
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return String(cause);
|
|
41
|
+
}
|
|
25
42
|
export function serializeError(err) {
|
|
26
43
|
if (err instanceof Error) {
|
|
27
44
|
const out = { name: err.name, message: err.message };
|
|
@@ -36,7 +53,7 @@ export function serializeError(err) {
|
|
|
36
53
|
out.cause.code = causeCode;
|
|
37
54
|
}
|
|
38
55
|
else if (err.cause !== undefined) {
|
|
39
|
-
out.cause = { name: 'cause', message: truncate(
|
|
56
|
+
out.cause = { name: 'cause', message: truncate(causeText(err.cause), 500) };
|
|
40
57
|
}
|
|
41
58
|
return out;
|
|
42
59
|
}
|
package/package.json
CHANGED