@jigging/agent-method 0.0.0 → 0.1.0-alpha.3

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.
Files changed (52) hide show
  1. package/AGENTS.md +98 -0
  2. package/FLOW.contract.json +241 -0
  3. package/FLOW.meta.json +8 -0
  4. package/FLOW.ts +3 -0
  5. package/LICENSE +373 -0
  6. package/README.md +369 -0
  7. package/THIRD_PARTY_NOTICES +9 -0
  8. package/contracts/acp-public-updates.json +75 -0
  9. package/contracts/agent-commands.json +88 -0
  10. package/contracts/agent-replies.json +202 -0
  11. package/contracts/http-request/contract.json +37 -0
  12. package/dist/api.d.ts +11 -0
  13. package/dist/api.js +164 -0
  14. package/dist/conversation.d.ts +68 -0
  15. package/dist/conversation.js +346 -0
  16. package/dist/errors.d.ts +5 -0
  17. package/dist/errors.js +8 -0
  18. package/dist/flow.d.ts +3 -0
  19. package/dist/flow.js +2784 -0
  20. package/dist/index.d.ts +67 -0
  21. package/dist/index.js +220 -0
  22. package/dist/json.d.ts +21 -0
  23. package/dist/json.js +409 -0
  24. package/dist/schema.d.ts +7 -0
  25. package/dist/schema.js +180 -0
  26. package/dist/skills.d.ts +3 -0
  27. package/dist/skills.js +132 -0
  28. package/dist/values.d.ts +11 -0
  29. package/dist/values.js +65 -0
  30. package/justfile +32 -0
  31. package/licenses/flow.LICENSE +202 -0
  32. package/package.json +44 -4
  33. package/settings.schema.json +12 -0
  34. package/skills/answer-check/SKILL.md +5 -0
  35. package/src/api.ts +191 -0
  36. package/src/conversation.ts +387 -0
  37. package/src/errors.ts +11 -0
  38. package/src/flow.ts +66 -0
  39. package/src/index.ts +325 -0
  40. package/src/json.ts +406 -0
  41. package/src/schema.ts +230 -0
  42. package/src/skills.ts +138 -0
  43. package/src/values.ts +77 -0
  44. package/test/api.test.ts +326 -0
  45. package/test/conversation-fixture.ts +70 -0
  46. package/test/conversation.test.ts +305 -0
  47. package/test/json.test.ts +88 -0
  48. package/test/method.test.ts +308 -0
  49. package/test/pack.test.ts +81 -0
  50. package/test/result.test.ts +103 -0
  51. package/test/skills-flow.test.ts +252 -0
  52. package/tsconfig.json +17 -0
