@dsh-jev/core 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/dist/index.d.ts +74 -0
- package/dist/index.js +102 -0
- package/package.json +26 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dsh-jev/core — jev System One client.
|
|
3
|
+
*
|
|
4
|
+
* Contract: every public call RESOLVES and never rejects. On any failure
|
|
5
|
+
* (network error, timeout, non-2xx, malformed response) the call degrades to
|
|
6
|
+
* the caller-supplied fallback and reports `degraded: true` plus an `error`
|
|
7
|
+
* reason. Callers must be able to rely on `value` always being present.
|
|
8
|
+
*/
|
|
9
|
+
export declare const JEV_DEFAULT_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
|
|
10
|
+
export declare const JEV_DEFAULT_TIMEOUT_MS = 4000;
|
|
11
|
+
export type JevPrimitive = 'choice' | 'score' | 'noul';
|
|
12
|
+
/** Envelope returned by every public method. */
|
|
13
|
+
export interface JevOutcome<T> {
|
|
14
|
+
/** true when the remote call succeeded, false when a fallback was used. */
|
|
15
|
+
ok: boolean;
|
|
16
|
+
/** The usable value — remote answer on success, fallback otherwise. */
|
|
17
|
+
value: T;
|
|
18
|
+
/** Human-readable failure reason when ok === false. */
|
|
19
|
+
error?: string;
|
|
20
|
+
/** Milliseconds the call took. */
|
|
21
|
+
durationMs: number;
|
|
22
|
+
}
|
|
23
|
+
export interface JevClientOptions {
|
|
24
|
+
/** Bearer token; read from JEV_API_KEY when omitted. */
|
|
25
|
+
apiKey?: string;
|
|
26
|
+
endpoint?: string;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
/** Injectable fetch for tests. */
|
|
29
|
+
fetchImpl?: typeof fetch;
|
|
30
|
+
}
|
|
31
|
+
export interface ChoiceRequest {
|
|
32
|
+
question: string;
|
|
33
|
+
options: string[];
|
|
34
|
+
/** Optional free-form context forwarded to System One. */
|
|
35
|
+
context?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
export interface ScoreRequest {
|
|
38
|
+
subject: string;
|
|
39
|
+
criteria: string[];
|
|
40
|
+
context?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
export interface NoulRequest {
|
|
43
|
+
prompt: string;
|
|
44
|
+
context?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
export interface ChoiceAnswer {
|
|
47
|
+
pickedIndex: number;
|
|
48
|
+
picked: string;
|
|
49
|
+
rationale?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface ScoreAnswer {
|
|
52
|
+
/** One score per criterion, in order, each in [0, 1]. */
|
|
53
|
+
scores: number[];
|
|
54
|
+
rationale?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface NoulAnswer {
|
|
57
|
+
text: string;
|
|
58
|
+
}
|
|
59
|
+
export declare class JevClient {
|
|
60
|
+
private readonly endpoint;
|
|
61
|
+
private readonly apiKey?;
|
|
62
|
+
private readonly timeoutMs;
|
|
63
|
+
private readonly fetchImpl;
|
|
64
|
+
constructor(opts?: JevClientOptions);
|
|
65
|
+
/** Pick one option; falls back to `fallback.pickedIndex`. */
|
|
66
|
+
choice(req: ChoiceRequest, fallback: ChoiceAnswer): Promise<JevOutcome<ChoiceAnswer>>;
|
|
67
|
+
/** Score a subject against criteria; falls back to `fallback.scores`. */
|
|
68
|
+
score(req: ScoreRequest, fallback: ScoreAnswer): Promise<JevOutcome<ScoreAnswer>>;
|
|
69
|
+
/** Open "noul" (no-output unconstrained) reflection; falls back to `fallback.text`. */
|
|
70
|
+
noul(req: NoulRequest, fallback: NoulAnswer): Promise<JevOutcome<NoulAnswer>>;
|
|
71
|
+
private call;
|
|
72
|
+
}
|
|
73
|
+
/** Convenience: create a shared default client. */
|
|
74
|
+
export declare function createJevClient(opts?: JevClientOptions): JevClient;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dsh-jev/core — jev System One client.
|
|
3
|
+
*
|
|
4
|
+
* Contract: every public call RESOLVES and never rejects. On any failure
|
|
5
|
+
* (network error, timeout, non-2xx, malformed response) the call degrades to
|
|
6
|
+
* the caller-supplied fallback and reports `degraded: true` plus an `error`
|
|
7
|
+
* reason. Callers must be able to rely on `value` always being present.
|
|
8
|
+
*/
|
|
9
|
+
export const JEV_DEFAULT_ENDPOINT = 'https://api.typesafe.ai/v1/systemone';
|
|
10
|
+
export const JEV_DEFAULT_TIMEOUT_MS = 4_000;
|
|
11
|
+
function envApiKey() {
|
|
12
|
+
return typeof process !== 'undefined' ? process.env?.JEV_API_KEY : undefined;
|
|
13
|
+
}
|
|
14
|
+
export class JevClient {
|
|
15
|
+
endpoint;
|
|
16
|
+
apiKey;
|
|
17
|
+
timeoutMs;
|
|
18
|
+
fetchImpl;
|
|
19
|
+
constructor(opts = {}) {
|
|
20
|
+
this.endpoint = (opts.endpoint ?? JEV_DEFAULT_ENDPOINT).replace(/\/+$/, '');
|
|
21
|
+
this.apiKey = opts.apiKey ?? envApiKey();
|
|
22
|
+
this.timeoutMs = opts.timeoutMs ?? JEV_DEFAULT_TIMEOUT_MS;
|
|
23
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
24
|
+
}
|
|
25
|
+
/** Pick one option; falls back to `fallback.pickedIndex`. */
|
|
26
|
+
async choice(req, fallback) {
|
|
27
|
+
return this.call('choice', { ...req }, fallback, (raw) => {
|
|
28
|
+
const r = raw;
|
|
29
|
+
const idx = Number(r?.pickedIndex);
|
|
30
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= req.options.length) {
|
|
31
|
+
throw new Error(`pickedIndex out of range: ${JSON.stringify(r?.pickedIndex)}`);
|
|
32
|
+
}
|
|
33
|
+
return { pickedIndex: idx, picked: req.options[idx], rationale: r?.rationale };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** Score a subject against criteria; falls back to `fallback.scores`. */
|
|
37
|
+
async score(req, fallback) {
|
|
38
|
+
return this.call('score', { ...req }, fallback, (raw) => {
|
|
39
|
+
const r = raw;
|
|
40
|
+
const arr = Array.isArray(r?.scores) ? r.scores : [];
|
|
41
|
+
if (arr.length !== req.criteria.length) {
|
|
42
|
+
throw new Error(`expected ${req.criteria.length} scores, got ${arr.length}`);
|
|
43
|
+
}
|
|
44
|
+
const scores = arr.map((s) => Number(s));
|
|
45
|
+
if (scores.some((s) => !Number.isFinite(s))) {
|
|
46
|
+
throw new Error('non-numeric score in response');
|
|
47
|
+
}
|
|
48
|
+
return { scores, rationale: r?.rationale };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** Open "noul" (no-output unconstrained) reflection; falls back to `fallback.text`. */
|
|
52
|
+
async noul(req, fallback) {
|
|
53
|
+
return this.call('noul', { ...req }, fallback, (raw) => {
|
|
54
|
+
const r = raw;
|
|
55
|
+
if (typeof r?.text !== 'string' || r.text.length === 0) {
|
|
56
|
+
throw new Error('missing text in response');
|
|
57
|
+
}
|
|
58
|
+
return { text: r.text, rationale: r.rationale };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async call(primitive, body, fallback, parse) {
|
|
62
|
+
const started = Date.now();
|
|
63
|
+
const fail = (error) => ({
|
|
64
|
+
ok: false,
|
|
65
|
+
value: fallback,
|
|
66
|
+
error,
|
|
67
|
+
durationMs: Date.now() - started,
|
|
68
|
+
});
|
|
69
|
+
try {
|
|
70
|
+
const headers = {
|
|
71
|
+
'content-type': 'application/json',
|
|
72
|
+
accept: 'application/json',
|
|
73
|
+
};
|
|
74
|
+
if (this.apiKey)
|
|
75
|
+
headers.authorization = `Bearer ${this.apiKey}`;
|
|
76
|
+
const res = await this.fetchImpl(this.endpoint, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers,
|
|
79
|
+
body: JSON.stringify({ primitive, ...body }),
|
|
80
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok) {
|
|
83
|
+
return fail(`HTTP ${res.status}`);
|
|
84
|
+
}
|
|
85
|
+
const json = await res.json().catch(() => null);
|
|
86
|
+
if (json === null || typeof json !== 'object') {
|
|
87
|
+
return fail('malformed JSON response');
|
|
88
|
+
}
|
|
89
|
+
const raw = json.answer;
|
|
90
|
+
const value = parse(raw);
|
|
91
|
+
return { ok: true, value, durationMs: Date.now() - started };
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
95
|
+
return fail(reason);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Convenience: create a shared default client. */
|
|
100
|
+
export function createJevClient(opts = {}) {
|
|
101
|
+
return new JevClient(opts);
|
|
102
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dsh-jev/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "jev System One client: choice/score/noul primitives, timeout + degrade semantics, never throws",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.json",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20.0.0",
|
|
22
|
+
"typescript": "^5.6.0",
|
|
23
|
+
"vitest": "^2.1.0"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|