runline 0.30.0 → 0.31.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 +1 -1
- package/dist/index.js +1 -1
- package/dist/plugins/_shared/authedFetch.js +33 -0
- package/dist/plugins/_shared/provider.js +80 -0
- package/dist/plugins/_shared/tokenRefresh.js +5 -3
- package/dist/plugins/gett/src/index.js +358 -0
- package/dist/plugins/gett/src/places.js +140 -0
- package/dist/plugins/gett/src/rides.js +144 -0
- package/dist/plugins/gett/src/session.js +242 -0
- package/dist/plugins/gett/src/shared.js +180 -0
- package/dist/plugins/linear/src/attachments.js +2 -2
- package/dist/plugins/linear/src/cycles.js +6 -6
- package/dist/plugins/linear/src/index.js +2 -2
- package/dist/plugins/linear/src/initiatives.js +6 -6
- package/dist/plugins/linear/src/issues.js +9 -9
- package/dist/plugins/linear/src/labels.js +6 -6
- package/dist/plugins/linear/src/organization.js +2 -2
- package/dist/plugins/linear/src/projects.js +12 -12
- package/dist/plugins/linear/src/shared.js +148 -20
- package/dist/plugins/linear/src/states.js +3 -3
- package/dist/plugins/linear/src/teams.js +6 -5
- package/dist/plugins/linear/src/users.js +3 -3
- package/dist/plugins/linear/src/views.js +10 -8
- package/dist/plugins/linear/src/webhooks.js +5 -5
- package/dist/plugins/openai/src/index.js +28 -9
- package/dist/plugins/typesafe/src/guide.js +172 -0
- package/dist/plugins/typesafe/src/index.js +169 -0
- package/dist/plugins/typesafe/src/questions.js +180 -0
- package/dist/plugins/typesafe/src/shared.js +166 -0
- package/dist/plugins/wolt/src/account.js +427 -0
- package/dist/plugins/wolt/src/catalog.js +166 -0
- package/dist/plugins/wolt/src/index.js +526 -0
- package/dist/plugins/wolt/src/orders.js +349 -0
- package/dist/plugins/wolt/src/shared.js +218 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import { bindGetAction, bindListAction, buildConnArgs, CUSTOM_VIEW_FIELDS, FEED_ITEM_FIELDS, gql, INITIATIVE_FIELDS, ISSUE_LITE, key, LIST_INPUT_SCHEMA, mergeIssueScopeFilter, PROJECT_FIELDS, requireUnscoped, } from "./shared.js";
|
|
2
|
+
import { bindGetAction, bindListAction, buildConnArgs, CUSTOM_VIEW_FIELDS, FEED_ITEM_FIELDS, gql, INITIATIVE_FIELDS, ISSUE_LITE, key, LIST_INPUT_SCHEMA, mergeIssueScopeFilter, PROJECT_FIELDS, requireUnscoped, withScopedNote, } from "./shared.js";
|
|
3
3
|
const ISSUE_FILTER_DESCRIPTION = "IssueFilter payload. Examples: label { labels: { id: { in: ['label-id'] } } }; project { project: { id: { eq: 'project-id' } } }; assignee { assignee: { id: { eq: 'user-id' } } }; state { state: { id: { eq: 'state-id' } } }; priority { priority: { eq: 1 } }; due window { dueDate: { gte: '2026-06-09', lte: '2026-06-16' } }; combine with and/or arrays.";
|
|
4
4
|
export function registerViewActions(rl) {
|
|
5
5
|
const listAction = bindListAction(rl);
|
|
@@ -7,7 +7,9 @@ export function registerViewActions(rl) {
|
|
|
7
7
|
function customViewConnectionAction(name, description, connectionField, filterTypeName, selection, includeSubTeamsDescription) {
|
|
8
8
|
rl.registerAction(name, {
|
|
9
9
|
access: "read",
|
|
10
|
-
description
|
|
10
|
+
description: connectionField === "issues"
|
|
11
|
+
? description
|
|
12
|
+
: withScopedNote(description),
|
|
11
13
|
inputSchema: t.Object({
|
|
12
14
|
viewId: t.String({ description: "The custom view ID or slug" }),
|
|
13
15
|
...LIST_INPUT_SCHEMA,
|
|
@@ -19,11 +21,11 @@ export function registerViewActions(rl) {
|
|
|
19
21
|
}),
|
|
20
22
|
async execute(input, ctx) {
|
|
21
23
|
const opts = (input ?? {});
|
|
22
|
-
const scopedOpts = connectionField === "issues"
|
|
23
|
-
? { ...opts, filter: mergeIssueScopeFilter(ctx, opts.filter) }
|
|
24
|
-
: opts;
|
|
25
24
|
if (connectionField !== "issues")
|
|
26
25
|
requireUnscoped(ctx, name);
|
|
26
|
+
const scopedOpts = connectionField === "issues"
|
|
27
|
+
? { ...opts, filter: await mergeIssueScopeFilter(ctx, opts.filter) }
|
|
28
|
+
: opts;
|
|
27
29
|
const { argsDecl, argsCall, vars } = buildConnArgs(scopedOpts, filterTypeName);
|
|
28
30
|
const declParts = ["$id: String!", argsDecl.slice(1, -1)];
|
|
29
31
|
const callParts = [argsCall.slice(1, -1)];
|
|
@@ -46,7 +48,7 @@ export function registerViewActions(rl) {
|
|
|
46
48
|
getAction("view.get", "Get a custom view by ID or slug.", "customView", CUSTOM_VIEW_FIELDS);
|
|
47
49
|
rl.registerAction("view.create", {
|
|
48
50
|
access: "write",
|
|
49
|
-
description: "Create a custom view. Set filterData for issue views; projectFilterData, initiativeFilterData, or feedItemFilterData for other view types. Read matches back with view.issues/projects/initiatives/updates.",
|
|
51
|
+
description: withScopedNote("Create a custom view. Set filterData for issue views; projectFilterData, initiativeFilterData, or feedItemFilterData for other view types. Read matches back with view.issues/projects/initiatives/updates."),
|
|
50
52
|
inputSchema: t.Object({
|
|
51
53
|
name: t.String({ description: "The name of the custom view" }),
|
|
52
54
|
description: t.Optional(t.String({ description: "The description of the custom view" })),
|
|
@@ -77,7 +79,7 @@ export function registerViewActions(rl) {
|
|
|
77
79
|
});
|
|
78
80
|
rl.registerAction("view.update", {
|
|
79
81
|
access: "write",
|
|
80
|
-
description: "Update a custom view. All fields optional; only provided fields are updated.",
|
|
82
|
+
description: withScopedNote("Update a custom view. All fields optional; only provided fields are updated."),
|
|
81
83
|
inputSchema: t.Object({
|
|
82
84
|
id: t.String({
|
|
83
85
|
description: "The identifier of the custom view to update",
|
|
@@ -109,7 +111,7 @@ export function registerViewActions(rl) {
|
|
|
109
111
|
});
|
|
110
112
|
rl.registerAction("view.delete", {
|
|
111
113
|
access: "write",
|
|
112
|
-
description: "Delete a custom view.",
|
|
114
|
+
description: withScopedNote("Delete a custom view."),
|
|
113
115
|
inputSchema: t.Object({
|
|
114
116
|
id: t.String({
|
|
115
117
|
description: "The identifier of the custom view to delete",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import { bindGetAction, bindListAction, gql, key, requireUnscoped, WEBHOOK_FIELDS, } from "./shared.js";
|
|
2
|
+
import { bindGetAction, bindListAction, gql, key, requireUnscoped, WEBHOOK_FIELDS, withScopedNote, } from "./shared.js";
|
|
3
3
|
export function registerWebhookActions(rl) {
|
|
4
4
|
const listAction = bindListAction(rl);
|
|
5
5
|
const getAction = bindGetAction(rl);
|
|
@@ -7,7 +7,7 @@ export function registerWebhookActions(rl) {
|
|
|
7
7
|
getAction("webhook.get", "Get a webhook by ID.", "webhook", WEBHOOK_FIELDS);
|
|
8
8
|
rl.registerAction("webhook.create", {
|
|
9
9
|
access: "write",
|
|
10
|
-
description: "Create a webhook. resourceTypes example: ['Issue','Comment','Project'].",
|
|
10
|
+
description: withScopedNote("Create a webhook. resourceTypes example: ['Issue','Comment','Project']."),
|
|
11
11
|
inputSchema: t.Object({
|
|
12
12
|
url: t.String({
|
|
13
13
|
description: "The URL that will be called on data changes",
|
|
@@ -40,7 +40,7 @@ export function registerWebhookActions(rl) {
|
|
|
40
40
|
});
|
|
41
41
|
rl.registerAction("webhook.update", {
|
|
42
42
|
access: "write",
|
|
43
|
-
description: "Update a webhook. teamId and allPublicTeams cannot be changed after creation.",
|
|
43
|
+
description: withScopedNote("Update a webhook. teamId and allPublicTeams cannot be changed after creation."),
|
|
44
44
|
inputSchema: t.Object({
|
|
45
45
|
id: t.String({ description: "The identifier of the webhook to update" }),
|
|
46
46
|
url: t.Optional(t.String({
|
|
@@ -64,7 +64,7 @@ export function registerWebhookActions(rl) {
|
|
|
64
64
|
});
|
|
65
65
|
rl.registerAction("webhook.delete", {
|
|
66
66
|
access: "write",
|
|
67
|
-
description: "Delete a webhook.",
|
|
67
|
+
description: withScopedNote("Delete a webhook."),
|
|
68
68
|
inputSchema: t.Object({
|
|
69
69
|
id: t.String({ description: "The identifier of the webhook to delete" }),
|
|
70
70
|
}),
|
|
@@ -76,7 +76,7 @@ export function registerWebhookActions(rl) {
|
|
|
76
76
|
});
|
|
77
77
|
rl.registerAction("webhook.rotateSecret", {
|
|
78
78
|
access: "write",
|
|
79
|
-
description: "Rotate a webhook's signing secret. Returns the new secret.",
|
|
79
|
+
description: withScopedNote("Rotate a webhook's signing secret. Returns the new secret."),
|
|
80
80
|
inputSchema: t.Object({
|
|
81
81
|
id: t.String({
|
|
82
82
|
description: "The identifier of the webhook to rotate the secret for",
|
|
@@ -18,8 +18,27 @@
|
|
|
18
18
|
import { readImageInput, SEND_FILE_NOTE, writeImageFile, } from "../../_shared/imageFile.js";
|
|
19
19
|
const ENDPOINT = "https://api.openai.com/v1/images/generations";
|
|
20
20
|
const EDIT_ENDPOINT = "https://api.openai.com/v1/images/edits";
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Newest GPT Image model; override per call or via the connection.
|
|
23
|
+
*
|
|
24
|
+
* The GPT Image 2.5 line ships as two models: `gpt-image-2.5-flare`
|
|
25
|
+
* (OpenAI's own default — gpt-image-2 quality at up to half the latency)
|
|
26
|
+
* and `gpt-image-2.5-sunburst` (precision editing). Both widen the
|
|
27
|
+
* quality range and take custom sizes; see QUALITY_RULE and SIZE_RULE.
|
|
28
|
+
* Pin `gpt-image-2` via `defaultModel` to stay on the older line.
|
|
29
|
+
*/
|
|
30
|
+
const DEFAULT_MODEL = "gpt-image-2.5-flare";
|
|
31
|
+
/**
|
|
32
|
+
* The size contract, shared by create and edit so the two cannot drift apart.
|
|
33
|
+
*
|
|
34
|
+
* All four constraints bind together: the total-pixel floor is the one that
|
|
35
|
+
* surprises, because 512x512 satisfies every other rule and still 400s.
|
|
36
|
+
*/
|
|
37
|
+
const SIZE_RULE = "gpt-image-2 and gpt-image-2.5-* take any WIDTHxHEIGHT with both sides multiples of 16, aspect between 1:3 and 3:1, " +
|
|
38
|
+
"no edge over 3840, and a total between 655,360 and 8,294,400 pixels — so 512x512 is too small and 1536x864 is fine; " +
|
|
39
|
+
"above 2560x1440 is experimental. gpt-image-1* and dall-e-* take 1024x1024 | 1536x1024 | 1024x1536.";
|
|
40
|
+
/** Quality tiers, likewise shared. `auto` is what the 2.5 models pick by default. */
|
|
41
|
+
const QUALITY_RULE = "low | medium | high | auto, plus xhigh | max on gpt-image-2.5-*";
|
|
23
42
|
/**
|
|
24
43
|
* Model precedence, shared by create and edit: the call wins, then the
|
|
25
44
|
* connection's `defaultModel` (or OPENAI_IMAGE_MODEL), then the newest
|
|
@@ -41,7 +60,7 @@ export default function openai(rl) {
|
|
|
41
60
|
defaultModel: {
|
|
42
61
|
type: "string",
|
|
43
62
|
required: false,
|
|
44
|
-
description: "Default image model when a call omits `model` — e.g. gpt-image-
|
|
63
|
+
description: "Default image model when a call omits `model` — e.g. gpt-image-2 to pin the older line. Defaults to gpt-image-2.5-flare.",
|
|
45
64
|
env: "OPENAI_IMAGE_MODEL",
|
|
46
65
|
},
|
|
47
66
|
});
|
|
@@ -62,17 +81,17 @@ export default function openai(rl) {
|
|
|
62
81
|
model: {
|
|
63
82
|
type: "string",
|
|
64
83
|
required: false,
|
|
65
|
-
description: "gpt-image-2 | gpt-image-1 | gpt-image-1-mini | dall-e-3 | dall-e-2. Omit to use the connection default.",
|
|
84
|
+
description: "gpt-image-2.5-flare | gpt-image-2.5-sunburst | gpt-image-2 | gpt-image-1 | gpt-image-1-mini | dall-e-3 | dall-e-2. Omit to use the connection default.",
|
|
66
85
|
},
|
|
67
86
|
size: {
|
|
68
87
|
type: "string",
|
|
69
88
|
required: false,
|
|
70
|
-
description:
|
|
89
|
+
description: `WxH (default: 1024x1024) or auto. ${SIZE_RULE}`,
|
|
71
90
|
},
|
|
72
91
|
quality: {
|
|
73
92
|
type: "string",
|
|
74
93
|
required: false,
|
|
75
|
-
description:
|
|
94
|
+
description: `${QUALITY_RULE}; standard | hd (dall-e-3)`,
|
|
76
95
|
},
|
|
77
96
|
style: {
|
|
78
97
|
type: "string",
|
|
@@ -164,17 +183,17 @@ export default function openai(rl) {
|
|
|
164
183
|
model: {
|
|
165
184
|
type: "string",
|
|
166
185
|
required: false,
|
|
167
|
-
description: "gpt-image-2 | gpt-image-1 | gpt-image-1-mini. Omit to use the connection default.",
|
|
186
|
+
description: "gpt-image-2.5-flare | gpt-image-2.5-sunburst | gpt-image-2 | gpt-image-1 | gpt-image-1-mini. Omit to use the connection default.",
|
|
168
187
|
},
|
|
169
188
|
size: {
|
|
170
189
|
type: "string",
|
|
171
190
|
required: false,
|
|
172
|
-
description:
|
|
191
|
+
description: `WxH output size. Omit to let the API match the input. ${SIZE_RULE}`,
|
|
173
192
|
},
|
|
174
193
|
quality: {
|
|
175
194
|
type: "string",
|
|
176
195
|
required: false,
|
|
177
|
-
description:
|
|
196
|
+
description: QUALITY_RULE,
|
|
178
197
|
},
|
|
179
198
|
n: {
|
|
180
199
|
type: "number",
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The offline half of the plugin's progressive disclosure: everything an
|
|
3
|
+
* agent should know before writing questions, without a network call and
|
|
4
|
+
* without pasting the whole of docs.typesafe.ai into every description.
|
|
5
|
+
*
|
|
6
|
+
* `typesafe.guide` serves these topics. Condensed from docs.typesafe.ai
|
|
7
|
+
* (concepts, confidence, patterns, cookbooks) and the jev-1.13 jaggedness
|
|
8
|
+
* page, reviewed 2026-09-16.
|
|
9
|
+
*/
|
|
10
|
+
export const GUIDE_TOPICS = [
|
|
11
|
+
"design",
|
|
12
|
+
"confidence",
|
|
13
|
+
"jaggedness",
|
|
14
|
+
"patterns",
|
|
15
|
+
"cookbooks",
|
|
16
|
+
"limits",
|
|
17
|
+
"sources",
|
|
18
|
+
];
|
|
19
|
+
export const GUIDE = {
|
|
20
|
+
design: {
|
|
21
|
+
model: "Jev is a System One model: it evaluates typed questions against a state and returns typed answers with calibrated " +
|
|
22
|
+
"probabilities. It does not generate text, cannot produce a type error, and cannot invent an option you did not declare. " +
|
|
23
|
+
"Typed output guarantees the interface, not the truth: calibration is a property of groups of predictions, not of " +
|
|
24
|
+
"any single answer, so validate performance in your own domain.",
|
|
25
|
+
shape: "Code stays in control of execution. The model supplies the fuzzy judgment; every branch, threshold, arithmetic step, " +
|
|
26
|
+
"and side effect belongs to your code. Think of a question as a learned semantic branch instruction.",
|
|
27
|
+
rules: [
|
|
28
|
+
"One question, one judgment. Split independently useful dimensions — but not a relationship you are asking about, and atomic does not mean trivial: a bounded action selection is one judgment.",
|
|
29
|
+
"Write literally. The model answers the words in the instruction, not the intent behind them. Name nested state by backticked path.",
|
|
30
|
+
"Send the smallest state that answers the question. Unrelated detail is a distractor and costs accuracy.",
|
|
31
|
+
"Batch every question about a state into one call, including speculative ones, each with its premise stated. Extra questions are near-free in latency, not in tokens.",
|
|
32
|
+
"Sequence a second call only when an answer decides what evidence to fetch, what state to build, or what the next options are.",
|
|
33
|
+
"Keep questions and thresholds together in one module. They are the part humans need to review.",
|
|
34
|
+
"Prefer a closed set the model chooses from over asking it to produce a value. For extraction, find candidates with regex or a generative model, then have Jev pick the right one — and check candidate coverage, because it cannot choose a value you omitted.",
|
|
35
|
+
],
|
|
36
|
+
choosing: {
|
|
37
|
+
choice: "Pick one of a declared set. Routing, classification, ranking a candidate against options, function selection.",
|
|
38
|
+
score: "Rate against an ordered rubric. Severity, frustration, relevance, quality. Levels can be the actions you will take.",
|
|
39
|
+
noul: "One yes/no proposition. Guardrail checks, relevance filters, presence tests. Cheapest way to fan out over a list.",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
confidence: {
|
|
43
|
+
definition: "confidence is a single number from 0 to 1 derived from the answer's own probability distribution: concentrated means " +
|
|
44
|
+
"certain, flat means uncertain. Choice and Score carry it; Noul does not, because its noul value is already the probability.",
|
|
45
|
+
ownMeasure: "The full probabilities map is always returned. If TypeSafe's confidence statistic does not fit your decision, compute " +
|
|
46
|
+
"your own from the distribution — margin between top two, entropy, mass above a level.",
|
|
47
|
+
bands: {
|
|
48
|
+
high: "Act automatically.",
|
|
49
|
+
medium: "Proceed with caution — confirm with the user, flag for review, or gather more state.",
|
|
50
|
+
low: "Consider not acting: route to a human, ask for clarification, or fall back to another system.",
|
|
51
|
+
},
|
|
52
|
+
bandsAreNotALaw: "Confidence summarizes how concentrated the distribution is. It is not a measure of workflow correctness and not " +
|
|
53
|
+
"permission to act. Several genuinely acceptable options spread probability too, so low confidence on a harmless " +
|
|
54
|
+
"preference is not a problem to solve, and uncertainty on a branch you never read can be ignored outright.",
|
|
55
|
+
noulIsNotIntensity: "A Noul near 0.5 means yes and no are about equally likely. It is not a medium-intensity reading — reach for a " +
|
|
56
|
+
"Score when you want degree.",
|
|
57
|
+
thresholds: "A threshold is not one number for the whole system. Gate each action at the level its blast radius deserves: a " +
|
|
58
|
+
"read-only screen can act on a mediocre confidence, an irreversible transfer should not. Start conservative, measure " +
|
|
59
|
+
"against your own labelled data and consequences, and move the numbers deliberately.",
|
|
60
|
+
antipattern: "If all you want is the best option, take the highest-probability choice — no threshold needed. Reach for confidence " +
|
|
61
|
+
"when 'should I act at all' is a real question, and for probabilities when you have a specific statistical algorithm in mind.",
|
|
62
|
+
},
|
|
63
|
+
jaggedness: {
|
|
64
|
+
applies: "jev-1.13, reviewed 2026-09-16.",
|
|
65
|
+
failureModes: [
|
|
66
|
+
{
|
|
67
|
+
mode: "Literal reading",
|
|
68
|
+
detail: "Scoping words, negations, and implied conditions are taken at face value. When you look at a wrong answer and " +
|
|
69
|
+
"catch yourself explaining what you really meant, that explanation is the missing half of the instruction.",
|
|
70
|
+
instead: "Write the exact condition; put boundary cases in the option or level descriptions.",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
mode: "Math and numbers",
|
|
74
|
+
detail: "Not a calculator. Counting is unreliable and the error grows with the size of the thing counted. Numeric " +
|
|
75
|
+
"representations (hex colors, RGB triples, assembly) underperform their semantic equivalents.",
|
|
76
|
+
instead: "Keep arithmetic in code. To count items matching a criterion, ask one Noul per item in a single batched call and sum the results yourself.",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
mode: "Date and time comparison",
|
|
80
|
+
detail: "Dates are read as text, not as ordered quantities. Ordering, distance, and window membership are all unreliable, worse with mixed formats.",
|
|
81
|
+
instead: "Extract the parts as Choices over closed sets (twelve months, thirty-one days) with an explicit 'not stated' option, then assemble and compare in code.",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
mode: "Indirection",
|
|
85
|
+
detail: "Double negatives and properties-of-properties cost accuracy with every hop.",
|
|
86
|
+
instead: "Ask directly, and name the relevant part of the state.",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
mode: "Large state full of irrelevant detail",
|
|
90
|
+
detail: "Accuracy falls as unrelated content grows, and attribution of a wrong answer gets harder.",
|
|
91
|
+
instead: "Retrieve and filter in code first. Where you cannot, use a Noul as a relevance filter before the real questions.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
mode: "Adversarial content",
|
|
95
|
+
detail: "State is treated as data, not as hostile. Injected instructions, misleading framing, and text that argues for its " +
|
|
96
|
+
"own classification can move the answer. This limits how much a single Jev call can be trusted as a standalone guardrail.",
|
|
97
|
+
instead: "Be explicit in the criteria, test edge cases before deploying, and keep a defence in depth around it.",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
mode: "Contradictory instructions and criteria",
|
|
101
|
+
detail: "Criteria that pull against the instruction confuse the model — a Noul whose true means 'no' is the classic case.",
|
|
102
|
+
instead: "Treat criteria as an extension of the instruction, phrased so an average reader would agree they match.",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
mode: "Generation",
|
|
106
|
+
detail: "Not trained to generate. Chaining choices to spell out text is slow and poor.",
|
|
107
|
+
instead: "Bound the answer space and let Jev pick; use a generative model when you genuinely need new text.",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
patterns: {
|
|
112
|
+
speculativeFanOut: "Ask everything you might need about a state in one call, including questions you may never read, stating each " +
|
|
113
|
+
"speculative premise explicitly in its own instructions since the questions cannot see one another. Extra questions " +
|
|
114
|
+
"barely move latency, and a branch that turns out to matter already has its answer.",
|
|
115
|
+
confidenceGatedRouting: "Two axes: the answer says what, the confidence says whether to act. Automate the confident cases, review the rest. " +
|
|
116
|
+
"This is the pattern that makes the model's uncertainty a feature rather than a liability.",
|
|
117
|
+
compositeScoring: "Break a complex judgment into atomic scores and combine them with weights your code owns. The weights stay reviewable " +
|
|
118
|
+
"and tunable without retraining or reprompting anything.",
|
|
119
|
+
intentRouting: "Classify the incoming request, then send it to the cheapest handler that can serve it: deterministic code, a specialist " +
|
|
120
|
+
"LLM, or a human. The classification costs a fraction of a cent and roughly 300ms.",
|
|
121
|
+
cascade: "Screen with Jev, escalate only what survives. A cheap first pass in front of an expensive model recovers most of the " +
|
|
122
|
+
"quality at a fraction of the spend.",
|
|
123
|
+
},
|
|
124
|
+
cookbooks: {
|
|
125
|
+
note: "Worked examples at https://docs.typesafe.ai/cookbooks — the ones closest to agent infrastructure:",
|
|
126
|
+
items: [
|
|
127
|
+
"rerank_typesafe — one question per query/candidate pair re-ranks a BM25 shortlist; top-1 accuracy 5% to 18%, top-10 38% to 62%.",
|
|
128
|
+
"skill_suggestion — picks at most one skill from a 182-skill catalog: one call ranks them all and asks whether a skill is needed, a second reads the top three and may reject all.",
|
|
129
|
+
"classifying_rag_passages — score every retrieved passage, then decide in code what reaches the answering model; drops passages carrying prompt injection.",
|
|
130
|
+
"llm_guardrails — screen messages in and out with hazard questions plus a severity score, then threshold to pass, review, block, or route.",
|
|
131
|
+
"function_calling — map function names and closed-set arguments to confidence-aware questions.",
|
|
132
|
+
"citation_check — decide whether a quote's context supports the claim, with confidence flagging for review.",
|
|
133
|
+
"semantic_find — score hundreds of line ids in one request, plus a Noul for 'is the answer here at all'.",
|
|
134
|
+
"parallel_questions — 13 questions in one call: 12.2x cheaper and 10.0x faster than 13 calls, same answers.",
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
limits: {
|
|
138
|
+
context: [
|
|
139
|
+
"~64k tokens for state plus all questions together.",
|
|
140
|
+
"~32k tokens for state plus the single longest question.",
|
|
141
|
+
"Packing many questions into one call is the efficient use of that budget.",
|
|
142
|
+
],
|
|
143
|
+
rateLimits: "250,000 tokens/sec and 1,200 requests/minute, adjusted dynamically by TypeSafe without notice. This plugin retries " +
|
|
144
|
+
"429 and any 5xx (including the undocumented 503 model_unavailable) with backoff, honouring retry-after; " +
|
|
145
|
+
"connection field maxRetries, default 2.",
|
|
146
|
+
price: "$0.042 per million input tokens. Output tokens are free. Typical call: 70-500ms.",
|
|
147
|
+
modality: "Text only — strings, JSON objects, arrays of text. No images, audio, or video.",
|
|
148
|
+
accuracy: "On TypeSafe's own four-workflow benchmark Jev lands near mid-tier LLM accuracy (~68%) while being one to two orders " +
|
|
149
|
+
"of magnitude cheaper and faster. Those numbers are self-reported and unreproduced; measure on your own data before " +
|
|
150
|
+
"trusting a threshold.",
|
|
151
|
+
},
|
|
152
|
+
sources: {
|
|
153
|
+
trust: "The live docs are the source of truth. Everything above is a condensed snapshot taken 2026-09-16 against jev-1.13: " +
|
|
154
|
+
"good for orientation and for writing a first question, not authoritative. Read the docs for anything " +
|
|
155
|
+
"version-dependent, any model past jev-1.13, or a cookbook you want in full rather than in one line.",
|
|
156
|
+
docsIndex: "https://docs.typesafe.ai/llms.txt lists every page and cookbook. Read targeted pages from it rather than the site.",
|
|
157
|
+
markdown: "Any docs page serves Markdown by appending .md to its path, e.g. https://docs.typesafe.ai/concepts/state.md. " +
|
|
158
|
+
"Resolve relative links against https://docs.typesafe.ai.",
|
|
159
|
+
officialSkill: "TypeSafe publishes an agent skill covering the same ground in more depth: " +
|
|
160
|
+
"https://raw.githubusercontent.com/typesafe-ai/skills/main/skills/typesafe-ai/SKILL.md " +
|
|
161
|
+
"(repo https://github.com/typesafe-ai/skills). This guide is condensed from it and from the docs. Read the skill " +
|
|
162
|
+
"when you are designing a whole workflow; this guide is enough for writing one good question.",
|
|
163
|
+
startHere: {
|
|
164
|
+
"programming model": "https://docs.typesafe.ai/concepts/how-to-build-with-system-one.md",
|
|
165
|
+
"what to build": "https://docs.typesafe.ai/concepts/use-case-map.md",
|
|
166
|
+
"state and questions": "https://docs.typesafe.ai/concepts/state.md, https://docs.typesafe.ai/primitives.md",
|
|
167
|
+
uncertainty: "https://docs.typesafe.ai/confidence.md",
|
|
168
|
+
"wire format": "https://docs.typesafe.ai/api.md",
|
|
169
|
+
"current failure modes": "https://docs.typesafe.ai/model-jaggedness/jev-1.13.md",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import * as t from "typebox";
|
|
2
|
+
import { GUIDE, GUIDE_TOPICS } from "./guide.js";
|
|
3
|
+
import { choiceRubric, noulRubric, questionsSchema, scoreRubric, toWireQuestion, toWireQuestions, } from "./questions.js";
|
|
4
|
+
import { DEFAULT_BASE, DEFAULT_MODEL, evaluate, modelSchema, request, resolveModel, STRICT, stateSchema, } from "./shared.js";
|
|
5
|
+
/**
|
|
6
|
+
* TypeSafe AI's Jev — a System One model — as runline actions.
|
|
7
|
+
*
|
|
8
|
+
* Jev does not generate text. You send a state plus typed questions and it
|
|
9
|
+
* returns values your code branches on, with calibrated probabilities and
|
|
10
|
+
* confidence, in 70-500ms for $0.042 per million input tokens (output free).
|
|
11
|
+
* Every question in a request is evaluated in parallel against the same
|
|
12
|
+
* state, so batching is nearly free.
|
|
13
|
+
*
|
|
14
|
+
* Auth: a TypeSafe API key (bearer), env TYPESAFE_API_KEY.
|
|
15
|
+
*
|
|
16
|
+
* await typesafe.noul({ state: ticket, instructions: "Does this request a refund?" })
|
|
17
|
+
* await typesafe.evaluate({ state: ticket, questions: [...] })
|
|
18
|
+
* await typesafe.guide({ topic: "jaggedness" })
|
|
19
|
+
*/
|
|
20
|
+
const SUMMARY = "Jev (TypeSafe System One) returns typed decisions with calibrated probabilities instead of text — ~70-500ms and a " +
|
|
21
|
+
"fraction of a cent, versus a full LLM turn.";
|
|
22
|
+
const USE_WHEN = "Reach for it whenever a bounded decision is currently costing an LLM turn: intent routing, classification, ranking " +
|
|
23
|
+
"without embeddings, relevance filtering, screening before a tool call. Not for generation, arithmetic, counting, or " +
|
|
24
|
+
"date comparison — read typesafe.guide first if you have not.";
|
|
25
|
+
export default function typesafe(rl) {
|
|
26
|
+
rl.setName("typesafe");
|
|
27
|
+
rl.setVersion("0.1.0");
|
|
28
|
+
rl.setConnectionSchema(t.Object({
|
|
29
|
+
apiKey: t.String({
|
|
30
|
+
env: "TYPESAFE_API_KEY",
|
|
31
|
+
description: "TypeSafe API key, sent as a bearer token. Mint one at https://console.typesafe.ai/settings/keys. Store only in secrets.",
|
|
32
|
+
}),
|
|
33
|
+
model: t.Optional(t.String({
|
|
34
|
+
env: "TYPESAFE_MODEL",
|
|
35
|
+
default: DEFAULT_MODEL,
|
|
36
|
+
description: "Default model for every call. 'jev-latest' (stable) or 'jev-preview', or pin a version such as 'jev-1.13.0' when thresholds are tuned against it.",
|
|
37
|
+
})),
|
|
38
|
+
baseUrl: t.Optional(t.String({
|
|
39
|
+
env: "TYPESAFE_API_BASE",
|
|
40
|
+
default: DEFAULT_BASE,
|
|
41
|
+
description: "TypeSafe API base URL.",
|
|
42
|
+
})),
|
|
43
|
+
maxRetries: t.Optional(t.Integer({
|
|
44
|
+
minimum: 0,
|
|
45
|
+
maximum: 5,
|
|
46
|
+
default: 2,
|
|
47
|
+
description: "Retries with exponential backoff on 429 and on any 5xx, honouring retry-after. Every action here is a pure read, so a retry can never double an effect. A 4xx other than 429 is your bug and never retries.",
|
|
48
|
+
})),
|
|
49
|
+
}));
|
|
50
|
+
/* ---------------------------------------------------------------- */
|
|
51
|
+
/* The primary call */
|
|
52
|
+
/* ---------------------------------------------------------------- */
|
|
53
|
+
rl.registerAction("evaluate", {
|
|
54
|
+
access: "read",
|
|
55
|
+
description: `Ask many typed questions about one state in a single parallel call. ${SUMMARY} ` +
|
|
56
|
+
"Returns one answer per question id, each carrying its full probability distribution and confidence — nothing is " +
|
|
57
|
+
`flattened to a verdict, so your code decides what to act on and what to escalate. ${USE_WHEN}`,
|
|
58
|
+
inputSchema: t.Object({
|
|
59
|
+
state: stateSchema,
|
|
60
|
+
questions: questionsSchema,
|
|
61
|
+
model: t.Optional(modelSchema),
|
|
62
|
+
}, STRICT),
|
|
63
|
+
async execute(input, ctx) {
|
|
64
|
+
const { state, questions, model } = input;
|
|
65
|
+
const response = await evaluate(ctx, state, toWireQuestions(questions), resolveModel(ctx, model));
|
|
66
|
+
return {
|
|
67
|
+
model: response.model,
|
|
68
|
+
answers: response.answers,
|
|
69
|
+
usage: response.usage,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
/* ---------------------------------------------------------------- */
|
|
74
|
+
/* Single-question shorthands */
|
|
75
|
+
/* ---------------------------------------------------------------- */
|
|
76
|
+
const single = (name, description, rubric) => {
|
|
77
|
+
rl.registerAction(name, {
|
|
78
|
+
access: "read",
|
|
79
|
+
description,
|
|
80
|
+
inputSchema: t.Object({
|
|
81
|
+
state: stateSchema,
|
|
82
|
+
...rubric,
|
|
83
|
+
model: t.Optional(modelSchema),
|
|
84
|
+
}, STRICT),
|
|
85
|
+
async execute(input, ctx) {
|
|
86
|
+
const { state, model, ...rest } = input;
|
|
87
|
+
// The rubric fields are this primitive's own, validated against the
|
|
88
|
+
// schema before execute runs; the id names the lone question.
|
|
89
|
+
const question = toWireQuestion({
|
|
90
|
+
...rest,
|
|
91
|
+
id: name,
|
|
92
|
+
type: name,
|
|
93
|
+
});
|
|
94
|
+
const response = await evaluate(ctx, state, { [name]: question }, resolveModel(ctx, model));
|
|
95
|
+
const answer = response.answers[name];
|
|
96
|
+
if (!answer)
|
|
97
|
+
throw new Error(`TypeSafe returned no ${name} answer.`);
|
|
98
|
+
return { ...answer, model: response.model, usage: response.usage };
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
single("choice", "Pick one option from a declared set, with the probability of every option and a confidence score. " +
|
|
103
|
+
`${SUMMARY} Shorthand for a one-question evaluate — when you need more than one judgment about the same state, ` +
|
|
104
|
+
"use typesafe.evaluate instead: questions run in parallel, so a second question costs tokens but barely any " +
|
|
105
|
+
"latency, while a second call costs a whole round trip.", choiceRubric);
|
|
106
|
+
single("score", "Rate the state against an ordered rubric, returning a probability-weighted score, the per-level distribution, and confidence. " +
|
|
107
|
+
`${SUMMARY} Shorthand for a one-question evaluate; batch through typesafe.evaluate when you have several judgments to make.`, scoreRubric);
|
|
108
|
+
single("noul", "Evaluate one yes/no statement, returning the probability it is true (0 to 1). The cheapest primitive: a relevance " +
|
|
109
|
+
`filter, a guardrail check, a presence test. ${SUMMARY} Shorthand for a one-question evaluate; to test a list of ` +
|
|
110
|
+
"items, ask one noul per item through typesafe.evaluate in a single call and combine the results in code.", noulRubric);
|
|
111
|
+
/* ---------------------------------------------------------------- */
|
|
112
|
+
/* Escape hatch and metadata */
|
|
113
|
+
/* ---------------------------------------------------------------- */
|
|
114
|
+
rl.registerAction("raw", {
|
|
115
|
+
access: "read",
|
|
116
|
+
description: "POST a literal TypeSafe request body to /v1/systemone, unmodified. Reach for it only when the typed actions cannot " +
|
|
117
|
+
"express the shape: structured criteria, or a question type added to the API after this plugin. Structured " +
|
|
118
|
+
"instructions need no escape hatch — typesafe.evaluate accepts them. Prefer evaluate: it validates before you spend.",
|
|
119
|
+
inputSchema: t.Object({
|
|
120
|
+
state: stateSchema,
|
|
121
|
+
questions: t.Record(t.String(), t.Unknown(), {
|
|
122
|
+
minProperties: 1,
|
|
123
|
+
description: "The wire-format questions map: your question id -> { type: 'choice' | 'score' | 'noul', instructions, criteria }. " +
|
|
124
|
+
"criteria is an option->description map for choice, an ordered array of level descriptions for score, and " +
|
|
125
|
+
"{ true, false } for noul. Answers return under the same ids.",
|
|
126
|
+
}),
|
|
127
|
+
model: t.Optional(modelSchema),
|
|
128
|
+
}, STRICT),
|
|
129
|
+
async execute(input, ctx) {
|
|
130
|
+
const { state, questions, model } = input;
|
|
131
|
+
return evaluate(ctx, state, questions, resolveModel(ctx, model));
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
rl.registerAction("model.list", {
|
|
135
|
+
access: "read",
|
|
136
|
+
description: "List the models and aliases this account may send in `model`, each with a description and release date. " +
|
|
137
|
+
"Versioned ids such as 'jev-1.13.0' are accepted whether or not they appear here.",
|
|
138
|
+
inputSchema: t.Object({}, STRICT),
|
|
139
|
+
async execute(_input, ctx) {
|
|
140
|
+
const body = await request(ctx, "/v1/models");
|
|
141
|
+
if (!Array.isArray(body.models)) {
|
|
142
|
+
throw new Error("TypeSafe returned a response with no models list.");
|
|
143
|
+
}
|
|
144
|
+
return body.models;
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
rl.registerAction("guide", {
|
|
148
|
+
access: "read",
|
|
149
|
+
description: "How to write questions that work, offline and free — no API key or network call. Read this before your first " +
|
|
150
|
+
"evaluate: it covers question design, the confidence bands and how to set thresholds by blast radius, the eight " +
|
|
151
|
+
"known failure modes of jev-1.13 (literal reading, counting, dates, prompt injection...), the architectural " +
|
|
152
|
+
"patterns, and the hard limits. Most bad results are a question-design problem this answers. It is a condensed " +
|
|
153
|
+
"snapshot of TypeSafe's docs and official agent skill, taken 2026-09-16 against jev-1.13 — topic 'sources' carries " +
|
|
154
|
+
"the links to both, and the live docs win wherever they disagree with this.",
|
|
155
|
+
inputSchema: t.Object({
|
|
156
|
+
topic: t.Optional(t.Union(GUIDE_TOPICS.map((topic) => t.Literal(topic)), {
|
|
157
|
+
description: "design (atomic questions, which primitive to use) | confidence (bands, thresholds by risk) | " +
|
|
158
|
+
"jaggedness (what jev-1.13 gets wrong and what to do instead) | patterns (fan-out, confidence-gated " +
|
|
159
|
+
"routing, composite scoring, intent routing, cascade) | cookbooks (worked examples) | limits (context, " +
|
|
160
|
+
"rate, price, accuracy) | sources (live docs index, the .md convention, TypeSafe's official agent skill, " +
|
|
161
|
+
"and how far to trust this snapshot). Omit for all of it — it is small.",
|
|
162
|
+
})),
|
|
163
|
+
}, STRICT),
|
|
164
|
+
async execute(input) {
|
|
165
|
+
const { topic } = (input ?? {});
|
|
166
|
+
return topic ? { topic, ...GUIDE[topic] } : GUIDE;
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|