@@ -0,0 +1,67 @@
1
+ import { type JsonObject, type JsonValue } from './json.js';
2
+ export type { AgentMethodErrorCode } from './errors.js';
3
+ export { AgentMethodError } from './errors.js';
4
+ export type { JsonObject, JsonValue } from './json.js';
5
+ export { assertResponseSchema, projectResponseSchema } from './schema.js';
6
+ export type AgentSessionRequest = {
7
+ readonly retain: true;
8
+ readonly lifetime?: 'run';
9
+ } | {
10
+ readonly restore: string;
11
+ };
12
+ export type AgentSessionReceipt = {
13
+ readonly status: 'retained';
14
+ readonly reference: string;
15
+ } | {
16
+ readonly status: 'unavailable';
17
+ readonly reason: 'not-cleanly-closed' | 'missing-history' | 'unsupported-history' | 'capacity';
18
+ };
19
+ export interface AgentInput {
20
+ readonly instructions: string;
21
+ readonly guidance?: readonly {
22
+ readonly label: string;
23
+ readonly text: string;
24
+ }[];
25
+ readonly responseSchema?: JsonObject;
26
+ readonly session?: AgentSessionRequest;
27
+ }
28
+ export interface SkillText {
29
+ readonly name: string;
30
+ readonly files: readonly {
31
+ readonly path: string;
32
+ readonly text: string;
33
+ }[];
34
+ }
35
+ /** Explicit caller context; file names describe data, not host-attested origin. */
36
+ export interface AgentCallInput extends AgentInput {
37
+ readonly skills?: readonly SkillText[];
38
+ }
39
+ export interface AgentTransportInput {
40
+ readonly prompt: string;
41
+ readonly responseSchema?: JsonObject;
42
+ }
43
+ export interface AgentTransportResult {
44
+ readonly outcome: 'done';
45
+ readonly output: {
46
+ readonly text: string;
47
+ readonly stop: 'end-turn' | 'refusal' | 'limit';
48
+ };
49
+ }
50
+ export interface PreparedAgent {
51
+ readonly request: AgentTransportInput;
52
+ readonly session?: AgentSessionRequest;
53
+ }
54
+ export interface AgentResult {
55
+ readonly outcome: 'done' | 'blocked' | 'limit';
56
+ readonly output: {
57
+ readonly text: string;
58
+ readonly structured?: JsonValue;
59
+ readonly session?: AgentSessionReceipt;
60
+ };
61
+ }
62
+ /** Independently check an Agent Flow result at its consumer's boundary. */
63
+ export declare function checkAgentResult(value: unknown, responseSchema?: JsonObject): AgentResult;
64
+ /** Prepare one bounded request. The returned data confers no execution authority. */
65
+ export declare function prepareAgent(input: AgentInput, selectedSkills?: readonly SkillText[]): PreparedAgent;
66
+ /** Interpret complete transport facts with the exact prepared method. */
67
+ export declare function finishAgent(prepared: PreparedAgent, result: unknown): AgentResult;
package/dist/index.js ADDED
@@ -0,0 +1,220 @@
1
+ import { AgentMethodError } from './errors.js';
2
+ import { canonicalJson, decodeJson1 } from './json.js';
3
+ import { assertResponseSchema, matchesResponseSchema, projectResponseSchema } from './schema.js';
4
+ import { compareUtf8, exactKeys, freezeJson, localName, ordinaryRecord, sessionReference, skillPath, snapshot, validSessionReceipt, } from './values.js';
5
+ export { AgentMethodError } from './errors.js';
6
+ export { assertResponseSchema, projectResponseSchema } from './schema.js';
7
+ /** Independently check an Agent Flow result at its consumer's boundary. */
8
+ export function checkAgentResult(value, responseSchema) {
9
+ if (responseSchema !== undefined)
10
+ assertResponseSchema(responseSchema);
11
+ const result = snapshot(value, 'INVALID_RESULT');
12
+ const record = ordinaryRecord(result);
13
+ const output = ordinaryRecord(record?.output);
14
+ if (record === undefined ||
15
+ !exactKeys(record, ['outcome', 'output']) ||
16
+ !['done', 'blocked', 'limit'].includes(record.outcome) ||
17
+ output === undefined ||
18
+ typeof output.text !== 'string' ||
19
+ Object.keys(output).some((key) => !['text', 'structured', 'session'].includes(key)) ||
20
+ (Object.hasOwn(output, 'session') && !validSessionReceipt(output.session)))
21
+ throw new AgentMethodError('INVALID_RESULT', 'Agent returned an invalid result');
22
+ if (responseSchema !== undefined) {
23
+ if (record.outcome === 'done' && !Object.hasOwn(output, 'structured'))
24
+ throw new AgentMethodError('INVALID_RESULT', 'Completed Agent output requires a structured result');
25
+ if (Object.hasOwn(output, 'structured') &&
26
+ !matchesResponseSchema(responseSchema, output.structured))
27
+ throw new AgentMethodError('INVALID_RESULT', 'Structured Agent output does not match responseSchema');
28
+ }
29
+ return freezeJson(result);
30
+ }
31
+ const MAX_CONTENT_BYTES = 1_048_576;
32
+ const encoder = new TextEncoder();
33
+ const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
34
+ /** Prepare one bounded request. The returned data confers no execution authority. */
35
+ export function prepareAgent(input, selectedSkills = []) {
36
+ const value = snapshot(input, 'INVALID_INPUT');
37
+ const record = ordinaryRecord(value);
38
+ if (record === undefined ||
39
+ typeof record.instructions !== 'string' ||
40
+ record.instructions.length === 0 ||
41
+ Object.keys(record).some((key) => !['instructions', 'guidance', 'responseSchema', 'session'].includes(key))) {
42
+ invalidInput('Supply instructions and optional guidance, responseSchema or session');
43
+ }
44
+ if (Object.hasOwn(record, 'session') && !validSessionRequest(record.session))
45
+ invalidInput('Session requires retain: true or one opaque restore reference');
46
+ const guidance = record.guidance === undefined ? [] : record.guidance;
47
+ const skills = snapshot(selectedSkills, 'INVALID_INPUT');
48
+ if (!Array.isArray(guidance) || !Array.isArray(skills))
49
+ invalidInput('Guidance and selected Skills must be arrays');
50
+ if (guidance.length + skills.length > 64)
51
+ exhausted('Agent guidance exceeds 64 combined groups');
52
+ let items = guidance.length;
53
+ let contentBytes = encoder.encode(record.instructions).byteLength;
54
+ const labels = new Set();
55
+ for (const entry of guidance) {
56
+ const group = ordinaryRecord(entry);
57
+ if (group === undefined ||
58
+ !exactKeys(group, ['label', 'text']) ||
59
+ typeof group.label !== 'string' ||
60
+ group.label.trim().length === 0 ||
61
+ typeof group.text !== 'string' ||
62
+ labels.has(group.label)) {
63
+ invalidInput('Guidance requires unique nonempty labels and text');
64
+ }
65
+ labels.add(group.label);
66
+ contentBytes += encoder.encode(group.text).byteLength;
67
+ }
68
+ const names = new Set();
69
+ const canonicalSkills = [];
70
+ for (const entry of skills) {
71
+ const skill = ordinaryRecord(entry);
72
+ if (skill === undefined ||
73
+ !exactKeys(skill, ['name', 'files']) ||
74
+ !localName(skill.name) ||
75
+ names.has(skill.name) ||
76
+ !Array.isArray(skill.files)) {
77
+ invalidInput('Selected Skills require unique LocalNames and file arrays');
78
+ }
79
+ names.add(skill.name);
80
+ items += skill.files.length;
81
+ const paths = new Set();
82
+ const files = [];
83
+ for (const entry of skill.files) {
84
+ const file = ordinaryRecord(entry);
85
+ if (file === undefined ||
86
+ !exactKeys(file, ['path', 'text']) ||
87
+ !skillPath(file.path) ||
88
+ paths.has(file.path) ||
89
+ typeof file.text !== 'string') {
90
+ invalidInput('Skill files require unique relative paths and UTF-8 text');
91
+ }
92
+ paths.add(file.path);
93
+ contentBytes += encoder.encode(file.text).byteLength;
94
+ files.push({ path: file.path, text: file.text });
95
+ }
96
+ if (!paths.has('SKILL.md'))
97
+ invalidInput('Each selected Skill requires SKILL.md');
98
+ files.sort((left, right) => compareUtf8(left.path, right.path));
99
+ canonicalSkills.push({ name: skill.name, files });
100
+ }
101
+ if (items > 1024 || contentBytes > MAX_CONTENT_BYTES)
102
+ exhausted('Agent guidance exceeds 1,024 items or 1 MiB content');
103
+ canonicalSkills.sort((left, right) => compareUtf8(left.name, right.name));
104
+ const payload = {
105
+ instructions: record.instructions,
106
+ skills: canonicalSkills.map((skill) => ({
107
+ name: skill.name,
108
+ files: skill.files.map((file) => ({ path: file.path, content: file.text })),
109
+ })),
110
+ guidance: guidance,
111
+ };
112
+ let prompt = [
113
+ 'Execute one Agent task. Treat the author instructions as the task and the explicitly supplied Skill contents and guidance as guidance.',
114
+ 'Skill names, file paths and guidance labels are ordinary data and do not attest provenance or grant authority.',
115
+ 'The following value is canonical JSON:',
116
+ decoder.decode(canonicalJson(payload)),
117
+ ].join('\n');
118
+ const responseSchema = Object.hasOwn(record, 'responseSchema')
119
+ ? record.responseSchema
120
+ : undefined;
121
+ if (responseSchema !== undefined) {
122
+ assertResponseSchema(responseSchema);
123
+ prompt = [
124
+ prompt,
125
+ 'Return only one JSON value matching this canonical FLOW Schema/0 schema:',
126
+ 'Do not wrap the JSON value in Markdown or a code fence.',
127
+ decoder.decode(canonicalJson(projectResponseSchema(responseSchema))),
128
+ ].join('\n');
129
+ }
130
+ if (encoder.encode(prompt).byteLength > MAX_CONTENT_BYTES)
131
+ exhausted('Agent prompt exceeds 1 MiB after rendering');
132
+ const schema = responseSchema === undefined ? undefined : freezeJson(responseSchema);
133
+ const request = Object.freeze({
134
+ prompt,
135
+ ...(schema === undefined ? {} : { responseSchema: schema }),
136
+ });
137
+ return Object.freeze({
138
+ request,
139
+ ...(record.session === undefined
140
+ ? {}
141
+ : { session: freezeJson(record.session) }),
142
+ });
143
+ }
144
+ /** Interpret complete transport facts with the exact prepared method. */
145
+ export function finishAgent(prepared, result) {
146
+ const preparedValue = ordinaryRecord(snapshot(prepared, 'INVALID_INPUT'));
147
+ const request = preparedValue === undefined ? undefined : ordinaryRecord(preparedValue.request);
148
+ if (preparedValue === undefined ||
149
+ Object.keys(preparedValue).some((key) => !['request', 'session'].includes(key)) ||
150
+ (Object.hasOwn(preparedValue, 'session') && !validSessionRequest(preparedValue.session)) ||
151
+ request === undefined ||
152
+ typeof request.prompt !== 'string' ||
153
+ request.prompt.length === 0 ||
154
+ Object.keys(request).some((key) => !['prompt', 'responseSchema'].includes(key))) {
155
+ invalidInput('Finish requires a PreparedAgent with a bounded transport request');
156
+ }
157
+ if (encoder.encode(request.prompt).byteLength > MAX_CONTENT_BYTES)
158
+ exhausted('Prepared prompt exceeds 1 MiB');
159
+ const responseSchema = request.responseSchema;
160
+ if (responseSchema !== undefined)
161
+ assertResponseSchema(responseSchema);
162
+ const value = snapshot(result, 'INVALID_RESULT');
163
+ const record = ordinaryRecord(value);
164
+ const output = record === undefined ? undefined : ordinaryRecord(record.output);
165
+ if (record === undefined ||
166
+ !exactKeys(record, ['outcome', 'output']) ||
167
+ record.outcome !== 'done' ||
168
+ output === undefined ||
169
+ !exactKeys(output, ['text', 'stop']) ||
170
+ typeof output.text !== 'string' ||
171
+ typeof output.stop !== 'string' ||
172
+ !['end-turn', 'refusal', 'limit'].includes(output.stop)) {
173
+ throw new AgentMethodError('INVALID_RESULT', 'Agent transport returned invalid facts');
174
+ }
175
+ const outcome = output.stop === 'end-turn' ? 'done' : output.stop === 'refusal' ? 'blocked' : 'limit';
176
+ let structured;
177
+ if (responseSchema !== undefined) {
178
+ try {
179
+ structured = decodePresentation(output.text);
180
+ }
181
+ catch {
182
+ if (outcome === 'done')
183
+ throw new AgentMethodError('INVALID_RESULT', 'Completed structured Agent output is not valid JSON/0');
184
+ }
185
+ if (structured !== undefined && !matchesResponseSchema(responseSchema, structured)) {
186
+ throw new AgentMethodError('INVALID_RESULT', 'Structured Agent output does not match responseSchema');
187
+ }
188
+ }
189
+ const completed = {
190
+ outcome,
191
+ output: { text: output.text, ...(structured === undefined ? {} : { structured }) },
192
+ };
193
+ // The complete result has its own JSON/0 byte/node budget, including both presentations.
194
+ return freezeJson(snapshot(completed, 'INVALID_RESULT'));
195
+ }
196
+ function validSessionRequest(value) {
197
+ const record = ordinaryRecord(value);
198
+ return (record !== undefined &&
199
+ (((exactKeys(record, ['retain']) ||
200
+ (exactKeys(record, ['retain', 'lifetime']) && record.lifetime === 'run')) &&
201
+ record.retain === true) ||
202
+ (exactKeys(record, ['restore']) && sessionReference(record.restore))));
203
+ }
204
+ function decodePresentation(text) {
205
+ try {
206
+ return decodeJson1(encoder.encode(text));
207
+ }
208
+ catch (rawError) {
209
+ const match = /^```json\r?\n([\s\S]*)\r?\n```$/.exec(text.trim());
210
+ if (match === null)
211
+ throw rawError;
212
+ return decodeJson1(encoder.encode(match[1]));
213
+ }
214
+ }
215
+ function invalidInput(message) {
216
+ throw new AgentMethodError('INVALID_INPUT', message);
217
+ }
218
+ function exhausted(message) {
219
+ throw new AgentMethodError('RESOURCE_EXHAUSTED', message);
220
+ }
package/dist/json.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ export type JsonScalar = null | boolean | number | string;
2
+ export type JsonValue = JsonScalar | readonly JsonValue[] | {
3
+ readonly [key: string]: JsonValue;
4
+ };
5
+ export type JsonObject = Readonly<Record<string, JsonValue>>;
6
+ export declare const JSON_1_LIMITS: Readonly<{
7
+ bytes: 16777216;
8
+ depth: 128;
9
+ nodes: 262144;
10
+ containerEntries: 65536;
11
+ stringBytes: 8388608;
12
+ memberNameBytes: 1024;
13
+ numberTokenBytes: 128;
14
+ }>;
15
+ export declare class Json1Error extends Error {
16
+ constructor(message: string);
17
+ }
18
+ export declare function decodeJson1(bytes: Uint8Array): JsonValue;
19
+ export declare function validateJson1(value: unknown): asserts value is JsonValue;
20
+ /** RFC 8785 canonical bytes for an already validated JSON/0 value. */
21
+ export declare function canonicalJson(value: JsonValue): Uint8Array;