@fayegram-ai/decision-bridge 0.1.2 → 0.1.3
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 +19 -0
- package/CONTRIBUTING.md +5 -3
- package/README.md +24 -8
- package/SECURITY.md +18 -11
- package/docs/README.md +17 -4
- package/docs/development/ARCHITECTURE.md +32 -5
- package/docs/development/DESIGN.md +21 -3
- package/docs/development/RELEASING.md +7 -3
- package/docs/development/REPOSITORY_LAYOUT.md +2 -0
- package/docs/development/TESTING.md +31 -3
- package/docs/guides/BROWSER.md +5 -0
- package/docs/guides/CLOUDFLARE_DECISIONS.md +24 -46
- package/docs/guides/CUSTOMIZATION.md +8 -2
- package/docs/guides/CUSTOM_GENERATIVE.md +5 -3
- package/docs/guides/CUSTOM_PROVIDERS.md +5 -3
- package/docs/guides/EXTRACTION.md +125 -0
- package/docs/guides/GETTING_STARTED.md +8 -4
- package/docs/guides/JEV.md +13 -20
- package/docs/guides/LIVE_DECISIONS.md +163 -0
- package/docs/guides/OPENAI_DECISIONS.md +52 -24
- package/docs/guides/PLATFORMS.md +3 -3
- package/docs/guides/SWITCHING_MODELS.md +6 -2
- package/docs/guides/TROUBLESHOOTING.md +25 -0
- package/docs/reference/CLI.md +48 -10
- package/docs/reference/CONFIGURATION.md +31 -8
- package/docs/reference/ERRORS.md +41 -12
- package/docs/reference/PROVIDERS.md +19 -6
- package/docs/reference/SDK.md +62 -24
- package/docs/reference/SOURCES.md +7 -0
- package/examples/extraction.mjs +16 -0
- package/examples/live-decisions.mjs +24 -0
- package/package.json +11 -1
- package/schemas/decision.config.schema.json +88 -11
- package/src/bridge.mjs +9 -6
- package/src/capabilities.mjs +28 -4
- package/src/cli/config-validation.mjs +4 -4
- package/src/cli/config.mjs +4 -2
- package/src/cli/initialize.mjs +8 -4
- package/src/cli/live.mjs +63 -0
- package/src/cli/main.mjs +35 -12
- package/src/cli/provider-loader.mjs +3 -2
- package/src/cloudflare-decisions.d.mts +12 -5
- package/src/decisions.d.mts +28 -4
- package/src/errors.mjs +7 -1
- package/src/extraction/index.d.mts +66 -0
- package/src/extraction/index.mjs +78 -0
- package/src/extraction/validation.mjs +105 -0
- package/src/index.d.mts +18 -4
- package/src/input.mjs +21 -3
- package/src/live/index.d.mts +73 -0
- package/src/live/index.mjs +170 -0
- package/src/live/state.mjs +125 -0
- package/src/live/validation.mjs +122 -0
- package/src/providers/cloudflare-images.mjs +3 -9
- package/src/providers/cloudflare-media.mjs +5 -10
- package/src/providers/cloudflare-preflight.mjs +4 -3
- package/src/providers/cloudflare-profile.mjs +10 -14
- package/src/providers/cloudflare-transport.mjs +12 -5
- package/src/providers/cloudflare-validation.mjs +3 -1
- package/src/providers/decisions-codec.mjs +10 -5
- package/src/providers/decisions-input.mjs +120 -0
- package/src/providers/decisions-validation.mjs +37 -69
- package/src/providers/jev-images.mjs +2 -8
- package/src/providers/jev-preflight.mjs +1 -0
- package/src/providers/jev-profile.mjs +2 -2
- package/src/providers/media-binary.mjs +0 -7
- package/src/providers/openai-decisions.mjs +2 -1
- package/src/providers/openai-errors.mjs +23 -0
- package/src/providers/openai-profile.mjs +2 -1
- package/src/response-transport.mjs +84 -17
- package/src/validation.mjs +26 -14
- package/src/version.mjs +1 -1
package/src/decisions.d.mts
CHANGED
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
/** Native OpenAI Decisions types, separate from the common bridge contract. */
|
|
2
2
|
export type NativeDecisionInputPart =
|
|
3
|
-
| { readonly type: 'input_text'; readonly text: string }
|
|
3
|
+
| { readonly type: 'input_text'; readonly text: string; readonly prompt_cache_breakpoint?: DecisionCacheBreakpoint | null }
|
|
4
4
|
| { readonly type: 'input_image'; readonly image_url: string;
|
|
5
|
-
readonly detail?: 'low' | 'high' | 'auto' | 'original' | null
|
|
6
|
-
|
|
5
|
+
readonly detail?: 'low' | 'high' | 'auto' | 'original' | null;
|
|
6
|
+
readonly prompt_cache_breakpoint?: DecisionCacheBreakpoint | null };
|
|
7
|
+
export interface DecisionCacheBreakpoint { readonly mode: 'explicit'; }
|
|
8
|
+
export interface DecisionCacheOptions { readonly mode?: 'implicit' | 'explicit' | null; readonly ttl?: '30m'; }
|
|
9
|
+
export type NativeDecisionMessage = {
|
|
7
10
|
readonly role: 'user'; readonly type?: 'message';
|
|
8
11
|
readonly content: string | readonly NativeDecisionInputPart[];
|
|
12
|
+
readonly phase?: 'commentary' | 'final_answer' | null;
|
|
13
|
+
} | {
|
|
14
|
+
readonly role: 'developer' | 'system'; readonly type?: 'message';
|
|
15
|
+
readonly content: string | readonly Extract<NativeDecisionInputPart, { readonly type: 'input_text' }>[];
|
|
16
|
+
readonly phase?: 'commentary' | 'final_answer' | null;
|
|
17
|
+
} | {
|
|
18
|
+
readonly role: 'assistant'; readonly type?: 'message';
|
|
19
|
+
readonly content: string | readonly ({ readonly type: 'output_text'; readonly text: string }
|
|
20
|
+
| { readonly type: 'refusal'; readonly refusal: string })[];
|
|
21
|
+
readonly phase?: 'commentary' | 'final_answer' | null;
|
|
22
|
+
};
|
|
23
|
+
interface ToolItem {
|
|
24
|
+
readonly call_id: string; readonly id?: string | null;
|
|
25
|
+
readonly status?: 'in_progress' | 'completed' | 'incomplete' | null;
|
|
9
26
|
}
|
|
27
|
+
export type NativeDecisionInputItem = NativeDecisionMessage
|
|
28
|
+
| (ToolItem & { readonly type: 'function_call'; readonly name: string; readonly arguments: string; readonly namespace?: string | null })
|
|
29
|
+
| (ToolItem & { readonly type: 'custom_tool_call'; readonly name: string; readonly input: string; readonly namespace?: string | null })
|
|
30
|
+
| (ToolItem & { readonly type: 'function_call_output' | 'custom_tool_call_output';
|
|
31
|
+
readonly output: string | readonly NativeDecisionInputPart[] });
|
|
10
32
|
export type NativeDecisionQuestion =
|
|
11
33
|
| { readonly type: 'predicate'; readonly name?: string; readonly instructions: string }
|
|
12
34
|
| { readonly type: 'choice'; readonly name?: string; readonly instructions: string;
|
|
@@ -15,9 +37,11 @@ export type NativeDecisionQuestion =
|
|
|
15
37
|
readonly levels: readonly { readonly label: string; readonly description?: string }[] };
|
|
16
38
|
export interface NativeDecisionRequest {
|
|
17
39
|
readonly model?: string;
|
|
18
|
-
readonly input: string | readonly
|
|
40
|
+
readonly input: string | readonly NativeDecisionInputItem[];
|
|
19
41
|
readonly questions: readonly NativeDecisionQuestion[];
|
|
20
42
|
readonly safety_identifier?: string | null;
|
|
43
|
+
readonly prompt_cache_key?: string | null;
|
|
44
|
+
readonly prompt_cache_options?: DecisionCacheOptions | null;
|
|
21
45
|
}
|
|
22
46
|
export type NativeDecisionAnswer =
|
|
23
47
|
| { readonly type: 'predicate'; readonly name: string | null; readonly probability: number }
|
package/src/errors.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Public errors never retain raw upstream bodies, headers or original exceptions. */
|
|
2
2
|
export class DecisionError extends Error {
|
|
3
|
-
constructor(message, { code = 'DECISION_ERROR', status, requestId, gatewayLogId, attempts, refusedQuestions, usage, upstreamCode
|
|
3
|
+
constructor(message, { code = 'DECISION_ERROR', status, requestId, gatewayLogId, attempts, refusedQuestions, usage, upstreamCode,
|
|
4
|
+
source, upstreamErrorCode, upstreamErrorType, retryAfterMs, retryable } = {}) {
|
|
4
5
|
super(message);
|
|
5
6
|
this.name = 'DecisionError';
|
|
6
7
|
this.code = code;
|
|
@@ -11,6 +12,9 @@ export class DecisionError extends Error {
|
|
|
11
12
|
if (refusedQuestions !== undefined) this.refusedQuestions = refusedQuestions;
|
|
12
13
|
if (usage !== undefined) this.usage = usage;
|
|
13
14
|
if (upstreamCode !== undefined) this.upstreamCode = upstreamCode;
|
|
15
|
+
for (const [key, value] of Object.entries({ source, upstreamErrorCode, upstreamErrorType, retryAfterMs, retryable })) {
|
|
16
|
+
if (value !== undefined) this[key] = value;
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
19
|
|
|
16
20
|
toJSON() {
|
|
@@ -24,6 +28,8 @@ export class DecisionError extends Error {
|
|
|
24
28
|
...(this.refusedQuestions === undefined ? {} : { refusedQuestions: this.refusedQuestions }),
|
|
25
29
|
...(this.usage === undefined ? {} : { usage: this.usage }),
|
|
26
30
|
...(this.upstreamCode === undefined ? {} : { upstreamCode: this.upstreamCode }),
|
|
31
|
+
...Object.fromEntries(['source', 'upstreamErrorCode', 'upstreamErrorType', 'retryAfterMs', 'retryable']
|
|
32
|
+
.filter(key => this[key] !== undefined).map(key => [key, this[key]])),
|
|
27
33
|
};
|
|
28
34
|
}
|
|
29
35
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { JsonValue, TransportOptions, ProviderCallOptions, BatchOptions, ErrorDetails } from '../index.mjs';
|
|
2
|
+
import type { CloudflareGatewayOptions, CloudflareBindingGatewayOptions, CloudflareTransportMetadata } from '../cloudflare-decisions.mjs';
|
|
3
|
+
|
|
4
|
+
export type ExtractionFieldType = 'text' | 'number' | 'date' | 'time' | 'datetime' | 'duration' | 'list' | 'estimate' | 'forecast';
|
|
5
|
+
export interface ExtractionField {
|
|
6
|
+
readonly type: ExtractionFieldType; readonly instructions?: string; readonly unit?: string;
|
|
7
|
+
readonly integer?: boolean; readonly decimals?: number; readonly items?: 'text' | 'number' | 'date';
|
|
8
|
+
readonly min?: number | string; readonly max?: number | string; readonly step?: number | 'month' | 'year';
|
|
9
|
+
readonly scale?: 'linear' | 'log'; readonly null_threshold?: number; readonly horizon?: number;
|
|
10
|
+
readonly interval?: 'step' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year';
|
|
11
|
+
}
|
|
12
|
+
export type ExtractionFields = Readonly<Record<string, ExtractionField>>;
|
|
13
|
+
export interface ExtractionRequest<F extends ExtractionFields = ExtractionFields> {
|
|
14
|
+
readonly model?: 'clairic' | '@cf/cloudflare/clairic'; readonly state?: string; readonly fields: F;
|
|
15
|
+
readonly images?: readonly (string | { readonly content_type: string; readonly base64: string })[];
|
|
16
|
+
readonly reference_date?: string;
|
|
17
|
+
readonly options?: { readonly include_quantiles?: boolean; readonly return_state_text?: boolean; readonly rejectIfBusy?: boolean };
|
|
18
|
+
}
|
|
19
|
+
export interface ExtractionAnswer {
|
|
20
|
+
readonly type: ExtractionFieldType; readonly value: JsonValue; readonly guess?: JsonValue;
|
|
21
|
+
readonly confidence?: number; readonly p_null?: number; readonly span?: readonly [number, number];
|
|
22
|
+
readonly spans?: readonly (readonly [number, number])[]; readonly mean?: JsonValue; readonly median?: JsonValue;
|
|
23
|
+
readonly p10?: JsonValue; readonly p90?: JsonValue; readonly p_below?: number; readonly p_above?: number;
|
|
24
|
+
readonly quantiles?: readonly JsonValue[]; readonly steps?: readonly JsonValue[];
|
|
25
|
+
readonly [key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
// Distribute over field/item unions so unspecialized and batch values stay honest.
|
|
28
|
+
type ListItemValue<I> = I extends 'number' ? number : I extends 'text' | 'date' ? string : number | string;
|
|
29
|
+
type FieldValue<T extends ExtractionFieldType, I> = T extends 'number' | 'duration' ? number | null
|
|
30
|
+
: T extends 'list' ? readonly ListItemValue<I>[] | null
|
|
31
|
+
: T extends 'estimate' ? number | string | null
|
|
32
|
+
: T extends 'forecast' ? JsonValue : string | null;
|
|
33
|
+
export type ExtractionValue<S extends ExtractionField> = FieldValue<S['type'], S['items']>;
|
|
34
|
+
export interface ExtractionResponse<F extends ExtractionFields = ExtractionFields> {
|
|
35
|
+
readonly model: string;
|
|
36
|
+
readonly answers: { readonly [K in keyof F]: ExtractionAnswer & { readonly type: F[K]['type']; readonly value: ExtractionValue<F[K]> } };
|
|
37
|
+
readonly truncated: boolean; readonly state_text?: string;
|
|
38
|
+
readonly usage: { readonly input_tokens: number; readonly output_tokens: number; readonly state_tokens?: number;
|
|
39
|
+
readonly image_tokens?: number; readonly schema_tokens?: number; readonly passes?: number; readonly [key: string]: unknown };
|
|
40
|
+
readonly [key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
export interface ExtractionResult<F extends ExtractionFields = ExtractionFields> {
|
|
43
|
+
readonly extraction: ExtractionResponse<F>;
|
|
44
|
+
readonly meta: CloudflareTransportMetadata & { readonly provider: 'cloudflare-extraction'; readonly requestedModel: string; readonly resolvedModel: string };
|
|
45
|
+
}
|
|
46
|
+
export interface CloudflareExtractionBinding {
|
|
47
|
+
run(model: '@cf/cloudflare/clairic', input: ExtractionRequest,
|
|
48
|
+
options?: { readonly signal?: AbortSignal; readonly returnRawResponse?: boolean; readonly rejectIfBusy?: boolean;
|
|
49
|
+
readonly gateway?: CloudflareBindingGatewayOptions }): Promise<unknown>;
|
|
50
|
+
}
|
|
51
|
+
type Settings = { model?: 'clairic' | '@cf/cloudflare/clairic'; rejectIfBusy?: boolean; gateway?: CloudflareGatewayOptions };
|
|
52
|
+
export type CloudflareExtractionOptions = Settings & (
|
|
53
|
+
| (TransportOptions & { transport?: 'rest'; accountId?: string; apiKey?: string; baseURL?: string; ai?: never })
|
|
54
|
+
| (Omit<TransportOptions, 'fetchImpl'> & { transport: 'binding'; ai: CloudflareExtractionBinding;
|
|
55
|
+
accountId?: never; apiKey?: never; baseURL?: never; fetchImpl?: never }));
|
|
56
|
+
export type ExtractionValidationOptions = Settings & { transport?: 'rest' | 'binding'; maxRequestBytes?: number; maxRetries?: number };
|
|
57
|
+
export function validateExtractionRequest<const F extends ExtractionFields>(request: ExtractionRequest<F>, options?: ExtractionValidationOptions): ExtractionRequest<F> & { readonly model: 'clairic' };
|
|
58
|
+
export class CloudflareExtractionProvider {
|
|
59
|
+
readonly name: 'cloudflare-extraction'; readonly kind: 'extraction'; readonly defaultModel: 'clairic';
|
|
60
|
+
constructor(options?: CloudflareExtractionOptions);
|
|
61
|
+
validate<const F extends ExtractionFields>(request: ExtractionRequest<F>): ExtractionRequest<F> & { readonly model: 'clairic' };
|
|
62
|
+
extract<const F extends ExtractionFields>(request: ExtractionRequest<F>, options?: ProviderCallOptions): Promise<ExtractionResult<F>>;
|
|
63
|
+
}
|
|
64
|
+
export type ExtractionBatchRecord = { readonly index: number; readonly ok: true; readonly result: ExtractionResult }
|
|
65
|
+
| { readonly index: number; readonly ok: false; readonly error: ErrorDetails };
|
|
66
|
+
export function extractBatch(provider: CloudflareExtractionProvider, inputs: readonly ExtractionRequest[], options?: BatchOptions): Promise<readonly ExtractionBatchRecord[]>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { optionsObject, TRANSPORT_FIELDS } from '../options.mjs';
|
|
2
|
+
import { DecisionError, requireCondition as check, withResponseMeta } from '../errors.mjs';
|
|
3
|
+
import { deadline } from '../async.mjs';
|
|
4
|
+
import { cloneJson, freezeJson } from '../validation.mjs';
|
|
5
|
+
import { CloudflareTransport } from '../providers/cloudflare-transport.mjs';
|
|
6
|
+
import { cloudflareGateway, validateCloudflareLimits } from '../providers/cloudflare-profile.mjs';
|
|
7
|
+
import { unwrapCloudflareResponse } from '../providers/cloudflare-validation.mjs';
|
|
8
|
+
import { validateExtractionRequest, validateExtractionResponse, extractionModel } from './validation.mjs';
|
|
9
|
+
import { evaluateBatch } from '../batch.mjs';
|
|
10
|
+
|
|
11
|
+
export { validateExtractionRequest } from './validation.mjs';
|
|
12
|
+
|
|
13
|
+
/** Typed extraction remains independent of the decision bridge's answer contract. */
|
|
14
|
+
export class CloudflareExtractionProvider {
|
|
15
|
+
name = 'cloudflare-extraction';
|
|
16
|
+
kind = 'extraction';
|
|
17
|
+
#settings;
|
|
18
|
+
#transport;
|
|
19
|
+
#timeoutMs;
|
|
20
|
+
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
const supplied = optionsObject(options, ['transport', 'accountId', 'apiKey', 'baseURL', 'ai', 'model',
|
|
23
|
+
'rejectIfBusy', 'gateway', ...TRANSPORT_FIELDS], 'Cloudflare extraction options');
|
|
24
|
+
const transport = supplied.transport === undefined ? 'rest' : supplied.transport;
|
|
25
|
+
check(['rest', 'binding'].includes(transport), 'Unknown Cloudflare transport.', 'CONFIGURATION_ERROR');
|
|
26
|
+
check(transport !== 'binding' || ['accountId', 'apiKey', 'baseURL', 'fetchImpl'].every(key => supplied[key] === undefined),
|
|
27
|
+
'Binding transport does not accept REST credentials or HTTP options.', 'CONFIGURATION_ERROR');
|
|
28
|
+
check(transport !== 'rest' || supplied.ai === undefined, 'REST transport does not accept an ai binding.', 'CONFIGURATION_ERROR');
|
|
29
|
+
this.defaultModel = extractionModel(supplied.model);
|
|
30
|
+
this.#timeoutMs = supplied.timeoutMs ?? 30000;
|
|
31
|
+
const gateway = cloudflareGateway(supplied.gateway);
|
|
32
|
+
this.#settings = { model: this.defaultModel, transport, gateway, ...validateCloudflareLimits({ ...supplied, gateway }) };
|
|
33
|
+
this.#transport = new CloudflareTransport({ ...supplied, ...this.#settings }, {
|
|
34
|
+
models: ['clairic'], requestLimit: (_model, ceiling) => ceiling ?? 8 * 1024 * 1024,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
validate(request) { return validateExtractionRequest(request, this.#settings); }
|
|
39
|
+
|
|
40
|
+
async extract(input, options = {}) {
|
|
41
|
+
const { signal } = optionsObject(options, ['signal'], 'Extraction call options');
|
|
42
|
+
const budget = deadline(this.#timeoutMs, signal);
|
|
43
|
+
try {
|
|
44
|
+
budget.throwIfAborted();
|
|
45
|
+
const snapshot = cloneJson(input, 'extraction request');
|
|
46
|
+
const request = this.validate(snapshot);
|
|
47
|
+
const requestedModel = snapshot.model ?? this.#settings.model;
|
|
48
|
+
budget.throwIfAborted();
|
|
49
|
+
const { data, meta } = await this.#transport.request(request, { signal: budget.signal });
|
|
50
|
+
const extraction = withResponseMeta(meta, () => validateExtractionResponse(
|
|
51
|
+
unwrapCloudflareResponse(data, this.#settings.transport, meta), request));
|
|
52
|
+
budget.throwIfAborted();
|
|
53
|
+
return freezeJson({ extraction, meta: { ...meta, provider: 'cloudflare-extraction',
|
|
54
|
+
requestedModel, resolvedModel: request.model } });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
const details = error instanceof DecisionError ? error.toJSON() : {};
|
|
57
|
+
if (signal?.aborted) throw new DecisionError('Extraction cancelled.', { ...details, code: 'ABORTED', source: 'client' });
|
|
58
|
+
if (budget.timedOut) throw new DecisionError('Extraction deadline exceeded.', { ...details, code: 'TIMEOUT', source: 'client' });
|
|
59
|
+
throw error;
|
|
60
|
+
} finally { budget.close(); }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Reuse the existing ordered, preflighted, caller-controlled batch implementation. */
|
|
65
|
+
export function extractBatch(provider, inputs, options = {}) {
|
|
66
|
+
check(provider && typeof provider.validate === 'function' && typeof provider.extract === 'function',
|
|
67
|
+
'An extraction provider is required.', 'CONFIGURATION_ERROR');
|
|
68
|
+
return evaluateBatch({
|
|
69
|
+
validate(input) {
|
|
70
|
+
// Preflight resolves the wire model, but dispatch must retain the caller's
|
|
71
|
+
// selection for diagnostics. Snapshot before validation and async work.
|
|
72
|
+
const request = cloneJson(input, 'extraction request');
|
|
73
|
+
provider.validate(request);
|
|
74
|
+
return freezeJson(request);
|
|
75
|
+
},
|
|
76
|
+
evaluate: (input, call) => provider.extract(input, call),
|
|
77
|
+
}, inputs, options);
|
|
78
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { requireCondition as check } from '../errors.mjs';
|
|
2
|
+
import { cloneJson, freezeJson, isRecord } from '../validation.mjs';
|
|
3
|
+
import { serializeRequestBody } from '../transport.mjs';
|
|
4
|
+
import { optionsObject } from '../options.mjs';
|
|
5
|
+
import { cloudflareBindingBody } from '../providers/cloudflare-preflight.mjs';
|
|
6
|
+
import { cloudflareGateway, validateCloudflareLimits } from '../providers/cloudflare-profile.mjs';
|
|
7
|
+
import { validateCloudflareImages } from '../providers/cloudflare-images.mjs';
|
|
8
|
+
|
|
9
|
+
const own = (value, key) => Object.hasOwn(value, key);
|
|
10
|
+
const TYPES = ['text', 'number', 'date', 'time', 'datetime', 'duration', 'list', 'estimate', 'forecast'];
|
|
11
|
+
const FIELD_KEYS = ['type', 'instructions', 'unit', 'integer', 'decimals', 'items', 'min', 'max',
|
|
12
|
+
'step', 'scale', 'null_threshold', 'horizon', 'interval'];
|
|
13
|
+
const isDate = value => typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value);
|
|
14
|
+
|
|
15
|
+
export function extractionModel(value = 'clairic') {
|
|
16
|
+
check(value === 'clairic' || value === '@cf/cloudflare/clairic', 'Unsupported extraction model.', 'UNSUPPORTED_CAPABILITY');
|
|
17
|
+
return 'clairic';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function field(spec) {
|
|
21
|
+
check(isRecord(spec) && Object.keys(spec).every(key => FIELD_KEYS.includes(key)) && TYPES.includes(spec.type), 'Invalid extraction field.');
|
|
22
|
+
for (const key of ['instructions', 'unit']) check(spec[key] === undefined || typeof spec[key] === 'string', `${key} must be text.`);
|
|
23
|
+
check(spec.integer === undefined || (['number', 'duration'].includes(spec.type) && typeof spec.integer === 'boolean'), 'Invalid integer option.');
|
|
24
|
+
check(spec.decimals === undefined || ((['number', 'duration'].includes(spec.type) || spec.type === 'list' && spec.items === 'number') &&
|
|
25
|
+
Number.isSafeInteger(spec.decimals) && spec.decimals >= 0), 'Invalid decimals option.');
|
|
26
|
+
if (spec.type === 'duration' && spec.unit !== undefined) check(['seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years'].includes(spec.unit), 'Invalid duration unit.');
|
|
27
|
+
if (spec.type === 'list') check(['text', 'number', 'date'].includes(spec.items), 'Lists require an item type.');
|
|
28
|
+
else check(spec.items === undefined, 'items applies only to lists.');
|
|
29
|
+
if (['estimate', 'forecast'].includes(spec.type)) {
|
|
30
|
+
check((typeof spec.min === 'number' && typeof spec.max === 'number' && spec.min <= spec.max) ||
|
|
31
|
+
(isDate(spec.min) && isDate(spec.max) && spec.min <= spec.max), 'Prediction fields require ordered numeric or ISO date bounds.');
|
|
32
|
+
} else check(spec.min === undefined && spec.max === undefined && spec.step === undefined && spec.scale === undefined,
|
|
33
|
+
'Prediction bounds apply only to estimates and forecasts.');
|
|
34
|
+
check(spec.step === undefined || (typeof spec.step === 'number' && spec.step > 0) || ['month', 'year'].includes(spec.step), 'Invalid prediction grid step.');
|
|
35
|
+
check(spec.scale === undefined || ['linear', 'log'].includes(spec.scale), 'Invalid prediction scale.');
|
|
36
|
+
if (spec.scale === 'log') check(typeof spec.min === 'number' && spec.min > 0, 'Log grids require positive numeric bounds.');
|
|
37
|
+
check(spec.null_threshold === undefined || (typeof spec.null_threshold === 'number' && spec.null_threshold > 0 && spec.null_threshold <= 1), 'Invalid null threshold.');
|
|
38
|
+
if (spec.type === 'forecast') {
|
|
39
|
+
check(spec.horizon === undefined || (Number.isSafeInteger(spec.horizon) && spec.horizon > 0), 'Invalid forecast horizon.');
|
|
40
|
+
check(spec.interval === undefined || ['step', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'].includes(spec.interval), 'Invalid forecast interval.');
|
|
41
|
+
} else check(spec.horizon === undefined && spec.interval === undefined, 'Forecast options require a forecast field.');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Credential-free preflight shares the exact wire budget with actual dispatch. */
|
|
45
|
+
export function validateExtractionRequest(input, options = {}) {
|
|
46
|
+
const settings = optionsObject(options, ['model', 'transport', 'gateway', 'maxRequestBytes', 'maxRetries', 'rejectIfBusy'], 'Extraction validation options');
|
|
47
|
+
const request = cloneJson(input, 'extraction request');
|
|
48
|
+
check(isRecord(request) && Object.keys(request).every(key => ['model', 'state', 'fields', 'images', 'reference_date', 'options'].includes(key)), 'Invalid extraction request fields.');
|
|
49
|
+
const model = extractionModel(own(request, 'model') ? request.model : settings.model);
|
|
50
|
+
check(isRecord(request.fields) && Object.keys(request.fields).length > 0, 'Extraction requires named fields.');
|
|
51
|
+
for (const [name, spec] of Object.entries(request.fields)) {
|
|
52
|
+
check(/^[A-Za-z_][A-Za-z0-9_.-]*$/.test(name), 'Invalid extraction field name.');
|
|
53
|
+
field(spec);
|
|
54
|
+
}
|
|
55
|
+
check(request.state === undefined || typeof request.state === 'string', 'Extraction state must be text.');
|
|
56
|
+
check((request.state?.length ?? 0) > 0 || (Array.isArray(request.images) && request.images.length > 0), 'Provide text or embedded images.');
|
|
57
|
+
check(request.reference_date === undefined || isDate(request.reference_date), 'reference_date must be an ISO date.');
|
|
58
|
+
const inference = own(request, 'options') ? request.options : {};
|
|
59
|
+
check(isRecord(inference) && Object.keys(inference).every(key => ['include_quantiles', 'return_state_text', 'rejectIfBusy'].includes(key)) &&
|
|
60
|
+
Object.values(inference).every(value => typeof value === 'boolean'), 'Invalid extraction options.');
|
|
61
|
+
const transport = settings.transport === undefined ? 'rest' : settings.transport;
|
|
62
|
+
check(['rest', 'binding'].includes(transport), 'Unknown Cloudflare transport.', 'CONFIGURATION_ERROR');
|
|
63
|
+
const gateway = cloudflareGateway(settings.gateway);
|
|
64
|
+
const rejectIfBusy = inference.rejectIfBusy ?? settings.rejectIfBusy ?? false;
|
|
65
|
+
validateCloudflareLimits({ ...settings, gateway, rejectIfBusy });
|
|
66
|
+
const result = { ...request, model, ...(rejectIfBusy ? { options: { ...inference, rejectIfBusy } } : {}) };
|
|
67
|
+
serializeRequestBody(transport === 'binding' ? cloudflareBindingBody(result, gateway) : result, settings.maxRequestBytes);
|
|
68
|
+
if (own(request, 'images')) validateCloudflareImages(request.images);
|
|
69
|
+
return freezeJson(result);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Validate established fields without guessing predictive structures or span units. */
|
|
73
|
+
export function validateExtractionResponse(input, request) {
|
|
74
|
+
const data = cloneJson(input, 'extraction response', 'INVALID_RESPONSE');
|
|
75
|
+
const invalid = (condition, message) => check(condition, message, 'INVALID_RESPONSE');
|
|
76
|
+
invalid(isRecord(data) && typeof data.model === 'string' && data.model.trim() && isRecord(data.answers) &&
|
|
77
|
+
typeof data.truncated === 'boolean' && isRecord(data.usage), 'Invalid extraction response.');
|
|
78
|
+
invalid(Object.keys(data.answers).length === Object.keys(request.fields).length, 'Extraction answer count mismatch.');
|
|
79
|
+
invalid(data.state_text === undefined || typeof data.state_text === 'string', 'Invalid returned state text.');
|
|
80
|
+
for (const key of ['input_tokens', 'output_tokens', 'state_tokens', 'image_tokens', 'schema_tokens', 'passes']) {
|
|
81
|
+
if (['input_tokens', 'output_tokens'].includes(key) || own(data.usage, key)) {
|
|
82
|
+
invalid(Number.isSafeInteger(data.usage[key]) && data.usage[key] >= 0, 'Invalid extraction usage.');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const [name, spec] of Object.entries(request.fields)) {
|
|
86
|
+
invalid(own(data.answers, name), 'Missing extraction answer.');
|
|
87
|
+
const answer = data.answers[name];
|
|
88
|
+
invalid(isRecord(answer) && answer.type === spec.type && own(answer, 'value'), 'Extraction answer type mismatch.');
|
|
89
|
+
const value = answer.value;
|
|
90
|
+
if (value !== null && spec.type !== 'forecast') {
|
|
91
|
+
if (spec.type === 'list') invalid(Array.isArray(value) && value.every(item =>
|
|
92
|
+
typeof item === (spec.items === 'number' ? 'number' : 'string')), 'Invalid extraction list.');
|
|
93
|
+
else invalid(typeof value === (['number', 'duration'].includes(spec.type) ? 'number'
|
|
94
|
+
: spec.type === 'estimate' ? typeof spec.min : 'string'), 'Invalid extraction value.');
|
|
95
|
+
}
|
|
96
|
+
for (const key of ['confidence', 'p_null', 'p_below', 'p_above']) if (own(answer, key)) {
|
|
97
|
+
invalid(typeof answer[key] === 'number' && answer[key] >= 0 && answer[key] <= 1, 'Invalid extraction probability.');
|
|
98
|
+
}
|
|
99
|
+
const span = item => Array.isArray(item) && item.length === 2 && item.every(n => Number.isSafeInteger(n) && n >= 0) && item[1] >= item[0];
|
|
100
|
+
if (own(answer, 'span')) invalid(span(answer.span), 'Invalid extraction span.');
|
|
101
|
+
if (own(answer, 'spans')) invalid(Array.isArray(answer.spans) && answer.spans.every(span), 'Invalid extraction spans.');
|
|
102
|
+
for (const key of ['quantiles', 'steps']) if (own(answer, key)) invalid(Array.isArray(answer[key]), 'Invalid predictive array.');
|
|
103
|
+
}
|
|
104
|
+
return freezeJson(data);
|
|
105
|
+
}
|
package/src/index.d.mts
CHANGED
|
@@ -3,7 +3,8 @@ import type { JevDangerZone, JevQuestions, JevDecisionRequest, JevDecisionRespon
|
|
|
3
3
|
export type { JevDangerZone, JevImageAsset, JevQuestions, JevDecisionRequest, JevDecisionResponse, JevTransportMetadata } from './jev.d.mts';
|
|
4
4
|
import type { NativeDecisionRequest, OpenAIDecisionResponse, OpenAIDecisionUsage,
|
|
5
5
|
OpenAIDecisionsMetadata, OpenAIDecisionsTransportMetadata } from './decisions.d.mts';
|
|
6
|
-
export type { NativeDecisionInputPart,
|
|
6
|
+
export type { NativeDecisionInputPart, NativeDecisionInputItem, DecisionCacheBreakpoint, DecisionCacheOptions,
|
|
7
|
+
NativeDecisionMessage, NativeDecisionQuestion, NativeDecisionRequest,
|
|
7
8
|
NativeDecisionAnswer, OpenAIDecisionResponse, OpenAIDecisionUsage, OpenAIDecisionsMetadata,
|
|
8
9
|
OpenAIDecisionsTransportMetadata, TransportMetadata } from './decisions.d.mts';
|
|
9
10
|
import type { CloudflareDecisionsOptions, CloudflareDecisionRequest, CloudflareDecisionResponse,
|
|
@@ -51,8 +52,12 @@ export type ScoreLevels = readonly [ScoreLevel, ScoreLevel, ...ScoreLevel[]];
|
|
|
51
52
|
export interface LabeledScoreQuestion<L extends ScoreLevels = ScoreLevels> {
|
|
52
53
|
readonly type: 'score'; readonly instructions: Instructions; readonly levels: L; readonly criteria?: never;
|
|
53
54
|
}
|
|
54
|
-
export type
|
|
55
|
-
|
|
55
|
+
export type DecisionConversationItem = import('./decisions.mjs').NativeDecisionInputItem;
|
|
56
|
+
export type DecisionInputPart = { readonly type: 'text'; readonly text: string;
|
|
57
|
+
readonly promptCacheBreakpoint?: import('./decisions.mjs').DecisionCacheBreakpoint | null }
|
|
58
|
+
| ({ readonly type: 'image'; readonly detail?: 'low' | 'high' | 'auto' | 'original' | null;
|
|
59
|
+
readonly promptCacheBreakpoint?: import('./decisions.mjs').DecisionCacheBreakpoint | null }
|
|
60
|
+
& ({ readonly dataUrl: string; readonly url?: never } | { readonly url: string; readonly dataUrl?: never }))
|
|
56
61
|
| { readonly type: 'audio'; readonly dataUrl: string }
|
|
57
62
|
| { readonly type: 'video'; readonly dataUrl: string };
|
|
58
63
|
export type OrderedInput = readonly DecisionInputPart[];
|
|
@@ -85,7 +90,11 @@ export type AnswerFor<Q extends Question> = Q extends TypedChoiceQuestion<infer
|
|
|
85
90
|
? ChoiceAnswer<ChoiceLabel<keyof C>> : Q extends NoulQuestion ? NoulAnswer : Q extends BoxQuestion ? BoxAnswer : ScoreAnswer;
|
|
86
91
|
export type EvaluationRequest<Q extends Questions = Questions> = {
|
|
87
92
|
readonly questions: Q; readonly model?: string; readonly safetyIdentifier?: string | null;
|
|
88
|
-
|
|
93
|
+
readonly promptCacheKey?: string | null;
|
|
94
|
+
readonly promptCacheOptions?: import('./decisions.mjs').DecisionCacheOptions | null;
|
|
95
|
+
} & ({ readonly state: State; readonly input?: never; readonly conversation?: never }
|
|
96
|
+
| { readonly input: OrderedInput; readonly state?: never; readonly conversation?: never }
|
|
97
|
+
| { readonly conversation: readonly DecisionConversationItem[]; readonly state?: never; readonly input?: never });
|
|
89
98
|
export type ResolvedRequest<Q extends Questions = Questions> = EvaluationRequest<Q> & { readonly model: string };
|
|
90
99
|
export interface Usage {
|
|
91
100
|
/** Null means not reported, not zero tokens. */
|
|
@@ -136,6 +145,7 @@ export interface Provider {
|
|
|
136
145
|
}
|
|
137
146
|
export interface DecisionProvider extends Provider { readonly kind: 'decision'; }
|
|
138
147
|
export interface Capabilities {
|
|
148
|
+
readonly conversation?: boolean; readonly toolHistory?: boolean; readonly remoteImages?: boolean; readonly promptCaching?: boolean;
|
|
139
149
|
readonly boxes?: boolean;
|
|
140
150
|
readonly audio?: boolean; readonly videos?: boolean;
|
|
141
151
|
readonly maxAudioClips?: number | null; readonly maxVideos?: number | null;
|
|
@@ -151,6 +161,8 @@ export interface Extension {
|
|
|
151
161
|
name: string;
|
|
152
162
|
prepareState?(state: State, context: ExtensionContext): State | undefined | Promise<State | undefined>;
|
|
153
163
|
prepareInput?(input: OrderedInput, context: ExtensionContext): OrderedInput | undefined | Promise<OrderedInput | undefined>;
|
|
164
|
+
prepareConversation?(conversation: readonly DecisionConversationItem[], context: ExtensionContext):
|
|
165
|
+
readonly DecisionConversationItem[] | undefined | Promise<readonly DecisionConversationItem[] | undefined>;
|
|
154
166
|
/** Observer failure is isolated in meta.observerFailures; no inference is retried. */
|
|
155
167
|
onResult?(result: EvaluationResult, context: ExtensionContext): void | Promise<void>;
|
|
156
168
|
}
|
|
@@ -233,8 +245,10 @@ export function decideChoice<K extends string>(answer: ChoiceAnswer<K>, options:
|
|
|
233
245
|
export function decideChoice<V extends string | boolean>(answer: TypedChoiceAnswer<V>, options: ChoicePolicyOptions<V>): ChoiceDecision<V>;
|
|
234
246
|
export interface RefusedQuestion { readonly index: number; readonly name: string | null; }
|
|
235
247
|
export interface ErrorDetails { code: string; message: string; status?: number; requestId?: string; gatewayLogId?: string; attempts?: number; upstreamCode?: number;
|
|
248
|
+
source?: 'client' | 'provider'; upstreamErrorCode?: string; upstreamErrorType?: string; retryAfterMs?: number; retryable?: boolean;
|
|
236
249
|
refusedQuestions?: readonly RefusedQuestion[]; usage?: OpenAIDecisionUsage; }
|
|
237
250
|
export class DecisionError extends Error {
|
|
251
|
+
source?: 'client' | 'provider'; upstreamErrorCode?: string; upstreamErrorType?: string; retryAfterMs?: number; retryable?: boolean;
|
|
238
252
|
code: string; status?: number; requestId?: string; gatewayLogId?: string; attempts?: number; upstreamCode?: number;
|
|
239
253
|
refusedQuestions?: readonly RefusedQuestion[]; usage?: OpenAIDecisionUsage;
|
|
240
254
|
constructor(message: string, details?: Omit<ErrorDetails, 'code' | 'message'> & { code?: string });
|
package/src/input.mjs
CHANGED
|
@@ -20,17 +20,35 @@ export function validateImage(dataUrl, detail, code = 'VALIDATION_ERROR') {
|
|
|
20
20
|
'Unsupported image detail.', code);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** A public reference is forwarded to the provider; the SDK never fetches it. */
|
|
24
|
+
export function validateImageSource(value, detail, code = 'VALIDATION_ERROR') {
|
|
25
|
+
if (typeof value === 'string' && /^https?:/i.test(value)) {
|
|
26
|
+
let url;
|
|
27
|
+
try { url = new URL(value); } catch { /* Validated below. */ }
|
|
28
|
+
check(url && ['http:', 'https:'].includes(url.protocol) && !url.username && !url.password,
|
|
29
|
+
'Images require a public HTTP(S) URL.', code);
|
|
30
|
+
check(detail === undefined || detail === null || ['low', 'high', 'auto', 'original'].includes(detail),
|
|
31
|
+
'Unsupported image detail.', code);
|
|
32
|
+
} else validateImage(value, detail, code);
|
|
33
|
+
}
|
|
34
|
+
|
|
23
35
|
export function validateOrderedInput(input) {
|
|
24
36
|
check(Array.isArray(input) && input.length > 0, 'input must be a nonempty ordered array.');
|
|
25
37
|
for (const part of input) {
|
|
26
38
|
check(part !== null && typeof part === 'object' && !Array.isArray(part), 'Input parts must be objects.');
|
|
27
|
-
const fields = part.type === 'text' ? ['type', 'text'] : part.type === 'image'
|
|
28
|
-
? ['type', 'dataUrl', 'detail'] : ['type', 'dataUrl'];
|
|
39
|
+
const fields = part.type === 'text' ? ['type', 'text', 'promptCacheBreakpoint'] : part.type === 'image'
|
|
40
|
+
? ['type', 'dataUrl', 'url', 'detail', 'promptCacheBreakpoint'] : ['type', 'dataUrl'];
|
|
29
41
|
check(Object.keys(part).every(key => fields.includes(key)), 'Unknown input part field.');
|
|
30
42
|
if (part.type === 'text') check(typeof part.text === 'string', 'Text parts require text.');
|
|
31
43
|
else {
|
|
32
44
|
check(['image', 'audio', 'video'].includes(part.type), 'Input parts must be text, image, audio, or video.');
|
|
33
|
-
if (part.type === 'image')
|
|
45
|
+
if (part.type === 'image') {
|
|
46
|
+
check(Object.hasOwn(part, 'dataUrl') !== Object.hasOwn(part, 'url'), 'Image requires exactly one of dataUrl or url.');
|
|
47
|
+
if (Object.hasOwn(part, 'url')) {
|
|
48
|
+
check(typeof part.url === 'string' && /^https?:/i.test(part.url), 'url must use HTTP(S).');
|
|
49
|
+
validateImageSource(part.url, part.detail);
|
|
50
|
+
} else validateImage(part.dataUrl, part.detail);
|
|
51
|
+
}
|
|
34
52
|
else embeddedDataUrl(part.dataUrl, part.type);
|
|
35
53
|
}
|
|
36
54
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { CallOptions, ErrorDetails, JsonValue } from '../index.mjs';
|
|
2
|
+
import type { NativeDecisionQuestion, NativeDecisionAnswer, OpenAIDecisionResponse } from '../decisions.mjs';
|
|
3
|
+
|
|
4
|
+
export type LiveTranscriptType = 'session.input_transcript.delta' | 'session.output_transcript.delta';
|
|
5
|
+
export interface LiveDecisionsConfiguration {
|
|
6
|
+
readonly delegation?: { readonly type: 'decisions'; readonly decisions: {
|
|
7
|
+
readonly model?: 'gpt-6-luna'; readonly questions?: readonly NativeDecisionQuestion[] } };
|
|
8
|
+
readonly triggers?: readonly { readonly type: 'decisions'; readonly event_type: LiveTranscriptType;
|
|
9
|
+
readonly decisions: { readonly questions: readonly NativeDecisionQuestion[] } }[];
|
|
10
|
+
}
|
|
11
|
+
export interface LiveDecisionItem {
|
|
12
|
+
readonly id?: string | null; readonly role: 'user'; readonly type?: 'message';
|
|
13
|
+
readonly content: string | readonly ({ readonly type: 'input_text'; readonly text: string }
|
|
14
|
+
| { readonly type: 'input_image'; readonly image_url: string; readonly detail?: 'low' | 'high' | 'auto' | 'original' | null })[];
|
|
15
|
+
}
|
|
16
|
+
export type LiveCommand = (
|
|
17
|
+
| { readonly type: 'session.update'; readonly session: LiveDecisionsConfiguration }
|
|
18
|
+
| { readonly type: 'decision.item.create'; readonly item: LiveDecisionItem }
|
|
19
|
+
| { readonly type: 'decision.item.delete'; readonly item_id: string }
|
|
20
|
+
| { readonly type: 'session.commentary.append'; readonly delegation_id: string | null; readonly content: string }
|
|
21
|
+
) & { readonly event_id?: string | null }
|
|
22
|
+
| { readonly type: 'decision.create'; readonly event_id: string };
|
|
23
|
+
export type LiveNativeDecision = Omit<OpenAIDecisionResponse, 'answers'> & {
|
|
24
|
+
readonly answers: readonly (NativeDecisionAnswer | { readonly type: 'refusal'; readonly name: string | null })[];
|
|
25
|
+
};
|
|
26
|
+
export interface LiveDecisionEvent {
|
|
27
|
+
readonly type: 'decision.event'; readonly event_id: string; readonly delegation_id: string | null;
|
|
28
|
+
readonly trigger_id: string | null; readonly source_event_id: string | null; readonly source_event_type: LiveTranscriptType | null;
|
|
29
|
+
readonly event: { readonly type: 'decision.completed'; readonly decision: LiveNativeDecision };
|
|
30
|
+
}
|
|
31
|
+
export type LiveServerEvent = LiveDecisionEvent | { readonly type: string; readonly event_id: string; readonly [key: string]: unknown };
|
|
32
|
+
export type LiveTransportNotification = { readonly kind: 'event'; readonly event: unknown }
|
|
33
|
+
| { readonly kind: 'close' | 'error' };
|
|
34
|
+
export interface LiveTransport {
|
|
35
|
+
send(event: LiveCommand): void | Promise<void>;
|
|
36
|
+
subscribe(listener: (notification: LiveTransportNotification) => void): () => void;
|
|
37
|
+
}
|
|
38
|
+
interface EventMetadata { readonly eventId: string; readonly eventType: string; }
|
|
39
|
+
export interface LiveCorrelation {
|
|
40
|
+
readonly matched: boolean; readonly stale: boolean; readonly sourceEventId: string | null;
|
|
41
|
+
readonly sourceEventType: LiveTranscriptType | null; readonly delegationId: string | null; readonly triggerId: string | null;
|
|
42
|
+
readonly generation?: number; readonly reason?: 'source_or_configuration_unavailable' | 'configuration_mismatch';
|
|
43
|
+
}
|
|
44
|
+
export type LiveConfigurationEvent = EventMetadata & { readonly type: 'configuration';
|
|
45
|
+
readonly configuration: LiveDecisionsConfiguration; readonly generation: number };
|
|
46
|
+
export type LiveItemEvent = EventMetadata & { readonly type: 'item'; readonly item?: LiveDecisionItem; readonly itemId?: string };
|
|
47
|
+
export type LiveNotification =
|
|
48
|
+
| LiveConfigurationEvent | LiveItemEvent
|
|
49
|
+
| (EventMetadata & { readonly type: 'decision'; readonly decision: LiveNativeDecision; readonly correlation: LiveCorrelation })
|
|
50
|
+
| (EventMetadata & { readonly type: 'transcript'; readonly direction: 'input' | 'output'; readonly delta: string; readonly sequence: number })
|
|
51
|
+
| (EventMetadata & { readonly type: 'delegation'; readonly delegationId: string })
|
|
52
|
+
| (EventMetadata & { readonly type: 'duplicate' })
|
|
53
|
+
| { readonly type: 'error' | 'observer_error' | 'closed'; readonly error?: ErrorDetails;
|
|
54
|
+
readonly eventId?: string; readonly eventType?: string; readonly clientEventId?: string };
|
|
55
|
+
export interface LiveSubmissionReceipt { readonly eventId: string; readonly type: string; readonly status: 'submitted'; }
|
|
56
|
+
export interface LiveDecisionsOptions {
|
|
57
|
+
readonly transport: LiveTransport; readonly configuration?: LiveDecisionsConfiguration;
|
|
58
|
+
readonly timeoutMs?: number; readonly maxPending?: number; readonly maxHistory?: number;
|
|
59
|
+
readonly maxRequestBytes?: number; readonly maxResponseBytes?: number;
|
|
60
|
+
}
|
|
61
|
+
export interface LiveDecisionsController {
|
|
62
|
+
configure(configuration: LiveDecisionsConfiguration, options?: CallOptions): Promise<LiveConfigurationEvent>;
|
|
63
|
+
createItem(item: LiveDecisionItem, options?: CallOptions): Promise<LiveItemEvent>;
|
|
64
|
+
deleteItem(itemId: string, options?: CallOptions): Promise<LiveItemEvent>;
|
|
65
|
+
requestDecision(options?: CallOptions): Promise<LiveSubmissionReceipt>;
|
|
66
|
+
appendCommentary(content: string, options?: CallOptions & { delegationId?: string | null }): Promise<LiveSubmissionReceipt>;
|
|
67
|
+
subscribe(observer: (event: LiveNotification) => void | Promise<void>): () => void;
|
|
68
|
+
dispose(): void;
|
|
69
|
+
}
|
|
70
|
+
export function createLiveDecisions(options: LiveDecisionsOptions): LiveDecisionsController;
|
|
71
|
+
export function validateLiveConfiguration(input: unknown): LiveDecisionsConfiguration;
|
|
72
|
+
export function validateLiveCommand(input: unknown): LiveCommand;
|
|
73
|
+
export function validateLiveEvent(input: unknown): LiveServerEvent;
|