@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,202 @@
1
+ {
2
+ "$schema": "https://flow.jig.md/schemas/channel-contract-0.schema.json",
3
+ "id": "https://jig.md/contracts/agent-replies",
4
+ "version": "0.1.0",
5
+ "semantics": "Essential direct replies for conversational Agent Run. accepted acknowledges a control, not completed work; result is one settled Agent answer; cancelled settles a cancelled turn without an answer; error reports a settled but invalid answer. rejected performs no requested action. All items are at most 64 KiB; lost, oversized or persistently blocked replies fail the conversation rather than truncate results. End of stream is not native cleanup: await the enclosing invocation.",
6
+ "item": {
7
+ "oneOf": [
8
+ {
9
+ "type": "object",
10
+ "properties": {
11
+ "type": {
12
+ "const": "accepted"
13
+ },
14
+ "command": {
15
+ "enum": [
16
+ "prompt",
17
+ "interrupt",
18
+ "close"
19
+ ]
20
+ },
21
+ "turn": {
22
+ "type": "integer",
23
+ "minimum": 0,
24
+ "maximum": 7
25
+ }
26
+ },
27
+ "required": [
28
+ "type",
29
+ "command",
30
+ "turn"
31
+ ],
32
+ "additionalProperties": false
33
+ },
34
+ {
35
+ "type": "object",
36
+ "properties": {
37
+ "type": {
38
+ "const": "rejected"
39
+ },
40
+ "command": {
41
+ "enum": [
42
+ "prompt",
43
+ "interrupt",
44
+ "close"
45
+ ]
46
+ },
47
+ "turn": {
48
+ "type": "integer",
49
+ "minimum": 0
50
+ },
51
+ "code": {
52
+ "enum": [
53
+ "BUSY",
54
+ "STALE_TURN",
55
+ "TURN_LIMIT",
56
+ "INVALID_INPUT",
57
+ "NOT_RUNNING"
58
+ ]
59
+ }
60
+ },
61
+ "required": [
62
+ "type",
63
+ "command",
64
+ "turn",
65
+ "code"
66
+ ],
67
+ "additionalProperties": false
68
+ },
69
+ {
70
+ "type": "object",
71
+ "properties": {
72
+ "type": {
73
+ "const": "result"
74
+ },
75
+ "turn": {
76
+ "type": "integer",
77
+ "minimum": 0,
78
+ "maximum": 7
79
+ },
80
+ "result": {
81
+ "oneOf": [
82
+ {
83
+ "type": "object",
84
+ "properties": {
85
+ "outcome": {
86
+ "const": "done"
87
+ },
88
+ "output": {
89
+ "$ref": "#/$defs/AgentOutput"
90
+ }
91
+ },
92
+ "required": [
93
+ "outcome",
94
+ "output"
95
+ ],
96
+ "additionalProperties": false
97
+ },
98
+ {
99
+ "type": "object",
100
+ "properties": {
101
+ "outcome": {
102
+ "const": "blocked"
103
+ },
104
+ "output": {
105
+ "$ref": "#/$defs/AgentOutput"
106
+ }
107
+ },
108
+ "required": [
109
+ "outcome",
110
+ "output"
111
+ ],
112
+ "additionalProperties": false
113
+ },
114
+ {
115
+ "type": "object",
116
+ "properties": {
117
+ "outcome": {
118
+ "const": "limit"
119
+ },
120
+ "output": {
121
+ "$ref": "#/$defs/AgentOutput"
122
+ }
123
+ },
124
+ "required": [
125
+ "outcome",
126
+ "output"
127
+ ],
128
+ "additionalProperties": false
129
+ }
130
+ ]
131
+ }
132
+ },
133
+ "required": [
134
+ "type",
135
+ "turn",
136
+ "result"
137
+ ],
138
+ "additionalProperties": false
139
+ },
140
+ {
141
+ "type": "object",
142
+ "properties": {
143
+ "type": {
144
+ "const": "cancelled"
145
+ },
146
+ "turn": {
147
+ "type": "integer",
148
+ "minimum": 0,
149
+ "maximum": 7
150
+ }
151
+ },
152
+ "required": [
153
+ "type",
154
+ "turn"
155
+ ],
156
+ "additionalProperties": false
157
+ },
158
+ {
159
+ "type": "object",
160
+ "properties": {
161
+ "type": {
162
+ "const": "error"
163
+ },
164
+ "turn": {
165
+ "type": "integer",
166
+ "minimum": 0,
167
+ "maximum": 7
168
+ },
169
+ "code": {
170
+ "const": "INVALID_RESULT"
171
+ },
172
+ "message": {
173
+ "type": "string"
174
+ }
175
+ },
176
+ "required": [
177
+ "type",
178
+ "turn",
179
+ "code",
180
+ "message"
181
+ ],
182
+ "additionalProperties": false
183
+ }
184
+ ]
185
+ },
186
+ "$defs": {
187
+ "AgentOutput": {
188
+ "type": "object",
189
+ "properties": {
190
+ "text": {
191
+ "type": "string",
192
+ "maxLength": 8388608
193
+ },
194
+ "structured": true
195
+ },
196
+ "required": [
197
+ "text"
198
+ ],
199
+ "additionalProperties": false
200
+ }
201
+ }
202
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://flow.jig.md/schemas/invocation-contract-0.schema.json",
3
+ "id": "https://jig.md/contracts/http-request",
4
+ "version": "0.1.0",
5
+ "input": {
6
+ "type": "object",
7
+ "properties": {
8
+ "body": {},
9
+ "response": { "const": "json" }
10
+ },
11
+ "required": [],
12
+ "additionalProperties": false
13
+ },
14
+ "result": {
15
+ "type": "object",
16
+ "properties": {
17
+ "outcome": {
18
+ "const": "done"
19
+ },
20
+ "output": {
21
+ "type": "object",
22
+ "properties": {
23
+ "status": {
24
+ "type": "integer",
25
+ "minimum": 200,
26
+ "maximum": 599
27
+ },
28
+ "body": {}
29
+ },
30
+ "required": ["status", "body"],
31
+ "additionalProperties": false
32
+ }
33
+ },
34
+ "required": ["outcome", "output"],
35
+ "additionalProperties": false
36
+ }
37
+ }
package/dist/api.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { AgentTransportResult, PreparedAgent } from './index.js';
2
+ import { type JsonObject } from './json.js';
3
+ type Api = 'chat-completions' | 'responses';
4
+ /** One finite text request. API settings choose syntax, never endpoint authority. */
5
+ export declare function prepareApiRequest(prepared: PreparedAgent, settings: unknown): {
6
+ readonly api: Api;
7
+ readonly body: JsonObject;
8
+ };
9
+ /** Interpret only complete HTTP evidence. Never echo an arbitrary provider error body. */
10
+ export declare function parseApiResult(result: unknown, api: Api): AgentTransportResult;
11
+ export {};
package/dist/api.js ADDED
@@ -0,0 +1,164 @@
1
+ import { AgentMethodError } from './errors.js';
2
+ import { canonicalJson } from './json.js';
3
+ import { projectResponseSchema } from './schema.js';
4
+ import { ordinaryRecord, snapshot } from './values.js';
5
+ /** One finite text request. API settings choose syntax, never endpoint authority. */
6
+ export function prepareApiRequest(prepared, settings) {
7
+ const value = ordinaryRecord(snapshot(settings, 'INVALID_INPUT'));
8
+ if (value === undefined ||
9
+ Object.keys(value).some((key) => !['model', 'maxCompletionTokens', 'api', 'structuredOutput'].includes(key)) ||
10
+ typeof value.model !== 'string' ||
11
+ value.model.trim().length === 0 ||
12
+ value.model.length > 256) {
13
+ throw new AgentMethodError('INVALID_INPUT', 'Configure the Agent model in Binding settings');
14
+ }
15
+ const tokens = Object.hasOwn(value, 'maxCompletionTokens') ? value.maxCompletionTokens : 4096;
16
+ if (!Number.isSafeInteger(tokens) || typeof tokens !== 'number' || tokens < 1 || tokens > 65536)
17
+ throw new AgentMethodError('INVALID_INPUT', 'maxCompletionTokens must be between 1 and 65536');
18
+ const api = Object.hasOwn(value, 'api') ? value.api : 'chat-completions';
19
+ if (api !== 'chat-completions' && api !== 'responses')
20
+ throw new AgentMethodError('INVALID_INPUT', 'api must be chat-completions or responses');
21
+ const structuredOutput = Object.hasOwn(value, 'structuredOutput')
22
+ ? value.structuredOutput
23
+ : 'prompt';
24
+ if (structuredOutput !== 'prompt' && structuredOutput !== 'json-schema')
25
+ throw new AgentMethodError('INVALID_INPUT', 'structuredOutput must be prompt or json-schema');
26
+ const format = structuredOutput === 'json-schema' && prepared.request.responseSchema !== undefined
27
+ ? {
28
+ type: 'json_schema',
29
+ name: 'flow_agent_result',
30
+ schema: projectResponseSchema(prepared.request.responseSchema),
31
+ strict: true,
32
+ }
33
+ : undefined;
34
+ const body = api === 'responses'
35
+ ? {
36
+ model: value.model,
37
+ input: prepared.request.prompt,
38
+ max_output_tokens: tokens,
39
+ stream: false,
40
+ store: false,
41
+ ...(format === undefined ? {} : { text: { format } }),
42
+ }
43
+ : {
44
+ model: value.model,
45
+ messages: [{ role: 'user', content: prepared.request.prompt }],
46
+ max_completion_tokens: tokens,
47
+ n: 1,
48
+ stream: false,
49
+ store: false,
50
+ ...(format === undefined
51
+ ? {}
52
+ : {
53
+ response_format: {
54
+ type: format.type,
55
+ json_schema: { name: format.name, schema: format.schema, strict: format.strict },
56
+ },
57
+ }),
58
+ };
59
+ if (canonicalJson(body).byteLength > 8_388_608)
60
+ throw new AgentMethodError('RESOURCE_EXHAUSTED', 'Rendered request exceeds the 8 MiB HTTP body ceiling');
61
+ return { api, body };
62
+ }
63
+ /** Interpret only complete HTTP evidence. Never echo an arbitrary provider error body. */
64
+ export function parseApiResult(result, api) {
65
+ const record = ordinaryRecord(snapshot(result, 'INVALID_RESULT'));
66
+ const http = record === undefined ? undefined : ordinaryRecord(record.output);
67
+ if (record?.outcome !== 'done' ||
68
+ http === undefined ||
69
+ typeof http.status !== 'number' ||
70
+ !Number.isInteger(http.status) ||
71
+ http.status < 200 ||
72
+ http.status > 599 ||
73
+ !Object.hasOwn(http, 'body'))
74
+ return invalid('HTTP slot returned invalid response evidence');
75
+ if (http.status !== 200)
76
+ return invalid(`Agent endpoint returned HTTP ${http.status}; no automatic retry was attempted`);
77
+ if (canonicalJson(http.body).byteLength > 12_582_912)
78
+ throw new AgentMethodError('RESOURCE_EXHAUSTED', 'Agent HTTP response exceeds 12 MiB');
79
+ const body = ordinaryRecord(http.body);
80
+ if (api === 'responses')
81
+ return responsesResult(body);
82
+ return chatResult(body);
83
+ }
84
+ function chatResult(body) {
85
+ const choices = body?.choices;
86
+ if (body?.object !== 'chat.completion' || !Array.isArray(choices) || choices.length !== 1)
87
+ return invalid('Expected one complete Chat Completions choice');
88
+ const choice = ordinaryRecord(choices[0]);
89
+ const message = ordinaryRecord(choice?.message);
90
+ if (choice?.index !== 0 ||
91
+ message?.role !== 'assistant' ||
92
+ (message.tool_calls !== undefined &&
93
+ message.tool_calls !== null &&
94
+ !(Array.isArray(message.tool_calls) && message.tool_calls.length === 0)) ||
95
+ (message.function_call !== undefined && message.function_call !== null))
96
+ return invalid('Agent returned an unsupported message or tool request');
97
+ const content = message.content;
98
+ const refusal = message.refusal;
99
+ if ((content !== null && typeof content !== 'string') ||
100
+ (refusal !== undefined && refusal !== null && typeof refusal !== 'string'))
101
+ return invalid('Agent response must contain text or an explicit refusal');
102
+ const reason = choice.finish_reason;
103
+ if (reason !== 'stop' && reason !== 'length' && reason !== 'content_filter')
104
+ return invalid('Agent response has no supported terminal stop reason');
105
+ const refused = reason === 'content_filter' || (typeof refusal === 'string' && refusal.length > 0);
106
+ if (content === null && !refused && reason !== 'length')
107
+ return invalid('Completed Agent response omitted text');
108
+ return {
109
+ outcome: 'done',
110
+ output: {
111
+ text: refused ? refusal || content || 'The Agent response was filtered.' : (content ?? ''),
112
+ stop: refused ? 'refusal' : reason === 'length' ? 'limit' : 'end-turn',
113
+ },
114
+ };
115
+ }
116
+ function responsesResult(body) {
117
+ if (body?.object !== 'response' ||
118
+ (body.status !== 'completed' && body.status !== 'incomplete') ||
119
+ (body.error !== undefined && body.error !== null) ||
120
+ !Array.isArray(body.output))
121
+ return invalid('Agent endpoint returned no completed or limited Responses result');
122
+ const reason = ordinaryRecord(body.incomplete_details)?.reason;
123
+ if (body.status === 'incomplete' && reason !== 'max_output_tokens' && reason !== 'content_filter')
124
+ return invalid('Agent response has no supported incomplete reason');
125
+ if (body.status === 'completed' && body.incomplete_details != null)
126
+ return invalid('Completed Agent response includes incomplete details');
127
+ const texts = [];
128
+ const refusals = [];
129
+ for (const raw of body.output) {
130
+ const item = ordinaryRecord(raw);
131
+ if (item?.type === 'reasoning')
132
+ continue;
133
+ if (item?.type !== 'message' ||
134
+ item.role !== 'assistant' ||
135
+ !Array.isArray(item.content) ||
136
+ (item.status !== 'completed' && item.status !== 'incomplete') ||
137
+ (body.status === 'completed' && item.status !== 'completed'))
138
+ return invalid('Agent returned an unsupported Responses output or tool request');
139
+ for (const rawPart of item.content) {
140
+ const part = ordinaryRecord(rawPart);
141
+ if (part?.type === 'output_text' && typeof part.text === 'string')
142
+ texts.push(part.text);
143
+ else if (part?.type === 'refusal' && typeof part.refusal === 'string')
144
+ refusals.push(part.refusal);
145
+ else
146
+ return invalid('Agent response must contain text or an explicit refusal');
147
+ }
148
+ }
149
+ if (body.status === 'completed' && texts.length === 0 && refusals.length === 0)
150
+ return invalid('Completed Agent response omitted text');
151
+ const refused = refusals.length > 0 || reason === 'content_filter';
152
+ return {
153
+ outcome: 'done',
154
+ output: {
155
+ text: refused
156
+ ? refusals.join('\n') || texts.join('') || 'The Agent response was filtered.'
157
+ : texts.join(''),
158
+ stop: refused ? 'refusal' : body.status === 'incomplete' ? 'limit' : 'end-turn',
159
+ },
160
+ };
161
+ }
162
+ function invalid(message) {
163
+ throw new AgentMethodError('INVALID_RESULT', message);
164
+ }
@@ -0,0 +1,68 @@
1
+ import type { ChannelSender, RunContext, RunResult } from '@jigging/flow';
2
+ import type { AgentCallInput, AgentInput } from './index.js';
3
+ export type AgentTurn = {
4
+ readonly type: 'result';
5
+ readonly turn: number;
6
+ readonly result: RunResult;
7
+ } | {
8
+ readonly type: 'cancelled';
9
+ readonly turn: number;
10
+ } | {
11
+ readonly type: 'error';
12
+ readonly turn: number;
13
+ readonly code: 'INVALID_RESULT';
14
+ readonly message: string;
15
+ };
16
+ export interface AgentConversation {
17
+ readonly initial: Promise<AgentTurn>;
18
+ prompt(input: Omit<AgentInput, 'session'>): Promise<AgentTurn>;
19
+ /** Acknowledges control, not cancellation. Await the turn for its actual outcome. */
20
+ interrupt(): Promise<'accepted' | 'not-running'>;
21
+ }
22
+ export interface ConversationOptions {
23
+ readonly operationId: string;
24
+ readonly slot: string;
25
+ readonly input: AgentCallInput;
26
+ /** Optional caller-created public update writer; observation stays application-owned. */
27
+ readonly events?: ChannelSender;
28
+ /** Synchronous filtering/presentation of public updates; no channel setup required. */
29
+ readonly onEvent?: (event: AgentUpdate) => void;
30
+ }
31
+ export type AgentUpdate = {
32
+ readonly sessionUpdate: 'agent_message_chunk';
33
+ readonly turn?: number;
34
+ readonly messageId?: string;
35
+ readonly content: {
36
+ readonly type: 'text';
37
+ readonly text: string;
38
+ };
39
+ } | {
40
+ readonly sessionUpdate: 'plan';
41
+ readonly turn?: number;
42
+ readonly entries: readonly {
43
+ readonly content: string;
44
+ readonly priority: 'high' | 'medium' | 'low';
45
+ readonly status: 'pending' | 'in_progress' | 'completed';
46
+ }[];
47
+ };
48
+ export type AgentObservation = {
49
+ readonly status: 'complete';
50
+ } | {
51
+ readonly status: 'incomplete';
52
+ readonly errors: readonly unknown[];
53
+ };
54
+ export interface ConversationResult<T> {
55
+ readonly value: T;
56
+ readonly turns: readonly AgentTurn[];
57
+ readonly settlement: RunResult;
58
+ /** Present only with onEvent. Observation never stands in for execution settlement. */
59
+ readonly observation?: AgentObservation;
60
+ }
61
+ /** Retains received answers and every primary/cleanup failure; never manufactures success. */
62
+ export declare class AgentConversationError extends AggregateError {
63
+ readonly turns: readonly AgentTurn[];
64
+ readonly settlement?: RunResult | undefined;
65
+ constructor(errors: readonly unknown[], turns: readonly AgentTurn[], settlement?: RunResult | undefined);
66
+ }
67
+ /** Ordinary Agent Run caller logic: no ACP implementation, host imports, or provider access. */
68
+ export declare function withAgentConversation<T>(run: RunContext, options: ConversationOptions, use: (conversation: AgentConversation) => Promise<T>): Promise<ConversationResult<T>>;