@niscorp/signal 0.1.6 → 0.1.7
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 +12 -0
- package/DESIGN.md +4 -0
- package/DOCS.md +18 -0
- package/dist/index.cjs +70 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +70 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @niscorp/signal
|
|
2
2
|
|
|
3
|
+
## 0.1.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f4a3f04: `decide()` can ask about images: `images` maps a uuid to an `ImageSource`, and a string in `state` equal to a uuid points at that image.
|
|
8
|
+
|
|
9
|
+
On `typesafe` the images go as the request's `assets` with the `TypeSafe-Danger-Zone-Images: true` header, so Jev answers about them with calibrated probabilities. TypeSafe takes PNG or JPEG data; a `url` image or another `mediaType` fails with `E_VALIDATION_FAILED` before the request. On a chat provider each image follows the state in the emulation message, after a label with its uuid; a model whose registry row says `multimodal: false` is refused before the request. A key that is not a uuid is refused on every provider.
|
|
10
|
+
|
|
11
|
+
A request without `images`, or with `images: {}`, is unchanged: TypeSafe gets the same body and headers, and a chat provider the same single text message.
|
|
12
|
+
|
|
13
|
+
**What to change:** nothing. A caller that posts images to TypeSafe itself can pass them to `decide()` instead.
|
|
14
|
+
|
|
3
15
|
## 0.1.6
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/DESIGN.md
CHANGED
|
@@ -119,6 +119,10 @@ sides is two changes.
|
|
|
119
119
|
to repair. The questions are the schema: the result type and the
|
|
120
120
|
gate (`decide/gate.ts`) both derive from them, which is the package's
|
|
121
121
|
one sentence applied to a model that answers instead of writing.
|
|
122
|
+
Images ride beside `state`, keyed by the uuid `state` uses for them —
|
|
123
|
+
TypeSafe's own wire structure — so the adapter translates fields,
|
|
124
|
+
refuses what TypeSafe's wire cannot carry, and never walks or
|
|
125
|
+
rewrites the caller's state.
|
|
122
126
|
12. **Emulation is honest or absent.** On a chat adapter `decide()`
|
|
123
127
|
runs the derived schema through `complete()` and returns picks with
|
|
124
128
|
`calibrated: false` — the discriminant of `DecideResult`, so the
|
package/DOCS.md
CHANGED
|
@@ -543,6 +543,24 @@ On a chat provider `decide()` still works: the same questions run through struct
|
|
|
543
543
|
if (signal.describe().kind === 'decisions') { /* sub-second */ }
|
|
544
544
|
```
|
|
545
545
|
|
|
546
|
+
### Images
|
|
547
|
+
|
|
548
|
+
`images` maps a uuid to an `ImageSource`, the same type `complete()` takes in content parts. A string in `state` equal to a uuid points at that image:
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
const duck = randomUUID();
|
|
552
|
+
const { decisions } = await jev.decide({
|
|
553
|
+
state: { instruction: 'Select all squares containing a duck', tiles: { tile1: duck } },
|
|
554
|
+
images: { [duck]: { type: 'base64', mediaType: 'image/png', data: pngBase64 } },
|
|
555
|
+
questions: { tile1: { type: 'noul', instructions: 'Does the picture in `tiles.tile1` show the requested object?' } },
|
|
556
|
+
});
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
- **On `typesafe`** the images go as the request's `assets`, with the `TypeSafe-Danger-Zone-Images: true` header. TypeSafe takes PNG or JPEG data: a `url` image or another `mediaType` fails with `E_VALIDATION_FAILED` before the request. TypeSafe's image documentation gives a minimum of 280 pixels a side; signal does not check sizes.
|
|
560
|
+
- **On a chat provider** each image follows the state in the message, after a label with its uuid. A model whose registry row says `multimodal: false` fails with `E_VALIDATION_FAILED` before the request; a model with no row is not checked. Emulation's correction retries send the images again.
|
|
561
|
+
|
|
562
|
+
A key that is not a uuid fails with `E_VALIDATION_FAILED` on every provider. A request with no images, or `images: {}`, is sent exactly as one without the field.
|
|
563
|
+
|
|
546
564
|
### Abort
|
|
547
565
|
|
|
548
566
|
```typescript
|
package/dist/index.cjs
CHANGED
|
@@ -484,17 +484,46 @@ var jsonOrText = (text) => {
|
|
|
484
484
|
}
|
|
485
485
|
};
|
|
486
486
|
var providerError = (message, raw) => new SignalError(`Provider error: ${message}`, ErrorCode.PROVIDER_ERROR, { raw });
|
|
487
|
+
var FORMATS = { "image/png": "png", "image/jpeg": "jpeg" };
|
|
488
|
+
var assetOf = (id, source) => {
|
|
489
|
+
if (source.type === "url") {
|
|
490
|
+
throw new SignalError(`Image ${id}: TypeSafe takes image data, not a URL`, ErrorCode.VALIDATION_FAILED, { image: id });
|
|
491
|
+
}
|
|
492
|
+
const format = FORMATS[source.mediaType];
|
|
493
|
+
if (format === void 0) {
|
|
494
|
+
throw new SignalError(
|
|
495
|
+
`Image ${id}: TypeSafe takes image/png or image/jpeg, not ${source.mediaType}`,
|
|
496
|
+
ErrorCode.VALIDATION_FAILED,
|
|
497
|
+
{ image: id }
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
return { type: "image", format, data_base64: source.data };
|
|
501
|
+
};
|
|
502
|
+
var assetsOf = (images) => {
|
|
503
|
+
const entries = Object.entries(images ?? {});
|
|
504
|
+
return entries.length === 0 ? void 0 : Object.fromEntries(entries.map(([id, source]) => [id, assetOf(id, source)]));
|
|
505
|
+
};
|
|
487
506
|
var createSystemOneAdapter = async (config) => {
|
|
488
507
|
const send = sendOf(config.client);
|
|
489
508
|
const url = `${config.baseUrl.replace(/\/+$/, "")}/systemone`;
|
|
490
509
|
const decide = async (request, options) => {
|
|
510
|
+
const assets = assetsOf(request.images);
|
|
491
511
|
let status;
|
|
492
512
|
let body;
|
|
493
513
|
try {
|
|
494
514
|
const response = await send(url, {
|
|
495
515
|
method: "POST",
|
|
496
|
-
headers: {
|
|
497
|
-
|
|
516
|
+
headers: {
|
|
517
|
+
authorization: `Bearer ${config.apiKey}`,
|
|
518
|
+
"content-type": "application/json",
|
|
519
|
+
...assets !== void 0 && { "TypeSafe-Danger-Zone-Images": "true" }
|
|
520
|
+
},
|
|
521
|
+
body: JSON.stringify({
|
|
522
|
+
model: request.model,
|
|
523
|
+
state: request.state,
|
|
524
|
+
questions: request.questions,
|
|
525
|
+
...assets !== void 0 && { assets }
|
|
526
|
+
}),
|
|
498
527
|
...options?.signal !== void 0 && { signal: options.signal }
|
|
499
528
|
});
|
|
500
529
|
status = response.status;
|
|
@@ -550,6 +579,7 @@ var bareSchemaCorrection = (issues) => `Your output was invalid: ${issues}. Repl
|
|
|
550
579
|
var decisionEmulationPrompt = (state) => `Answer every field of the OUTPUT SCHEMA about the STATE below. Each field's description is the question and lists everything you may answer with.
|
|
551
580
|
STATE:
|
|
552
581
|
${state}`;
|
|
582
|
+
var decisionImageLabel = (id) => `Image ${id}:`;
|
|
553
583
|
var decisionQuestionDescription = (question) => {
|
|
554
584
|
if (question.type === "choice") return `${question.instructions} Options: ${JSON.stringify(question.criteria)}`;
|
|
555
585
|
if (question.type === "score") {
|
|
@@ -1340,6 +1370,35 @@ var assertEffort = (config, effort) => {
|
|
|
1340
1370
|
ErrorCode.VALIDATION_FAILED
|
|
1341
1371
|
);
|
|
1342
1372
|
};
|
|
1373
|
+
var imageId = zod.z.uuid();
|
|
1374
|
+
var assertImageIds = (images) => {
|
|
1375
|
+
for (const id of Object.keys(images ?? {})) {
|
|
1376
|
+
if (!imageId.safeParse(id).success) {
|
|
1377
|
+
throw new SignalError(`Image key "${id}" is not a uuid`, ErrorCode.VALIDATION_FAILED, { image: id });
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
var assertTakesImages = (config) => {
|
|
1382
|
+
const resolved = modelRowOf(config);
|
|
1383
|
+
const row = resolved?.row;
|
|
1384
|
+
if (resolved === void 0 || row === void 0 || row.capabilities.multimodal) return;
|
|
1385
|
+
throw new SignalError(
|
|
1386
|
+
`${resolved.provider} ${resolved.model} takes no images (measured ${row.verified})`,
|
|
1387
|
+
ErrorCode.VALIDATION_FAILED
|
|
1388
|
+
);
|
|
1389
|
+
};
|
|
1390
|
+
var emulationContent = (state, images) => {
|
|
1391
|
+
const prompt = decisionEmulationPrompt(state);
|
|
1392
|
+
const entries = Object.entries(images ?? {});
|
|
1393
|
+
if (entries.length === 0) return prompt;
|
|
1394
|
+
return [
|
|
1395
|
+
{ type: "text", text: prompt },
|
|
1396
|
+
...entries.flatMap(([id, source]) => [
|
|
1397
|
+
{ type: "text", text: decisionImageLabel(id) },
|
|
1398
|
+
{ type: "image", source }
|
|
1399
|
+
])
|
|
1400
|
+
];
|
|
1401
|
+
};
|
|
1343
1402
|
var wireIdsOf = (config) => {
|
|
1344
1403
|
const source = typeof config.provider === "string" ? providerRegistry[config.provider] : config.provider;
|
|
1345
1404
|
return source === void 0 || source.adapter === "systemone" ? [] : source.wire ?? [];
|
|
@@ -1517,7 +1576,12 @@ var createSignalFromConfig = (config) => {
|
|
|
1517
1576
|
const stepStream = stepStreamAs("stepStream");
|
|
1518
1577
|
const decideCalibrated = async (adapter, model, request, started) => {
|
|
1519
1578
|
const response = await adapter.decide(
|
|
1520
|
-
{
|
|
1579
|
+
{
|
|
1580
|
+
model,
|
|
1581
|
+
state: request.state,
|
|
1582
|
+
questions: request.questions,
|
|
1583
|
+
...request.images !== void 0 && { images: request.images }
|
|
1584
|
+
},
|
|
1521
1585
|
request.options?.signal !== void 0 ? { signal: request.options.signal } : void 0
|
|
1522
1586
|
);
|
|
1523
1587
|
const decisions = acceptCalibrated(request.questions, response.answers, response.raw);
|
|
@@ -1535,10 +1599,11 @@ var createSignalFromConfig = (config) => {
|
|
|
1535
1599
|
};
|
|
1536
1600
|
};
|
|
1537
1601
|
const decideEmulated = async (model, request, started) => {
|
|
1602
|
+
if (Object.keys(request.images ?? {}).length > 0) assertTakesImages(config);
|
|
1538
1603
|
const state = typeof request.state === "string" ? request.state : JSON.stringify(request.state);
|
|
1539
1604
|
const result = await runComplete(
|
|
1540
1605
|
{
|
|
1541
|
-
messages: [{ role: "user", content:
|
|
1606
|
+
messages: [{ role: "user", content: emulationContent(state, request.images) }],
|
|
1542
1607
|
schema: uncalibratedSchemaOf(request.questions),
|
|
1543
1608
|
tools: void 0,
|
|
1544
1609
|
retries: config.retries ?? 2,
|
|
@@ -1637,6 +1702,7 @@ var createSignalFromConfig = (config) => {
|
|
|
1637
1702
|
}),
|
|
1638
1703
|
decide: async (request) => {
|
|
1639
1704
|
assertQuestions(request.questions);
|
|
1705
|
+
assertImageIds(request.images);
|
|
1640
1706
|
const started = Date.now();
|
|
1641
1707
|
const adapter = await getAdapter();
|
|
1642
1708
|
const model = resolveProvider(config).model;
|