@molecule/api-ai-decisions-jeff 1.0.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/LICENSE +115 -0
- package/README.md +385 -0
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +19 -0
- package/dist/browser-guard.js.map +1 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/provider.d.ts +39 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +135 -0
- package/dist/provider.js.map +1 -0
- package/dist/secrets.d.ts +11 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +27 -0
- package/dist/secrets.js.map +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/wire.d.ts +91 -0
- package/dist/wire.d.ts.map +1 -0
- package/dist/wire.js +198 -0
- package/dist/wire.js.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jeff decisions provider for molecule.dev — typed decisions from the
|
|
3
|
+
* open-weights Jeff models on a `jeff-serve` host you run.
|
|
4
|
+
*
|
|
5
|
+
* Jeff (github.com/firelex/jeff; code MIT, weights Apache-2.0) is a family of
|
|
6
|
+
* fine-tunes of Qwen3.5-0.8B, Qwen3.5-2B and Gemma 4 E2B that answer
|
|
7
|
+
* `choice`, `score` and yes/no questions with a probability per option — no
|
|
8
|
+
* text generation. Its maker reports ~22 ms per request for the 0.8B on an
|
|
9
|
+
* NVIDIA RTX PRO 6000 and ~28 ms on an Apple M4 Max. `jeff-serve` speaks
|
|
10
|
+
* TypeSafe Jev's `/v1/systemone` protocol, so swapping to
|
|
11
|
+
* `@molecule/api-ai-decisions-jev` or `-laya` is a one-line change.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```bash
|
|
15
|
+
* # Run the model server (NVIDIA GPU, or Apple silicon with JEFF_BACKEND=mlx)
|
|
16
|
+
* git clone https://github.com/firelex/jeff && cd jeff && uv sync
|
|
17
|
+
* uv hf download mstrasser/Jeff-Qwen3.5-0.8B --local-dir checkpoints/jeff-0.8b
|
|
18
|
+
* JEFF_API_KEY=change-me JEFF_CHECKPOINT=checkpoints/jeff-0.8b PORT=8000 uv run jeff-serve
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { setProvider, requireProvider } from '@molecule/api-ai-decisions'
|
|
23
|
+
* import { provider } from '@molecule/api-ai-decisions-jeff'
|
|
24
|
+
*
|
|
25
|
+
* setProvider(provider) // reads JEFF_URL + JEFF_API_KEY on first use
|
|
26
|
+
*
|
|
27
|
+
* const { answers } = await requireProvider().decide({
|
|
28
|
+
* state: 'My card was charged twice, please refund one of them',
|
|
29
|
+
* questions: {
|
|
30
|
+
* queue: { type: 'choice', instructions: 'Which team?', criteria: { billing: 'charges, refunds', tech: 'bugs, login' } },
|
|
31
|
+
* refund: { type: 'yesNo', instructions: 'The customer wants a refund.' },
|
|
32
|
+
* },
|
|
33
|
+
* })
|
|
34
|
+
* answers.queue.choice // 'billing'
|
|
35
|
+
* answers.refund.probability // 0.95
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* - **You run the model.** `JEFF_URL` (default `http://localhost:8000`) points
|
|
40
|
+
* at a `jeff-serve` host. Its code defaults to port 8000 but its README
|
|
41
|
+
* examples use `PORT=8765` — set `JEFF_URL` to match. `jeff-serve` binds
|
|
42
|
+
* `127.0.0.1` unless you set `JEFF_HOST`. Set `JEFF_API_KEY` on BOTH sides to
|
|
43
|
+
* require a bearer token; without it anyone who can reach the server can use it.
|
|
44
|
+
* - **`model` is required on the wire and is NOT a checkpoint picker.** The
|
|
45
|
+
* server accepts only `'jeff'`, `'jeff-latest'` (the default here) or its own
|
|
46
|
+
* name, rejects anything else with a 422, and always answers with the ONE
|
|
47
|
+
* checkpoint it loaded (`JEFF_CHECKPOINT`). Never pass a Jev model id. Run a
|
|
48
|
+
* second server to use a second checkpoint.
|
|
49
|
+
* - **At most 26 `choice` options** with the released checkpoints (options are
|
|
50
|
+
* coded A–Z; the server refuses more). `score` takes 2–10 levels. This bond
|
|
51
|
+
* refuses both before sending — shortlist long option lists first. Raise
|
|
52
|
+
* `createProvider({ maxOptions })` only for a checkpoint whose `/health`
|
|
53
|
+
* reports a higher `max_options`.
|
|
54
|
+
* - **One request at a time.** A busy server answers 529 + `Retry-After: 1`
|
|
55
|
+
* and a loading one 503; this bond retries 429/502/503/504/529 up to 3 times.
|
|
56
|
+
* Errors carry the HTTP `status`.
|
|
57
|
+
* - **English text only.** Use `@molecule/api-ai-decisions-laya`'s multilingual
|
|
58
|
+
* checkpoint for other languages.
|
|
59
|
+
* - **Images:** up to 4 PNG/JPEG/WebP `images` are forwarded, but only the
|
|
60
|
+
* PyTorch backend reads them (`/health` → `modalities`); the Apple MLX
|
|
61
|
+
* backend is text-only, and the maker publishes no image accuracy figures.
|
|
62
|
+
* For image questions prefer `@molecule/api-ai-decisions-intern-decision`.
|
|
63
|
+
* - **Small models don't reason.** Expect fast choices between options you
|
|
64
|
+
* describe, not multi-step logic (the 0.8B scores 68% on BIG-Bench Hard vs
|
|
65
|
+
* Jev's 94%). Describe each option's consequences in plain words.
|
|
66
|
+
* - **Calibrate on your own data.** The checkpoints carry one fitted
|
|
67
|
+
* temperature from the maker's training mix; recalibrate on labelled samples
|
|
68
|
+
* of your inputs and gate on `minConfidence`. See the core's remarks.
|
|
69
|
+
* - Use the core's `setProvider`, not `bond('ai-decisions', …)` directly.
|
|
70
|
+
*
|
|
71
|
+
* @module
|
|
72
|
+
*/
|
|
73
|
+
export * from './browser-guard.js';
|
|
74
|
+
export * from './provider.js';
|
|
75
|
+
export * from './secrets.js';
|
|
76
|
+
export * from './types.js';
|
|
77
|
+
export * from './wire.js';
|
|
78
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuEG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jeff implementation of `AIDecisionsProvider`.
|
|
3
|
+
*
|
|
4
|
+
* Talks to a self-hosted `jeff-serve` over its `POST /v1/systemone` route
|
|
5
|
+
* (the TypeSafe Jev wire protocol), so the open-weights Jeff models answer
|
|
6
|
+
* typed decisions on your own hardware.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import './secrets.js';
|
|
11
|
+
import type { AIDecisionsProvider } from '@molecule/api-ai-decisions';
|
|
12
|
+
import type { JeffConfig } from './types.js';
|
|
13
|
+
/** `jeff-serve`'s default bind (`PORT` defaults to 8000; its README examples use 8765). */
|
|
14
|
+
export declare const DEFAULT_JEFF_URL = "http://localhost:8000";
|
|
15
|
+
/** A model id every `jeff-serve` accepts; it answers with the checkpoint it loaded. */
|
|
16
|
+
export declare const DEFAULT_JEFF_MODEL = "jeff-latest";
|
|
17
|
+
/** Most `choice` options the released Jeff checkpoints answer (options are coded A–Z). */
|
|
18
|
+
export declare const DEFAULT_JEFF_MAX_OPTIONS = 26;
|
|
19
|
+
/** `jeff-serve`'s bounds on a `score` question's levels. */
|
|
20
|
+
export declare const JEFF_SCORE_LEVELS: {
|
|
21
|
+
readonly min: 2;
|
|
22
|
+
readonly max: 10;
|
|
23
|
+
};
|
|
24
|
+
/** Most images `jeff-serve` accepts per request. */
|
|
25
|
+
export declare const JEFF_MAX_IMAGES = 4;
|
|
26
|
+
/** Image media types `jeff-serve` decodes. */
|
|
27
|
+
export declare const JEFF_IMAGE_TYPES: readonly string[];
|
|
28
|
+
/**
|
|
29
|
+
* Creates a Jeff decisions provider.
|
|
30
|
+
*
|
|
31
|
+
* @param config - Base URL, API key, model id and option limit.
|
|
32
|
+
* @returns An `AIDecisionsProvider` backed by a `jeff-serve` host.
|
|
33
|
+
*/
|
|
34
|
+
export declare function createProvider(config?: JeffConfig): AIDecisionsProvider;
|
|
35
|
+
/**
|
|
36
|
+
* The provider implementation — lazy, so env vars are read on first use.
|
|
37
|
+
*/
|
|
38
|
+
export declare const provider: AIDecisionsProvider;
|
|
39
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,cAAc,CAAA;AAErB,OAAO,KAAK,EACV,mBAAmB,EAKpB,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAG5C,2FAA2F;AAC3F,eAAO,MAAM,gBAAgB,0BAA0B,CAAA;AAEvD,uFAAuF;AACvF,eAAO,MAAM,kBAAkB,gBAAgB,CAAA;AAE/C,0FAA0F;AAC1F,eAAO,MAAM,wBAAwB,KAAK,CAAA;AAE1C,4DAA4D;AAC5D,eAAO,MAAM,iBAAiB;;;CAA+B,CAAA;AAE7D,oDAAoD;AACpD,eAAO,MAAM,eAAe,IAAI,CAAA;AAEhC,8CAA8C;AAC9C,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAA8C,CAAA;AAwG5F;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,mBAAmB,CAEvE;AAGD;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,mBASrB,CAAA"}
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jeff implementation of `AIDecisionsProvider`.
|
|
3
|
+
*
|
|
4
|
+
* Talks to a self-hosted `jeff-serve` over its `POST /v1/systemone` route
|
|
5
|
+
* (the TypeSafe Jev wire protocol), so the open-weights Jeff models answer
|
|
6
|
+
* typed decisions on your own hardware.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
// Side-effect import: registers this bond's secret definitions.
|
|
11
|
+
import './secrets.js';
|
|
12
|
+
import { fromWireResponse, postSystemOne, toWireQuestions } from './wire.js';
|
|
13
|
+
/** `jeff-serve`'s default bind (`PORT` defaults to 8000; its README examples use 8765). */
|
|
14
|
+
export const DEFAULT_JEFF_URL = 'http://localhost:8000';
|
|
15
|
+
/** A model id every `jeff-serve` accepts; it answers with the checkpoint it loaded. */
|
|
16
|
+
export const DEFAULT_JEFF_MODEL = 'jeff-latest';
|
|
17
|
+
/** Most `choice` options the released Jeff checkpoints answer (options are coded A–Z). */
|
|
18
|
+
export const DEFAULT_JEFF_MAX_OPTIONS = 26;
|
|
19
|
+
/** `jeff-serve`'s bounds on a `score` question's levels. */
|
|
20
|
+
export const JEFF_SCORE_LEVELS = { min: 2, max: 10 };
|
|
21
|
+
/** Most images `jeff-serve` accepts per request. */
|
|
22
|
+
export const JEFF_MAX_IMAGES = 4;
|
|
23
|
+
/** Image media types `jeff-serve` decodes. */
|
|
24
|
+
export const JEFF_IMAGE_TYPES = ['image/png', 'image/jpeg', 'image/webp'];
|
|
25
|
+
/**
|
|
26
|
+
* Refuses, before any request, what `jeff-serve` would reject with a bare 422:
|
|
27
|
+
* too many `choice` options, a `score` outside 2–10 levels, too many or
|
|
28
|
+
* unsupported images.
|
|
29
|
+
*
|
|
30
|
+
* @param questions - The questions keyed by id.
|
|
31
|
+
* @param images - The images, if any.
|
|
32
|
+
* @param maxOptions - Most `choice` options the checkpoint answers.
|
|
33
|
+
*/
|
|
34
|
+
function checkLimits(questions, images, maxOptions) {
|
|
35
|
+
for (const [id, q] of Object.entries(questions)) {
|
|
36
|
+
if (q.type === 'choice') {
|
|
37
|
+
const count = Object.keys(q.criteria ?? {}).length;
|
|
38
|
+
if (count < 1 || count > maxOptions) {
|
|
39
|
+
throw new Error(`ai-decisions-jeff: question "${id}" has ${count} options; Jeff answers 1–${maxOptions}. Shortlist the options first, or split the question.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (q.type === 'score') {
|
|
43
|
+
const count = q.criteria?.length ?? 0;
|
|
44
|
+
if (count < JEFF_SCORE_LEVELS.min || count > JEFF_SCORE_LEVELS.max) {
|
|
45
|
+
throw new Error(`ai-decisions-jeff: question "${id}" has ${count} score levels; Jeff answers ${JEFF_SCORE_LEVELS.min}–${JEFF_SCORE_LEVELS.max}.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (images.length > JEFF_MAX_IMAGES) {
|
|
50
|
+
throw new Error(`ai-decisions-jeff: ${images.length} images passed; jeff-serve accepts at most ${JEFF_MAX_IMAGES}.`);
|
|
51
|
+
}
|
|
52
|
+
for (const image of images) {
|
|
53
|
+
if (!JEFF_IMAGE_TYPES.includes(image.mimeType)) {
|
|
54
|
+
throw new Error(`ai-decisions-jeff: image type "${image.mimeType}" is not supported; use ${JEFF_IMAGE_TYPES.join(', ')}.`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Jeff decisions provider over HTTP.
|
|
60
|
+
*/
|
|
61
|
+
class JeffDecisionsProvider {
|
|
62
|
+
name = 'jeff';
|
|
63
|
+
baseUrl;
|
|
64
|
+
apiKey;
|
|
65
|
+
model;
|
|
66
|
+
maxOptions;
|
|
67
|
+
/**
|
|
68
|
+
* Creates the provider.
|
|
69
|
+
*
|
|
70
|
+
* @param config - Provider configuration; env vars fill anything omitted.
|
|
71
|
+
*/
|
|
72
|
+
constructor(config = {}) {
|
|
73
|
+
this.baseUrl = (config.baseUrl || process.env.JEFF_URL || DEFAULT_JEFF_URL).replace(/\/+$/, '');
|
|
74
|
+
this.apiKey = config.apiKey ?? (process.env.JEFF_API_KEY || undefined);
|
|
75
|
+
this.model = config.model || DEFAULT_JEFF_MODEL;
|
|
76
|
+
this.maxOptions = config.maxOptions ?? DEFAULT_JEFF_MAX_OPTIONS;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Answers every question about `input.state` in one request.
|
|
80
|
+
*
|
|
81
|
+
* @param input - The state, questions and options.
|
|
82
|
+
* @returns One typed answer per question id.
|
|
83
|
+
*/
|
|
84
|
+
async decide(input) {
|
|
85
|
+
if (input.state === undefined || input.state === null) {
|
|
86
|
+
throw new Error('ai-decisions: decide() requires a `state`');
|
|
87
|
+
}
|
|
88
|
+
if (!input.questions || Object.keys(input.questions).length === 0) {
|
|
89
|
+
throw new Error('ai-decisions: decide() requires at least one question');
|
|
90
|
+
}
|
|
91
|
+
const images = input.images ?? [];
|
|
92
|
+
checkLimits(input.questions, images, this.maxOptions);
|
|
93
|
+
const body = await postSystemOne({
|
|
94
|
+
url: `${this.baseUrl}/v1/systemone`,
|
|
95
|
+
apiKey: this.apiKey,
|
|
96
|
+
body: {
|
|
97
|
+
model: input.model || this.model,
|
|
98
|
+
state: input.state,
|
|
99
|
+
questions: toWireQuestions(input.questions),
|
|
100
|
+
...(images.length
|
|
101
|
+
? { images: images.map((image) => `data:${image.mimeType};base64,${image.data}`) }
|
|
102
|
+
: {}),
|
|
103
|
+
},
|
|
104
|
+
signal: input.signal,
|
|
105
|
+
label: 'Jeff',
|
|
106
|
+
});
|
|
107
|
+
return fromWireResponse(input.questions, body, input.minConfidence);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Creates a Jeff decisions provider.
|
|
112
|
+
*
|
|
113
|
+
* @param config - Base URL, API key, model id and option limit.
|
|
114
|
+
* @returns An `AIDecisionsProvider` backed by a `jeff-serve` host.
|
|
115
|
+
*/
|
|
116
|
+
export function createProvider(config) {
|
|
117
|
+
return new JeffDecisionsProvider(config);
|
|
118
|
+
}
|
|
119
|
+
let _provider = null;
|
|
120
|
+
/**
|
|
121
|
+
* The provider implementation — lazy, so env vars are read on first use.
|
|
122
|
+
*/
|
|
123
|
+
export const provider = new Proxy({}, {
|
|
124
|
+
get(_, prop, receiver) {
|
|
125
|
+
if (!_provider)
|
|
126
|
+
_provider = createProvider();
|
|
127
|
+
return Reflect.get(_provider, prop, receiver);
|
|
128
|
+
},
|
|
129
|
+
set(_, prop, value) {
|
|
130
|
+
if (!_provider)
|
|
131
|
+
_provider = createProvider();
|
|
132
|
+
return Reflect.set(_provider, prop, value);
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jeff secret definitions — self-registered at import time so the runtime
|
|
3
|
+
* secrets registry (`@molecule/api-secrets`) can drive boot-time configuration
|
|
4
|
+
* reports and actionable "not configured" errors.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { SecretDefinition } from '@molecule/api-secrets';
|
|
9
|
+
/** Secret definitions used by the Jeff decisions bond. */
|
|
10
|
+
export declare const aiDecisionsJeffSecretDefinitions: SecretDefinition[];
|
|
11
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,0DAA0D;AAC1D,eAAO,MAAM,gCAAgC,EAAE,gBAAgB,EAiB9D,CAAA"}
|
package/dist/secrets.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jeff secret definitions — self-registered at import time so the runtime
|
|
3
|
+
* secrets registry (`@molecule/api-secrets`) can drive boot-time configuration
|
|
4
|
+
* reports and actionable "not configured" errors.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import { registerSecrets } from '@molecule/api-secrets';
|
|
9
|
+
/** Secret definitions used by the Jeff decisions bond. */
|
|
10
|
+
export const aiDecisionsJeffSecretDefinitions = [
|
|
11
|
+
{
|
|
12
|
+
key: 'JEFF_URL',
|
|
13
|
+
description: 'Jeff server URL — The base URL of your jeff-serve host (clone github.com/firelex/jeff, uv sync, download a checkpoint, then JEFF_CHECKPOINT=<dir> uv run jeff-serve). Defaults to http://localhost:8000.',
|
|
14
|
+
helpUrl: 'https://github.com/firelex/jeff#readme',
|
|
15
|
+
required: false,
|
|
16
|
+
example: 'http://localhost:8000',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: 'JEFF_API_KEY',
|
|
20
|
+
description: 'Jeff server API key — The bearer token your jeff-serve host requires, if you set JEFF_API_KEY on the server.',
|
|
21
|
+
helpUrl: 'https://github.com/firelex/jeff#readme',
|
|
22
|
+
required: false,
|
|
23
|
+
example: 'a-long-random-string',
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
registerSecrets(aiDecisionsJeffSecretDefinitions);
|
|
27
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAEvD,0DAA0D;AAC1D,MAAM,CAAC,MAAM,gCAAgC,GAAuB;IAClE;QACE,GAAG,EAAE,UAAU;QACf,WAAW,EACT,0MAA0M;QAC5M,OAAO,EAAE,wCAAwC;QACjD,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,uBAAuB;KACjC;IACD;QACE,GAAG,EAAE,cAAc;QACnB,WAAW,EACT,8GAA8G;QAChH,OAAO,EAAE,wCAAwC;QACjD,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,sBAAsB;KAChC;CACF,CAAA;AAED,eAAe,CAAC,gCAAgC,CAAC,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jeff decisions provider configuration.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the Jeff decisions provider.
|
|
8
|
+
*/
|
|
9
|
+
export interface JeffConfig {
|
|
10
|
+
/** Base URL of the `jeff-serve` host. Defaults to `JEFF_URL`, then `http://localhost:8000`. */
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
/** Bearer token, when the server sets `JEFF_API_KEY`. Defaults to the `JEFF_API_KEY` env var. */
|
|
13
|
+
apiKey?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Model id sent on every request. `jeff-serve` REQUIRES one and accepts only
|
|
16
|
+
* `'jeff'`, `'jeff-latest'` or its own name (e.g. `'jeff-qwen3.5-0.8b'`) —
|
|
17
|
+
* it answers with whichever checkpoint it loaded. Defaults to `'jeff-latest'`.
|
|
18
|
+
*/
|
|
19
|
+
model?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Most `choice` options the loaded checkpoint answers (the server's `/health`
|
|
22
|
+
* reports it as `max_options`). Defaults to 26 — the Jeff models released on
|
|
23
|
+
* 2026-09-28. Raise it only for a checkpoint trained on more.
|
|
24
|
+
*/
|
|
25
|
+
maxOptions?: number;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,+FAA+F;IAC/F,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/dist/wire.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/v1/systemone` wire protocol (TypeSafe Jev, copied by `jeff-serve`) ↔
|
|
3
|
+
* the `@molecule/api-ai-decisions` types.
|
|
4
|
+
*
|
|
5
|
+
* Wire facts (github.com/firelex/jeff `src/jeff/server.py`, `types.py`,
|
|
6
|
+
* `model.py`): request `{ model, state, questions: { <id>: { type: 'choice'|'score'|'noul', instructions?, criteria } }, images? }`
|
|
7
|
+
* — `model` is REQUIRED and must be `jeff`, `jeff-latest` or the server's own
|
|
8
|
+
* name; `choice` criteria is `{ label: description }`, `score` criteria a list
|
|
9
|
+
* of 2–10 levels, `noul` criteria optional `{ true, false }`; `images` is up to
|
|
10
|
+
* 4 PNG/JPEG/WebP `data:` URLs. Answers carry `choice` + `probabilities` keyed
|
|
11
|
+
* by label, `score` (expected level) + `probabilities` keyed `"0".."k-1"`, or
|
|
12
|
+
* `noul` (P(true)). Vendor `confidence` fields are ignored: the core defines
|
|
13
|
+
* `confidence` as the probability of the reported answer, computed here from
|
|
14
|
+
* the distribution.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import type { DecideResult, DecisionAnswer, DecisionQuestion } from '@molecule/api-ai-decisions';
|
|
19
|
+
/** One question as sent on the wire. */
|
|
20
|
+
export interface WireQuestion {
|
|
21
|
+
type: 'choice' | 'score' | 'noul';
|
|
22
|
+
instructions: string;
|
|
23
|
+
criteria?: Record<string, string> | string[];
|
|
24
|
+
}
|
|
25
|
+
/** One answer as received on the wire (only the fields we read). */
|
|
26
|
+
export interface WireAnswer {
|
|
27
|
+
type?: string;
|
|
28
|
+
choice?: string;
|
|
29
|
+
score?: number;
|
|
30
|
+
noul?: number;
|
|
31
|
+
probabilities?: Record<string, number>;
|
|
32
|
+
}
|
|
33
|
+
/** The response body (only the fields we read). */
|
|
34
|
+
export interface WireResponse {
|
|
35
|
+
model?: string;
|
|
36
|
+
answers?: Record<string, WireAnswer>;
|
|
37
|
+
usage?: {
|
|
38
|
+
input_tokens?: number;
|
|
39
|
+
output_tokens?: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Converts the core's questions into wire questions (`yesNo` → `noul`).
|
|
44
|
+
*
|
|
45
|
+
* @param questions - The questions keyed by id.
|
|
46
|
+
* @returns The wire `questions` object.
|
|
47
|
+
*/
|
|
48
|
+
export declare function toWireQuestions(questions: Record<string, DecisionQuestion>): Record<string, WireQuestion>;
|
|
49
|
+
/**
|
|
50
|
+
* Converts one wire answer into the core's answer for `question`.
|
|
51
|
+
*
|
|
52
|
+
* @param id - The question id (for error messages).
|
|
53
|
+
* @param question - The question that was asked.
|
|
54
|
+
* @param wire - The wire answer.
|
|
55
|
+
* @param minConfidence - Optional low-confidence threshold.
|
|
56
|
+
* @returns The typed answer.
|
|
57
|
+
*/
|
|
58
|
+
export declare function fromWireAnswer(id: string, question: DecisionQuestion, wire: WireAnswer | undefined, minConfidence?: number): DecisionAnswer;
|
|
59
|
+
/**
|
|
60
|
+
* Converts a whole wire response into the core's result.
|
|
61
|
+
*
|
|
62
|
+
* @param questions - The questions that were asked.
|
|
63
|
+
* @param body - The parsed response body.
|
|
64
|
+
* @param minConfidence - Optional low-confidence threshold.
|
|
65
|
+
* @returns The typed result.
|
|
66
|
+
*/
|
|
67
|
+
export declare function fromWireResponse<Q extends Record<string, DecisionQuestion>>(questions: Q, body: WireResponse, minConfidence?: number): DecideResult<Q>;
|
|
68
|
+
/** Options for {@link postSystemOne}. */
|
|
69
|
+
export interface PostOptions {
|
|
70
|
+
/** Full endpoint URL. */
|
|
71
|
+
url: string;
|
|
72
|
+
/** Bearer token, if any. */
|
|
73
|
+
apiKey?: string;
|
|
74
|
+
/** Request body. */
|
|
75
|
+
body: Record<string, unknown>;
|
|
76
|
+
/** Abort signal. */
|
|
77
|
+
signal?: AbortSignal;
|
|
78
|
+
/** Label for error messages (`'Jeff'`). */
|
|
79
|
+
label: string;
|
|
80
|
+
/** Max retries on a retryable status (default 3). */
|
|
81
|
+
maxRetries?: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* POSTs a `/v1/systemone` request with retry on 429/5xx-busy, honouring
|
|
85
|
+
* `Retry-After`. Throws an Error carrying `status` on a non-2xx response.
|
|
86
|
+
*
|
|
87
|
+
* @param opts - Request options.
|
|
88
|
+
* @returns The parsed response body.
|
|
89
|
+
*/
|
|
90
|
+
export declare function postSystemOne(opts: PostOptions): Promise<WireResponse>;
|
|
91
|
+
//# sourceMappingURL=wire.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../src/wire.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAEV,YAAY,EACZ,cAAc,EACd,gBAAgB,EAGjB,MAAM,4BAA4B,CAAA;AAEnC,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;IACjC,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,CAAA;CAC7C;AAED,oEAAoE;AACpE,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACvC;AAED,mDAAmD;AACnD,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACpC,KAAK,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAC1D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC1C,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAuB9B;AAKD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,UAAU,GAAG,SAAS,EAC5B,aAAa,CAAC,EAAE,MAAM,GACrB,cAAc,CAiDhB;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACzE,SAAS,EAAE,CAAC,EACZ,IAAI,EAAE,YAAY,EAClB,aAAa,CAAC,EAAE,MAAM,GACrB,YAAY,CAAC,CAAC,CAAC,CAiBjB;AAKD,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,yBAAyB;IACzB,GAAG,EAAE,MAAM,CAAA;IACX,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,oBAAoB;IACpB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAA;IACb,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CA8C5E"}
|
package/dist/wire.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/v1/systemone` wire protocol (TypeSafe Jev, copied by `jeff-serve`) ↔
|
|
3
|
+
* the `@molecule/api-ai-decisions` types.
|
|
4
|
+
*
|
|
5
|
+
* Wire facts (github.com/firelex/jeff `src/jeff/server.py`, `types.py`,
|
|
6
|
+
* `model.py`): request `{ model, state, questions: { <id>: { type: 'choice'|'score'|'noul', instructions?, criteria } }, images? }`
|
|
7
|
+
* — `model` is REQUIRED and must be `jeff`, `jeff-latest` or the server's own
|
|
8
|
+
* name; `choice` criteria is `{ label: description }`, `score` criteria a list
|
|
9
|
+
* of 2–10 levels, `noul` criteria optional `{ true, false }`; `images` is up to
|
|
10
|
+
* 4 PNG/JPEG/WebP `data:` URLs. Answers carry `choice` + `probabilities` keyed
|
|
11
|
+
* by label, `score` (expected level) + `probabilities` keyed `"0".."k-1"`, or
|
|
12
|
+
* `noul` (P(true)). Vendor `confidence` fields are ignored: the core defines
|
|
13
|
+
* `confidence` as the probability of the reported answer, computed here from
|
|
14
|
+
* the distribution.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Converts the core's questions into wire questions (`yesNo` → `noul`).
|
|
20
|
+
*
|
|
21
|
+
* @param questions - The questions keyed by id.
|
|
22
|
+
* @returns The wire `questions` object.
|
|
23
|
+
*/
|
|
24
|
+
export function toWireQuestions(questions) {
|
|
25
|
+
const out = {};
|
|
26
|
+
for (const [id, q] of Object.entries(questions)) {
|
|
27
|
+
if (q.type === 'choice') {
|
|
28
|
+
out[id] = { type: 'choice', instructions: q.instructions, criteria: q.criteria };
|
|
29
|
+
}
|
|
30
|
+
else if (q.type === 'score') {
|
|
31
|
+
out[id] = { type: 'score', instructions: q.instructions, criteria: q.criteria };
|
|
32
|
+
}
|
|
33
|
+
else if (q.type === 'yesNo') {
|
|
34
|
+
const criteria = {};
|
|
35
|
+
if (q.criteria?.yes)
|
|
36
|
+
criteria.true = q.criteria.yes;
|
|
37
|
+
if (q.criteria?.no)
|
|
38
|
+
criteria.false = q.criteria.no;
|
|
39
|
+
out[id] = {
|
|
40
|
+
type: 'noul',
|
|
41
|
+
instructions: q.instructions,
|
|
42
|
+
...(Object.keys(criteria).length ? { criteria } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
throw new Error(`ai-decisions: question "${id}" has unknown type "${q.type}"`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
52
|
+
const clamp01 = (v) => Math.max(0, Math.min(1, v));
|
|
53
|
+
/**
|
|
54
|
+
* Converts one wire answer into the core's answer for `question`.
|
|
55
|
+
*
|
|
56
|
+
* @param id - The question id (for error messages).
|
|
57
|
+
* @param question - The question that was asked.
|
|
58
|
+
* @param wire - The wire answer.
|
|
59
|
+
* @param minConfidence - Optional low-confidence threshold.
|
|
60
|
+
* @returns The typed answer.
|
|
61
|
+
*/
|
|
62
|
+
export function fromWireAnswer(id, question, wire, minConfidence) {
|
|
63
|
+
if (!wire || typeof wire !== 'object') {
|
|
64
|
+
throw new Error(`ai-decisions: the provider returned no answer for question "${id}"`);
|
|
65
|
+
}
|
|
66
|
+
let answer;
|
|
67
|
+
if (question.type === 'choice') {
|
|
68
|
+
const labels = Object.keys(question.criteria);
|
|
69
|
+
const probabilities = {};
|
|
70
|
+
for (const label of labels)
|
|
71
|
+
probabilities[label] = clamp01(num(wire.probabilities?.[label]));
|
|
72
|
+
let top = labels[0] ?? '';
|
|
73
|
+
for (const label of labels)
|
|
74
|
+
if (probabilities[label] > probabilities[top])
|
|
75
|
+
top = label;
|
|
76
|
+
const choice = typeof wire.choice === 'string' && labels.includes(wire.choice) ? wire.choice : top;
|
|
77
|
+
answer = {
|
|
78
|
+
type: 'choice',
|
|
79
|
+
choice,
|
|
80
|
+
probabilities,
|
|
81
|
+
confidence: probabilities[choice] ?? 0,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
else if (question.type === 'score') {
|
|
85
|
+
const probabilities = question.criteria.map((_, i) => clamp01(num(wire.probabilities?.[String(i)])));
|
|
86
|
+
let level = 0;
|
|
87
|
+
probabilities.forEach((p, i) => {
|
|
88
|
+
if (p > probabilities[level])
|
|
89
|
+
level = i;
|
|
90
|
+
});
|
|
91
|
+
const expected = probabilities.reduce((sum, p, i) => sum + p * i, 0);
|
|
92
|
+
answer = {
|
|
93
|
+
type: 'score',
|
|
94
|
+
score: typeof wire.score === 'number' && Number.isFinite(wire.score) ? wire.score : expected,
|
|
95
|
+
level,
|
|
96
|
+
probabilities,
|
|
97
|
+
confidence: probabilities[level] ?? 0,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
if (typeof wire.noul !== 'number' || !Number.isFinite(wire.noul)) {
|
|
102
|
+
throw new Error(`ai-decisions: the provider's answer to "${id}" has no noul probability`);
|
|
103
|
+
}
|
|
104
|
+
const probability = clamp01(wire.noul);
|
|
105
|
+
answer = {
|
|
106
|
+
type: 'yesNo',
|
|
107
|
+
probability,
|
|
108
|
+
answer: probability >= 0.5,
|
|
109
|
+
confidence: Math.max(probability, 1 - probability),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (typeof minConfidence === 'number')
|
|
113
|
+
answer.lowConfidence = answer.confidence < minConfidence;
|
|
114
|
+
return answer;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Converts a whole wire response into the core's result.
|
|
118
|
+
*
|
|
119
|
+
* @param questions - The questions that were asked.
|
|
120
|
+
* @param body - The parsed response body.
|
|
121
|
+
* @param minConfidence - Optional low-confidence threshold.
|
|
122
|
+
* @returns The typed result.
|
|
123
|
+
*/
|
|
124
|
+
export function fromWireResponse(questions, body, minConfidence) {
|
|
125
|
+
const answers = {};
|
|
126
|
+
for (const [id, q] of Object.entries(questions)) {
|
|
127
|
+
answers[id] = fromWireAnswer(id, q, body.answers?.[id], minConfidence);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
answers: answers,
|
|
131
|
+
...(typeof body.model === 'string' ? { model: body.model } : {}),
|
|
132
|
+
...(body.usage
|
|
133
|
+
? {
|
|
134
|
+
usage: {
|
|
135
|
+
inputTokens: num(body.usage.input_tokens),
|
|
136
|
+
outputTokens: num(body.usage.output_tokens),
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
: {}),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/** Statuses worth retrying: rate limit, not ready (503), busy (`jeff-serve` 529 + Retry-After). */
|
|
143
|
+
const RETRYABLE = new Set([429, 502, 503, 504, 529]);
|
|
144
|
+
/**
|
|
145
|
+
* POSTs a `/v1/systemone` request with retry on 429/5xx-busy, honouring
|
|
146
|
+
* `Retry-After`. Throws an Error carrying `status` on a non-2xx response.
|
|
147
|
+
*
|
|
148
|
+
* @param opts - Request options.
|
|
149
|
+
* @returns The parsed response body.
|
|
150
|
+
*/
|
|
151
|
+
export async function postSystemOne(opts) {
|
|
152
|
+
const { url, apiKey, body, signal, label, maxRetries = 3 } = opts;
|
|
153
|
+
const headers = { 'content-type': 'application/json' };
|
|
154
|
+
if (apiKey)
|
|
155
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
156
|
+
for (let attempt = 0;; attempt++) {
|
|
157
|
+
const response = await fetch(url, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
headers,
|
|
160
|
+
body: JSON.stringify(body),
|
|
161
|
+
signal,
|
|
162
|
+
});
|
|
163
|
+
if (response.ok)
|
|
164
|
+
return (await response.json());
|
|
165
|
+
if (RETRYABLE.has(response.status) && attempt < maxRetries) {
|
|
166
|
+
const retryAfter = Number(response.headers.get('retry-after'));
|
|
167
|
+
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0
|
|
168
|
+
? Math.min(retryAfter * 1000, 10_000)
|
|
169
|
+
: 250 * 2 ** attempt;
|
|
170
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const text = await response.text().catch(() => '');
|
|
174
|
+
let detail = text.length > 0 && text.length < 300 ? text : `HTTP ${response.status}`;
|
|
175
|
+
try {
|
|
176
|
+
const parsed = JSON.parse(text);
|
|
177
|
+
if (typeof parsed.detail === 'string')
|
|
178
|
+
detail = parsed.detail;
|
|
179
|
+
else if (Array.isArray(parsed.detail)) {
|
|
180
|
+
// FastAPI request validation: [{ loc, msg, type }, …]
|
|
181
|
+
detail = parsed.detail
|
|
182
|
+
.map((item) => [Array.isArray(item.loc) ? item.loc.join('.') : '', item.msg]
|
|
183
|
+
.filter(Boolean)
|
|
184
|
+
.join(': '))
|
|
185
|
+
.join('; ');
|
|
186
|
+
}
|
|
187
|
+
else if (typeof parsed.error?.message === 'string')
|
|
188
|
+
detail = parsed.error.message;
|
|
189
|
+
}
|
|
190
|
+
catch (_error) {
|
|
191
|
+
// Not JSON — keep the raw text (or the status) as the detail.
|
|
192
|
+
}
|
|
193
|
+
throw Object.assign(new Error(`${label} decision request failed (${response.status}): ${detail}`), {
|
|
194
|
+
status: response.status,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=wire.js.map
|