@dogpile/sdk 0.1.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/CHANGELOG.md +37 -0
- package/LICENSE +16 -0
- package/README.md +842 -0
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +4493 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +44 -0
- package/dist/providers/openai-compatible.d.ts.map +1 -0
- package/dist/providers/openai-compatible.js +305 -0
- package/dist/providers/openai-compatible.js.map +1 -0
- package/dist/runtime/broadcast.d.ts +18 -0
- package/dist/runtime/broadcast.d.ts.map +1 -0
- package/dist/runtime/broadcast.js +335 -0
- package/dist/runtime/broadcast.js.map +1 -0
- package/dist/runtime/cancellation.d.ts +6 -0
- package/dist/runtime/cancellation.d.ts.map +1 -0
- package/dist/runtime/cancellation.js +35 -0
- package/dist/runtime/cancellation.js.map +1 -0
- package/dist/runtime/coordinator.d.ts +18 -0
- package/dist/runtime/coordinator.d.ts.map +1 -0
- package/dist/runtime/coordinator.js +434 -0
- package/dist/runtime/coordinator.js.map +1 -0
- package/dist/runtime/decisions.d.ts +5 -0
- package/dist/runtime/decisions.d.ts.map +1 -0
- package/dist/runtime/decisions.js +31 -0
- package/dist/runtime/decisions.js.map +1 -0
- package/dist/runtime/defaults.d.ts +63 -0
- package/dist/runtime/defaults.d.ts.map +1 -0
- package/dist/runtime/defaults.js +426 -0
- package/dist/runtime/defaults.js.map +1 -0
- package/dist/runtime/engine.d.ts +79 -0
- package/dist/runtime/engine.d.ts.map +1 -0
- package/dist/runtime/engine.js +723 -0
- package/dist/runtime/engine.js.map +1 -0
- package/dist/runtime/model.d.ts +14 -0
- package/dist/runtime/model.d.ts.map +1 -0
- package/dist/runtime/model.js +82 -0
- package/dist/runtime/model.js.map +1 -0
- package/dist/runtime/sequential.d.ts +18 -0
- package/dist/runtime/sequential.d.ts.map +1 -0
- package/dist/runtime/sequential.js +277 -0
- package/dist/runtime/sequential.js.map +1 -0
- package/dist/runtime/shared.d.ts +18 -0
- package/dist/runtime/shared.d.ts.map +1 -0
- package/dist/runtime/shared.js +288 -0
- package/dist/runtime/shared.js.map +1 -0
- package/dist/runtime/termination.d.ts +77 -0
- package/dist/runtime/termination.d.ts.map +1 -0
- package/dist/runtime/termination.js +355 -0
- package/dist/runtime/termination.js.map +1 -0
- package/dist/runtime/tools.d.ts +314 -0
- package/dist/runtime/tools.d.ts.map +1 -0
- package/dist/runtime/tools.js +969 -0
- package/dist/runtime/tools.js.map +1 -0
- package/dist/runtime/validation.d.ts +23 -0
- package/dist/runtime/validation.d.ts.map +1 -0
- package/dist/runtime/validation.js +656 -0
- package/dist/runtime/validation.js.map +1 -0
- package/dist/types.d.ts +2434 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +81 -0
- package/dist/types.js.map +1 -0
- package/package.json +157 -0
- package/src/browser/index.ts +7 -0
- package/src/index.ts +195 -0
- package/src/providers/openai-compatible.ts +406 -0
- package/src/runtime/broadcast.test.ts +355 -0
- package/src/runtime/broadcast.ts +428 -0
- package/src/runtime/cancellation.ts +40 -0
- package/src/runtime/coordinator.test.ts +468 -0
- package/src/runtime/coordinator.ts +581 -0
- package/src/runtime/decisions.ts +38 -0
- package/src/runtime/defaults.ts +547 -0
- package/src/runtime/engine.ts +880 -0
- package/src/runtime/model.ts +117 -0
- package/src/runtime/sequential.test.ts +262 -0
- package/src/runtime/sequential.ts +357 -0
- package/src/runtime/shared.test.ts +265 -0
- package/src/runtime/shared.ts +367 -0
- package/src/runtime/termination.ts +463 -0
- package/src/runtime/tools.ts +1518 -0
- package/src/runtime/validation.ts +771 -0
- package/src/types.ts +2729 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { DogpileError } from "../types.js";
|
|
2
|
+
import type {
|
|
3
|
+
ConfiguredModelProvider,
|
|
4
|
+
DogpileErrorCode,
|
|
5
|
+
JsonObject,
|
|
6
|
+
JsonValue,
|
|
7
|
+
ModelFinishReason,
|
|
8
|
+
ModelMessage,
|
|
9
|
+
ModelRequest,
|
|
10
|
+
ModelResponse
|
|
11
|
+
} from "../types.js";
|
|
12
|
+
|
|
13
|
+
const defaultBaseURL = "https://api.openai.com/v1";
|
|
14
|
+
const defaultPath = "/chat/completions";
|
|
15
|
+
|
|
16
|
+
export type OpenAICompatibleFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
17
|
+
|
|
18
|
+
export interface OpenAICompatibleProviderCostContext {
|
|
19
|
+
readonly providerId: string;
|
|
20
|
+
readonly request: ModelRequest;
|
|
21
|
+
readonly response: OpenAICompatibleChatCompletionResponse;
|
|
22
|
+
readonly usage?: ModelResponse["usage"];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type OpenAICompatibleProviderCostEstimator = (
|
|
26
|
+
context: OpenAICompatibleProviderCostContext
|
|
27
|
+
) => number | undefined;
|
|
28
|
+
|
|
29
|
+
export interface OpenAICompatibleProviderOptions {
|
|
30
|
+
readonly model: string;
|
|
31
|
+
readonly apiKey?: string;
|
|
32
|
+
readonly baseURL?: string | URL;
|
|
33
|
+
readonly path?: string;
|
|
34
|
+
readonly id?: string;
|
|
35
|
+
readonly headers?: Readonly<Record<string, string | undefined>>;
|
|
36
|
+
readonly fetch?: OpenAICompatibleFetch;
|
|
37
|
+
readonly maxOutputTokens?: number;
|
|
38
|
+
readonly extraBody?: JsonObject;
|
|
39
|
+
readonly costEstimator?: OpenAICompatibleProviderCostEstimator;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OpenAICompatibleChatCompletionResponse {
|
|
43
|
+
readonly id?: string;
|
|
44
|
+
readonly object?: string;
|
|
45
|
+
readonly created?: number;
|
|
46
|
+
readonly model?: string;
|
|
47
|
+
readonly choices?: readonly OpenAICompatibleChatCompletionChoice[];
|
|
48
|
+
readonly usage?: OpenAICompatibleUsage;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface OpenAICompatibleChatCompletionChoice {
|
|
52
|
+
readonly finish_reason?: string | null;
|
|
53
|
+
readonly message?: {
|
|
54
|
+
readonly content?: unknown;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface OpenAICompatibleUsage extends JsonObject {
|
|
59
|
+
readonly prompt_tokens?: number;
|
|
60
|
+
readonly completion_tokens?: number;
|
|
61
|
+
readonly total_tokens?: number;
|
|
62
|
+
readonly input_tokens?: number;
|
|
63
|
+
readonly output_tokens?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function createOpenAICompatibleProvider(options: OpenAICompatibleProviderOptions): ConfiguredModelProvider {
|
|
67
|
+
validateOptions(options);
|
|
68
|
+
|
|
69
|
+
const providerId = options.id ?? `openai-compatible:${options.model}`;
|
|
70
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch?.bind(globalThis);
|
|
71
|
+
|
|
72
|
+
if (!fetchImplementation) {
|
|
73
|
+
throw new DogpileError({
|
|
74
|
+
code: "invalid-configuration",
|
|
75
|
+
message: "createOpenAICompatibleProvider() requires a fetch implementation in this runtime.",
|
|
76
|
+
retryable: false,
|
|
77
|
+
providerId,
|
|
78
|
+
detail: {
|
|
79
|
+
kind: "configuration-validation",
|
|
80
|
+
path: "fetch",
|
|
81
|
+
expected: "a fetch-compatible function"
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
id: providerId,
|
|
88
|
+
async generate(request: ModelRequest): Promise<ModelResponse> {
|
|
89
|
+
const response = await fetchImplementation(createURL(options), {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: createHeaders(options),
|
|
92
|
+
body: JSON.stringify(createBody(options, request)),
|
|
93
|
+
...(request.signal !== undefined ? { signal: request.signal } : {})
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const payload = await readJson(response, providerId);
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw createProviderError(response, payload, providerId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const completion = asChatCompletionResponse(payload, providerId);
|
|
103
|
+
const text = readAssistantText(completion, providerId);
|
|
104
|
+
const usage = normalizeUsage(completion.usage);
|
|
105
|
+
const finishReason = normalizeFinishReason(completion.choices?.[0]?.finish_reason);
|
|
106
|
+
const costUsd = options.costEstimator?.({
|
|
107
|
+
providerId,
|
|
108
|
+
request,
|
|
109
|
+
response: completion,
|
|
110
|
+
...(usage ? { usage } : {})
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
text,
|
|
115
|
+
...(finishReason !== undefined ? { finishReason } : {}),
|
|
116
|
+
...(usage ? { usage } : {}),
|
|
117
|
+
...(costUsd !== undefined ? { costUsd } : {}),
|
|
118
|
+
metadata: {
|
|
119
|
+
openAICompatible: responseMetadata(completion)
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function validateOptions(options: OpenAICompatibleProviderOptions): void {
|
|
127
|
+
if (!isRecord(options)) {
|
|
128
|
+
throwInvalid("options", "an options object");
|
|
129
|
+
}
|
|
130
|
+
if (!isNonEmptyString(options.model)) {
|
|
131
|
+
throwInvalid("model", "a non-empty model id");
|
|
132
|
+
}
|
|
133
|
+
if (options.apiKey !== undefined && !isNonEmptyString(options.apiKey)) {
|
|
134
|
+
throwInvalid("apiKey", "a non-empty API key when provided");
|
|
135
|
+
}
|
|
136
|
+
if (options.id !== undefined && !isNonEmptyString(options.id)) {
|
|
137
|
+
throwInvalid("id", "a non-empty provider id when provided");
|
|
138
|
+
}
|
|
139
|
+
if (options.path !== undefined && !isNonEmptyString(options.path)) {
|
|
140
|
+
throwInvalid("path", "a non-empty request path when provided");
|
|
141
|
+
}
|
|
142
|
+
if (options.fetch !== undefined && typeof options.fetch !== "function") {
|
|
143
|
+
throwInvalid("fetch", "a fetch-compatible function when provided");
|
|
144
|
+
}
|
|
145
|
+
if (options.maxOutputTokens !== undefined && (!Number.isInteger(options.maxOutputTokens) || options.maxOutputTokens <= 0)) {
|
|
146
|
+
throwInvalid("maxOutputTokens", "a positive integer when provided");
|
|
147
|
+
}
|
|
148
|
+
if (options.costEstimator !== undefined && typeof options.costEstimator !== "function") {
|
|
149
|
+
throwInvalid("costEstimator", "a function when provided");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function throwInvalid(path: string, expected: string): never {
|
|
154
|
+
throw new DogpileError({
|
|
155
|
+
code: "invalid-configuration",
|
|
156
|
+
message: `Invalid OpenAI-compatible provider option at ${path}.`,
|
|
157
|
+
retryable: false,
|
|
158
|
+
detail: {
|
|
159
|
+
kind: "configuration-validation",
|
|
160
|
+
path,
|
|
161
|
+
expected
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function createURL(options: OpenAICompatibleProviderOptions): URL {
|
|
167
|
+
const baseURL = new URL(String(options.baseURL ?? defaultBaseURL));
|
|
168
|
+
const path = options.path ?? defaultPath;
|
|
169
|
+
return new URL(path.startsWith("/") ? path.slice(1) : path, ensureTrailingSlash(baseURL));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function ensureTrailingSlash(url: URL): URL {
|
|
173
|
+
const next = new URL(url);
|
|
174
|
+
if (!next.pathname.endsWith("/")) {
|
|
175
|
+
next.pathname = `${next.pathname}/`;
|
|
176
|
+
}
|
|
177
|
+
return next;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function createHeaders(options: OpenAICompatibleProviderOptions): Headers {
|
|
181
|
+
const headers = new Headers();
|
|
182
|
+
for (const [key, value] of Object.entries(options.headers ?? {})) {
|
|
183
|
+
if (value !== undefined) {
|
|
184
|
+
headers.set(key, value);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
headers.set("content-type", "application/json");
|
|
188
|
+
if (options.apiKey !== undefined && !headers.has("authorization")) {
|
|
189
|
+
headers.set("authorization", `Bearer ${options.apiKey}`);
|
|
190
|
+
}
|
|
191
|
+
return headers;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function createBody(options: OpenAICompatibleProviderOptions, request: ModelRequest): JsonObject {
|
|
195
|
+
return {
|
|
196
|
+
...(options.extraBody ?? {}),
|
|
197
|
+
model: options.model,
|
|
198
|
+
messages: request.messages.map(toChatMessage),
|
|
199
|
+
temperature: request.temperature,
|
|
200
|
+
...(options.maxOutputTokens !== undefined ? { max_tokens: options.maxOutputTokens } : {})
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function toChatMessage(message: ModelMessage): JsonObject {
|
|
205
|
+
return {
|
|
206
|
+
role: message.role,
|
|
207
|
+
content: message.content
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function readJson(response: Response, providerId: string): Promise<unknown> {
|
|
212
|
+
try {
|
|
213
|
+
return await response.json();
|
|
214
|
+
} catch (error) {
|
|
215
|
+
throw new DogpileError({
|
|
216
|
+
code: "provider-invalid-response",
|
|
217
|
+
message: "OpenAI-compatible provider returned a non-JSON response.",
|
|
218
|
+
cause: error,
|
|
219
|
+
retryable: response.status >= 500,
|
|
220
|
+
providerId,
|
|
221
|
+
detail: {
|
|
222
|
+
statusCode: response.status,
|
|
223
|
+
statusText: response.statusText
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function asChatCompletionResponse(payload: unknown, providerId: string): OpenAICompatibleChatCompletionResponse {
|
|
230
|
+
if (!isRecord(payload)) {
|
|
231
|
+
throw new DogpileError({
|
|
232
|
+
code: "provider-invalid-response",
|
|
233
|
+
message: "OpenAI-compatible provider response must be a JSON object.",
|
|
234
|
+
retryable: true,
|
|
235
|
+
providerId
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return payload as OpenAICompatibleChatCompletionResponse;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function readAssistantText(response: OpenAICompatibleChatCompletionResponse, providerId: string): string {
|
|
243
|
+
const content = response.choices?.[0]?.message?.content;
|
|
244
|
+
const text = normalizeContent(content);
|
|
245
|
+
|
|
246
|
+
if (!text) {
|
|
247
|
+
throw new DogpileError({
|
|
248
|
+
code: "provider-invalid-response",
|
|
249
|
+
message: "OpenAI-compatible provider response did not include assistant text.",
|
|
250
|
+
retryable: true,
|
|
251
|
+
providerId
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return text;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function normalizeContent(content: unknown): string {
|
|
259
|
+
if (typeof content === "string") {
|
|
260
|
+
return content;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!Array.isArray(content)) {
|
|
264
|
+
return "";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return content
|
|
268
|
+
.map((part) => {
|
|
269
|
+
if (!isRecord(part)) {
|
|
270
|
+
return "";
|
|
271
|
+
}
|
|
272
|
+
const text = part.text;
|
|
273
|
+
return typeof text === "string" ? text : "";
|
|
274
|
+
})
|
|
275
|
+
.filter(Boolean)
|
|
276
|
+
.join("");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function normalizeUsage(usage: OpenAICompatibleUsage | undefined): ModelResponse["usage"] | undefined {
|
|
280
|
+
if (!usage) {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const inputTokens = readTokenCount(usage.prompt_tokens ?? usage.input_tokens);
|
|
285
|
+
const outputTokens = readTokenCount(usage.completion_tokens ?? usage.output_tokens);
|
|
286
|
+
const totalTokens = readTokenCount(usage.total_tokens) ?? sumIfPresent(inputTokens, outputTokens);
|
|
287
|
+
|
|
288
|
+
if (inputTokens === undefined || outputTokens === undefined || totalTokens === undefined) {
|
|
289
|
+
return undefined;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
inputTokens,
|
|
294
|
+
outputTokens,
|
|
295
|
+
totalTokens
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function readTokenCount(value: unknown): number | undefined {
|
|
300
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function sumIfPresent(left: number | undefined, right: number | undefined): number | undefined {
|
|
304
|
+
return left === undefined || right === undefined ? undefined : left + right;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function normalizeFinishReason(reason: string | null | undefined): ModelFinishReason | undefined {
|
|
308
|
+
switch (reason) {
|
|
309
|
+
case "stop":
|
|
310
|
+
return "stop";
|
|
311
|
+
case "length":
|
|
312
|
+
return "length";
|
|
313
|
+
case "content_filter":
|
|
314
|
+
case "content-filter":
|
|
315
|
+
return "content-filter";
|
|
316
|
+
case "tool_calls":
|
|
317
|
+
case "tool-calls":
|
|
318
|
+
return "tool-calls";
|
|
319
|
+
case undefined:
|
|
320
|
+
case null:
|
|
321
|
+
return undefined;
|
|
322
|
+
default:
|
|
323
|
+
return "other";
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function responseMetadata(response: OpenAICompatibleChatCompletionResponse): JsonObject {
|
|
328
|
+
return removeUndefined({
|
|
329
|
+
id: response.id,
|
|
330
|
+
object: response.object,
|
|
331
|
+
created: response.created,
|
|
332
|
+
model: response.model,
|
|
333
|
+
usage: isJsonValue(response.usage) ? response.usage : undefined
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function createProviderError(response: Response, payload: unknown, providerId: string): DogpileError {
|
|
338
|
+
return new DogpileError({
|
|
339
|
+
code: codeForStatus(response.status),
|
|
340
|
+
message: errorMessage(response, payload),
|
|
341
|
+
retryable: response.status === 408 || response.status === 429 || response.status >= 500,
|
|
342
|
+
providerId,
|
|
343
|
+
detail: removeUndefined({
|
|
344
|
+
statusCode: response.status,
|
|
345
|
+
statusText: response.statusText,
|
|
346
|
+
response: isJsonValue(payload) ? payload : undefined
|
|
347
|
+
})
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function codeForStatus(status: number): DogpileErrorCode {
|
|
352
|
+
if (status === 401 || status === 403) {
|
|
353
|
+
return "provider-authentication";
|
|
354
|
+
}
|
|
355
|
+
if (status === 404) {
|
|
356
|
+
return "provider-not-found";
|
|
357
|
+
}
|
|
358
|
+
if (status === 408 || status === 504) {
|
|
359
|
+
return "provider-timeout";
|
|
360
|
+
}
|
|
361
|
+
if (status === 429) {
|
|
362
|
+
return "provider-rate-limited";
|
|
363
|
+
}
|
|
364
|
+
if (status >= 500) {
|
|
365
|
+
return "provider-unavailable";
|
|
366
|
+
}
|
|
367
|
+
if (status >= 400) {
|
|
368
|
+
return "provider-invalid-request";
|
|
369
|
+
}
|
|
370
|
+
return "provider-error";
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function errorMessage(response: Response, payload: unknown): string {
|
|
374
|
+
const providerMessage = isRecord(payload) && isRecord(payload.error) && typeof payload.error.message === "string"
|
|
375
|
+
? payload.error.message
|
|
376
|
+
: undefined;
|
|
377
|
+
return providerMessage ?? `OpenAI-compatible provider request failed with HTTP ${response.status}.`;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function removeUndefined(values: Record<string, JsonValue | undefined>): JsonObject {
|
|
381
|
+
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined)) as JsonObject;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
385
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function isNonEmptyString(value: unknown): value is string {
|
|
389
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function isJsonValue(value: unknown): value is JsonValue {
|
|
393
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
394
|
+
return typeof value !== "number" || Number.isFinite(value);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (Array.isArray(value)) {
|
|
398
|
+
return value.every(isJsonValue);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (isRecord(value)) {
|
|
402
|
+
return Object.values(value).every(isJsonValue);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return false;
|
|
406
|
+
}
|