@felspar/sdk 0.2.0 → 0.2.1
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/README.md +27 -65
- package/dist/chunks/{chunk-247XZ6IY.js → chunk-B4BWUYQY.js} +4 -5
- package/dist/chunks/{chunk-HACK7D24.js → chunk-XLKRU5RA.js} +54 -10
- package/dist/chunks/{chunk-IY6A2423.js → chunk-Y6KRRIQI.js} +354 -189
- package/dist/draft-action-gate-claude.js +5 -5
- package/dist/draft-action-gate-eve.js +3 -3
- package/dist/draft-action-gate-openai.js +5 -5
- package/dist/draft-action-gate.js +2 -2
- package/dist/draft-hosted-client.js +1 -1
- package/dist/eve.js +3 -3
- package/dist/types/draft-action-gate-cases.d.ts +1 -0
- package/dist/types/draft-action-gate-decisions.d.ts +33 -3
- package/dist/types/draft-action-gate-describe.d.ts +39 -1
- package/dist/types/draft-action-gate-subject.d.ts +33 -0
- package/dist/types/draft-action-gate.d.ts +5 -3
- package/dist/types/draft-hosted-client.d.ts +149 -28
- package/package.json +1 -1
- package/skills/felspar/SKILL.md +5 -4
package/README.md
CHANGED
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
# @felspar/sdk
|
|
2
2
|
|
|
3
|
-
Felspar is a shared, versioned decision system for enterprise agents. A
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
This package connects an agent to Felspar: an [Eve](https://github.com/vercel/eve)
|
|
9
|
-
connection in two lines, a TypeScript client for the hosted Felspar API, an
|
|
10
|
-
action gate that keeps an agent's tools from acting without a verified Felspar
|
|
11
|
-
decision, and the agent skill for running any Felspar job. It runs on Node.js
|
|
12
|
-
24 or later, and its only dependency is `zod`. The SDK is a transport client:
|
|
13
|
-
decisions and policy stay with the Felspar service and the released pack, and
|
|
14
|
-
the SDK does not reimplement them.
|
|
15
|
-
|
|
16
|
-
The modules under `draft/` may change between minor versions while the API
|
|
17
|
-
settles, before 1.0.
|
|
3
|
+
Felspar is a shared, versioned decision system for enterprise agents. A company's approved meaning, the evidence a decision needs and the decision itself travel together in a released Decision Pack. Agents ask Felspar for a decision, get a receipt they can verify, and act only on what Felspar decided.
|
|
4
|
+
|
|
5
|
+
This package connects an agent to Felspar: an [Eve](https://github.com/vercel/eve) connection in two lines, a TypeScript client for the hosted Felspar API, an action gate that keeps an agent's tools from acting without a verified Felspar decision, and the agent skill for running any Felspar job. It runs on Node.js 24 or later, and its only dependency is `zod`. The SDK is a transport client: decisions and policy stay with the Felspar service and the released pack, and the SDK does not reimplement them.
|
|
6
|
+
|
|
7
|
+
The modules under `draft/` may change between minor versions while the API settles, before 1.0.
|
|
18
8
|
|
|
19
9
|
## Install
|
|
20
10
|
|
|
@@ -32,12 +22,7 @@ import { felsparConnection } from "@felspar/sdk/eve";
|
|
|
32
22
|
export default defineMcpClientConnection(felsparConnection());
|
|
33
23
|
```
|
|
34
24
|
|
|
35
|
-
The agent gets Felspar's `get_job_requirements`, `get_job_decision` and
|
|
36
|
-
`check_output` tools and nothing else. The key is the agent's own, created on
|
|
37
|
-
the Felspar web page (Workspace → Agents) and read from `FELSPAR_AGENT_KEY`
|
|
38
|
-
when Eve connects; the endpoint is `FELSPAR_MCP_URL`, or production. Pass
|
|
39
|
-
`{ url, agentKey, description }` to override them. Add the skill (below) so the
|
|
40
|
-
agent knows the method.
|
|
25
|
+
The agent gets Felspar's `list_jobs`, `get_job_requirements`, `get_job_decision` and `check_output` tools and nothing else. The key is the agent's own, created on the Felspar web page (Workspace → Agents) and read from `FELSPAR_AGENT_KEY` when Eve connects; the endpoint is `FELSPAR_MCP_URL`, or production. Pass `{ url, agentKey, description }` to override them. Add the skill (below) so the agent knows the method.
|
|
41
26
|
|
|
42
27
|
## Hosted client
|
|
43
28
|
|
|
@@ -68,18 +53,18 @@ const response = await felspar.resolveFacts(releaseId, {
|
|
|
68
53
|
if (response.status === "ready") {
|
|
69
54
|
console.log(response.result?.outcome, response.result?.receipt);
|
|
70
55
|
}
|
|
56
|
+
|
|
57
|
+
// The jobs this agent key may run, by ID.
|
|
58
|
+
const { jobs } = await felspar.listJobs();
|
|
71
59
|
```
|
|
72
60
|
|
|
73
|
-
|
|
74
|
-
|
|
61
|
+
How a case is identified comes from the job's description (`identity`): send `entityId` for the record's own ID (`provided`), `identity` field values (`fields`), or nothing (`content`, and the first decision of an `issued` job). The result's `case` gives the case's `id` and `mode`; send `case.id` as `entityId` for every later decision on the same case. Results are typed (`FelsparJobResult`): `case`, `subject` with its `related` records, `escalation` (`id`, `trigger`, `why`) and `missing` for a gap.
|
|
62
|
+
|
|
63
|
+
A refused request throws `FelsparRequestError` with the HTTP `status` and the service's `refusal` (its `error` code, where there is one a `fix`, and any `escalation` it names, such as for `attempt_limit_reached`).
|
|
75
64
|
|
|
76
65
|
## Action gate
|
|
77
66
|
|
|
78
|
-
The gate sits in front of a tool that acts, such as writing an email draft or
|
|
79
|
-
replying to a ticket. It allows a call only when the call's fields are allowed,
|
|
80
|
-
the call is about a case Felspar decided on in this session, Felspar
|
|
81
|
-
re-verifies that decision's receipt, and the decided outcome permits the tool.
|
|
82
|
-
Any error, and anything unknown or ambiguous, denies.
|
|
67
|
+
The gate sits in front of a tool that acts, such as writing an email draft or replying to a ticket. It allows a call only when the call's fields are allowed, the call is about a case Felspar decided on in this session, Felspar re-verifies that decision's receipt, and the decided outcome permits the tool. Any error, and anything unknown or ambiguous, denies.
|
|
83
68
|
|
|
84
69
|
```ts
|
|
85
70
|
import {
|
|
@@ -110,31 +95,16 @@ const gate = createActionGate({
|
|
|
110
95
|
|
|
111
96
|
What a tool may do comes from the released pack, not from the gate's config:
|
|
112
97
|
|
|
113
|
-
- `writes` names the output the tool writes, by its id in the pack. The call is
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
- `entity`
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Recipients are then not tied to the case, so a tool that can address anyone
|
|
124
|
-
should also limit them (`testRecipients`, or the tool's own settings).
|
|
125
|
-
- `outputCheck` requires that Felspar passed exactly the text the call sends
|
|
126
|
-
for that decision (`check_output`); with `writes`, for that output.
|
|
127
|
-
|
|
128
|
-
A denial is `{ allow: false, reason, escalate? }`. `escalate` is the company's
|
|
129
|
-
next step, quoted from the released description: the outcome's next step when
|
|
130
|
-
the outcome doesn't permit the tool, or what to do when nothing is decided. For
|
|
131
|
-
a missing or failed output check it is the fixed instruction to fix what
|
|
132
|
-
`check_output` flagged and check again. It is absent when the description
|
|
133
|
-
doesn't say. Every adapter below shows it to the
|
|
134
|
-
model with the reason.
|
|
135
|
-
|
|
136
|
-
Then connect the gate to your agent framework. Tool names must match exactly
|
|
137
|
-
as the runtime reports them.
|
|
98
|
+
- `writes` names the output the tool writes, by its id in the pack. The call is allowed only when the released job description lists that output under the outcome Felspar's replay reports. (A rule may give `outcomes`, a list of outcome ids, instead; exactly one of the two.)
|
|
99
|
+
- Which case a call is about comes from the released job's subject and identity. A decision is recorded by the `case.id` Felspar's result names (so an agent that sends no `entity_id`, for an issued, fields or content job, still gets one), and Felspar's replay must name the same case ID and mode. By default:
|
|
100
|
+
- for a job whose subject is a provided, case-insensitive ID such as a web domain (or a release with no subject), every recipient's email domain must belong to the one company Felspar decided on; a decided ID that isn't a company domain allows no recipient, so give such a rule an entity field;
|
|
101
|
+
- for any other job, the call is about the one case decided in the session for this job; with several, the call is denied until the rule names a field.
|
|
102
|
+
- `entity: { field }` overrides that: the input field must hold the case's ID, the record's own ID (such as a ticket ID) or the `case.id` Felspar issued or derived, trimmed and matched by the subject's match rule (exact unless the subject is case-insensitive; with no subject, `HC-1201` and `hc-1201` are two cases). Recipients are then not tied to the case, so a tool that can address anyone should also limit them (`testRecipients`, or the tool's own settings).
|
|
103
|
+
- `outputCheck` requires that Felspar passed exactly the text the call sends for that decision (`check_output`); with `writes`, for that output.
|
|
104
|
+
|
|
105
|
+
A denial is `{ allow: false, reason, escalate?, escalation? }`. `escalate` is the company's next step, quoted from the released description: the outcome's next step when the outcome doesn't permit the tool, or what to do when nothing is decided. For a missing or failed output check it is the fixed instruction to fix what `check_output` flagged and check again. `escalation` is the hand-off the policy names: `{ id, trigger, why }`, with the id from Felspar's replay of the outcome (or the job's unresolved path) and `why` quoted from the release. Both are absent when the release doesn't say. Every adapter below shows them to the model with the reason.
|
|
106
|
+
|
|
107
|
+
Then connect the gate to your agent framework. Tool names must match exactly as the runtime reports them.
|
|
138
108
|
|
|
139
109
|
### Eve
|
|
140
110
|
|
|
@@ -174,28 +144,20 @@ toolInputGuardrails: [felsparToolInputGuardrail(gate, { sessionOf })],
|
|
|
174
144
|
toolOutputGuardrails: [felsparDecisionOutputGuardrail(gate, { sessionOf })],
|
|
175
145
|
```
|
|
176
146
|
|
|
177
|
-
Outside an agent framework,
|
|
178
|
-
`checkAction({ rules, toolName, input, decisions, verify, describe })` returns
|
|
179
|
-
the same verdict.
|
|
147
|
+
Outside an agent framework, `checkAction({ rules, toolName, input, decisions, verify, describe })` returns the same verdict.
|
|
180
148
|
|
|
181
|
-
A Felspar receipt records what Felspar decided from the evidence it was given.
|
|
182
|
-
It is not a factual, legal or security certification, and a passed output
|
|
183
|
-
check means the text follows the released rules, not that it is true or
|
|
184
|
-
approved to send.
|
|
149
|
+
A Felspar receipt records what Felspar decided from the evidence it was given. It is not a factual, legal or security certification, and a passed output check means the text follows the released rules, not that it is true or approved to send.
|
|
185
150
|
|
|
186
151
|
## Agent skill
|
|
187
152
|
|
|
188
|
-
The package ships the skill that teaches an agent the method for any Felspar
|
|
189
|
-
job: get the requirements, gather exact evidence, get the decision, write and
|
|
190
|
-
check what the decision asks for, and escalate with the company's next step.
|
|
153
|
+
The package ships the skill that teaches an agent the method for any Felspar job: get the requirements, gather exact evidence, get the decision, write and check what the decision asks for, and escalate with the company's next step.
|
|
191
154
|
|
|
192
155
|
```bash
|
|
193
156
|
npx @felspar/sdk skill # writes agent/skills/felspar/SKILL.md
|
|
194
157
|
npx @felspar/sdk skill .claude --force
|
|
195
158
|
```
|
|
196
159
|
|
|
197
|
-
The directory defaults to `agent`, where an Eve agent keeps its skills; an
|
|
198
|
-
existing copy is kept unless you pass `--force`.
|
|
160
|
+
The directory defaults to `agent`, where an Eve agent keeps its skills; an existing copy is kept unless you pass `--force`.
|
|
199
161
|
|
|
200
162
|
## Documentation
|
|
201
163
|
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
denialText,
|
|
3
3
|
entityIdOf,
|
|
4
4
|
jobOf
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-Y6KRRIQI.js";
|
|
6
6
|
|
|
7
7
|
// src/draft-action-gate-eve.ts
|
|
8
8
|
function felsparGate(gate) {
|
|
@@ -22,11 +22,10 @@ function felsparDecisionEvents(gate) {
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
for (const action of actions) {
|
|
25
|
-
const entity = entityIdOf(action.input);
|
|
26
|
-
if (entity !== void 0 && gate.isDecisionTool(action.toolName ?? "")) {
|
|
27
|
-
gate.decisions.noteRequest(action.callId, entity);
|
|
28
|
-
}
|
|
29
25
|
const job = jobOf(action.input);
|
|
26
|
+
if (gate.isDecisionTool(action.toolName ?? "")) {
|
|
27
|
+
gate.decisions.noteRequest(action.callId, { entity: entityIdOf(action.input), job });
|
|
28
|
+
}
|
|
30
29
|
if (job !== void 0 && gate.isOutputCheckTool(action.toolName ?? "")) {
|
|
31
30
|
checks.set(action.callId, job);
|
|
32
31
|
}
|
|
@@ -5060,6 +5060,35 @@ var HostedOutputCheckReplySchema = z56.object({
|
|
|
5060
5060
|
decision: z56.object({ executionId: z56.string(), receiptDigest: digestText }),
|
|
5061
5061
|
actionAuthorized: z56.literal(false)
|
|
5062
5062
|
}).loose();
|
|
5063
|
+
var quotedWords = z56.object({ text: z56.string() }).loose();
|
|
5064
|
+
var FelsparCaseSchema = z56.object({ id: z56.string().min(1).max(200), mode: z56.enum(identityModes) });
|
|
5065
|
+
var FelsparRelatedSchema = z56.object({
|
|
5066
|
+
relationship: z56.string(),
|
|
5067
|
+
subject: z56.string(),
|
|
5068
|
+
id: z56.string(),
|
|
5069
|
+
questions: z56.array(z56.string())
|
|
5070
|
+
});
|
|
5071
|
+
var FelsparResultSubjectSchema = z56.object({
|
|
5072
|
+
type: z56.string(),
|
|
5073
|
+
id: z56.string(),
|
|
5074
|
+
related: z56.array(FelsparRelatedSchema)
|
|
5075
|
+
});
|
|
5076
|
+
var FelsparEscalationSchema = z56.object({
|
|
5077
|
+
id: z56.string().min(1).max(200),
|
|
5078
|
+
trigger: z56.enum(escalationTriggers),
|
|
5079
|
+
why: quotedWords
|
|
5080
|
+
});
|
|
5081
|
+
var FelsparJobResultSchema = HostedJobResultSchema.extend({
|
|
5082
|
+
result: HostedJobResultSchema.shape.result.unwrap().extend({
|
|
5083
|
+
case: FelsparCaseSchema.optional(),
|
|
5084
|
+
subject: FelsparResultSubjectSchema.optional(),
|
|
5085
|
+
escalation: FelsparEscalationSchema.optional(),
|
|
5086
|
+
missing: z56.array(z56.string()).optional()
|
|
5087
|
+
}).nullable()
|
|
5088
|
+
});
|
|
5089
|
+
var FelsparJobListSchema = z56.object({
|
|
5090
|
+
jobs: z56.array(z56.object({ id: z56.string().min(1).max(200) })).max(256)
|
|
5091
|
+
});
|
|
5063
5092
|
var hostedPartsContentType = "application/x.felspar-content-parts";
|
|
5064
5093
|
async function partsBody(value) {
|
|
5065
5094
|
const packed = await packContentParts(value);
|
|
@@ -5067,10 +5096,10 @@ async function partsBody(value) {
|
|
|
5067
5096
|
}
|
|
5068
5097
|
async function factsRequest(value, describeRelease) {
|
|
5069
5098
|
if (typeof value.expectedReleaseDigest === "string") {
|
|
5070
|
-
return
|
|
5099
|
+
return HostedFactsRequestSchema.parse({ mode: "facts", ...value });
|
|
5071
5100
|
}
|
|
5072
5101
|
const described = await describeRelease(HostedObjectSchema.shape.id.parse(value.jobId));
|
|
5073
|
-
return
|
|
5102
|
+
return HostedFactsRequestSchema.parse({
|
|
5074
5103
|
mode: "facts",
|
|
5075
5104
|
...value,
|
|
5076
5105
|
expectedReleaseDigest: described.release.releaseDigest
|
|
@@ -5112,16 +5141,24 @@ ${jobId}`;
|
|
|
5112
5141
|
}
|
|
5113
5142
|
var refusalCode = z56.string().regex(/^[a-z][a-z_]{2,63}$/u);
|
|
5114
5143
|
var fieldPath = z56.array(z56.string().max(256)).max(32);
|
|
5144
|
+
var refusalEscalationSchema = z56.object({
|
|
5145
|
+
id: z56.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9._:/@-]{0,199}$/u),
|
|
5146
|
+
trigger: z56.enum(escalationTriggers),
|
|
5147
|
+
why: z56.object({ text: z56.string().max(2e4) })
|
|
5148
|
+
});
|
|
5115
5149
|
var refusalSchema = z56.object({
|
|
5116
5150
|
error: refusalCode,
|
|
5117
5151
|
path: fieldPath.optional(),
|
|
5118
5152
|
currentReleaseDigest: z56.string().regex(/^sha256:[a-f0-9]{64}$/u).optional(),
|
|
5119
5153
|
retryAfterSeconds: z56.number().int().min(1).max(3600).optional(),
|
|
5154
|
+
escalation: z56.unknown().optional(),
|
|
5120
5155
|
issues: z56.array(z56.object({ code: refusalCode, path: fieldPath })).max(64).optional()
|
|
5121
|
-
}).transform((value) => {
|
|
5156
|
+
}).transform(({ escalation: named, ...value }) => {
|
|
5122
5157
|
const fix = hostedRefusalFix(value.error);
|
|
5158
|
+
const escalation = refusalEscalationSchema.safeParse(named).data;
|
|
5123
5159
|
return {
|
|
5124
5160
|
...value,
|
|
5161
|
+
...escalation !== void 0 && { escalation },
|
|
5125
5162
|
...fix !== null && { fix },
|
|
5126
5163
|
...value.issues && {
|
|
5127
5164
|
issues: value.issues.map((issue) => {
|
|
@@ -5172,6 +5209,18 @@ function outputCheckCalls(call) {
|
|
|
5172
5209
|
readOutputCheck: async (job, checkId) => await call(`${checksOf(job)}/${encodeURIComponent(objectId.parse(checkId))}`, HostedOutputCheckReplySchema)
|
|
5173
5210
|
};
|
|
5174
5211
|
}
|
|
5212
|
+
function agentCalls(call) {
|
|
5213
|
+
const objectId = HostedObjectSchema.shape.id;
|
|
5214
|
+
return {
|
|
5215
|
+
listJobs: async () => await call("/v0/jobs", FelsparJobListSchema),
|
|
5216
|
+
resolveJob: async (id12, value) => {
|
|
5217
|
+
const input = HostedSuppliedJobInputSchema.parse(value);
|
|
5218
|
+
const releaseId = objectId.parse(id12);
|
|
5219
|
+
const path3 = `/v0/releases/${encodeURIComponent(releaseId)}/jobs`;
|
|
5220
|
+
return pinnedResult(await call(path3, FelsparJobResultSchema, input), releaseId, input);
|
|
5221
|
+
}
|
|
5222
|
+
};
|
|
5223
|
+
}
|
|
5175
5224
|
function createHostedFelsparClient(configuration) {
|
|
5176
5225
|
const origin = new URL(configuration.origin);
|
|
5177
5226
|
const invalid = [
|
|
@@ -5218,17 +5267,12 @@ function createHostedFelsparClient(configuration) {
|
|
|
5218
5267
|
verifyReceipt: async (executionObjectId, value) => await call(`/v0/executions/${encodeURIComponent(objectId.parse(executionObjectId))}/verify`, HostedReceiptVerificationSchema, HostedReceiptVerificationInputSchema.parse(value)),
|
|
5219
5268
|
...outputCheckCalls(call),
|
|
5220
5269
|
executeJob: async (id12, value) => await call(`/v0/releases/${encodeURIComponent(objectId.parse(id12))}/jobs`, HostedJobResultSchema, HostedJobInputSchema.parse(value)),
|
|
5270
|
+
...agentCalls(call),
|
|
5221
5271
|
resolveFacts: async (id12, value) => {
|
|
5222
5272
|
const releaseId = objectId.parse(id12);
|
|
5223
5273
|
const input = await factsRequest(value, async (jobId) => await describe(releaseId, jobId));
|
|
5224
5274
|
const path3 = `/v0/releases/${encodeURIComponent(releaseId)}/jobs`;
|
|
5225
|
-
return pinnedResult(await call(path3,
|
|
5226
|
-
},
|
|
5227
|
-
resolveJob: async (id12, value) => {
|
|
5228
|
-
const input = HostedSuppliedJobInputSchema.parse(value);
|
|
5229
|
-
const releaseId = objectId.parse(id12);
|
|
5230
|
-
const response = await call(`/v0/releases/${encodeURIComponent(releaseId)}/jobs`, HostedJobResultSchema, input);
|
|
5231
|
-
return pinnedResult(response, releaseId, input);
|
|
5275
|
+
return pinnedResult(await call(path3, FelsparJobResultSchema, input), releaseId, input);
|
|
5232
5276
|
}
|
|
5233
5277
|
};
|
|
5234
5278
|
}
|