mycomesh-consumer 0.1.52 → 0.2.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/README.md +27 -135
- package/bin/mycomesh-consumer.mjs +5 -8
- package/networks/mycomesh-v11-sepolia.json +27 -0
- package/package.json +8 -21
- package/src/chain.mjs +52 -0
- package/src/cli.mjs +131 -643
- package/src/consumer.mjs +144 -473
- package/src/disputes.mjs +54 -0
- package/src/eip712.mjs +151 -0
- package/src/protocol.mjs +90 -0
- package/src/secure-envelope.mjs +324 -0
- package/src/sse.mjs +101 -0
- package/bin/mycomesh.mjs +0 -10
- package/networks/v10-controlled-test.json +0 -119
- package/networks/v10-dynamic-20260926.ca.crt +0 -25
- package/networks/v10-dynamic-20260926.json +0 -154
- package/src/consumer-discovery.mjs +0 -355
- package/src/consumer-history.mjs +0 -287
- package/src/consumer-request-journal.mjs +0 -189
- package/src/consumer-reserved.mjs +0 -112
- package/src/consumer-runtime.mjs +0 -4176
- package/src/consumer-sessions.mjs +0 -205
- package/src/release.mjs +0 -12
- /package/networks/{v10-controlled-test.ca.crt → mycomesh-testnet-ca.crt} +0 -0
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
// Routing-only hints: never forward these as Gateway stateful-history headers.
|
|
4
|
-
export const CONSUMER_SESSION_HEADERS = Object.freeze([
|
|
5
|
-
"x-mycomesh-session-id",
|
|
6
|
-
"x-session-id",
|
|
7
|
-
"session_id",
|
|
8
|
-
]);
|
|
9
|
-
|
|
10
|
-
const TOOL_CALL_TYPES = new Set([
|
|
11
|
-
"tool_call", "function_call", "local_shell_call", "tool_search_call",
|
|
12
|
-
"custom_tool_call", "mcp_tool_call",
|
|
13
|
-
]);
|
|
14
|
-
const TOOL_OUTPUT_TYPES = new Set([
|
|
15
|
-
"function_call_output", "tool_search_output", "custom_tool_call_output",
|
|
16
|
-
"mcp_tool_call_output",
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
|
-
export class ConsumerSessionError extends Error {
|
|
20
|
-
constructor(message, statusCode = 400) {
|
|
21
|
-
super(message);
|
|
22
|
-
this.name = "ConsumerSessionError";
|
|
23
|
-
this.statusCode = statusCode;
|
|
24
|
-
this.status = statusCode;
|
|
25
|
-
this.code = statusCode === 409 ? "session_continuation_unavailable" : "invalid_session";
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function object(value) {
|
|
30
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function validIdentifier(value, limit) {
|
|
34
|
-
return typeof value === "string" && value.length > 0 && value.length <= limit
|
|
35
|
-
&& /^[\x21-\x7e]+$/.test(value) && !value.includes(",");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function sessionIdentifier(value) {
|
|
39
|
-
if (!validIdentifier(value, 128)) {
|
|
40
|
-
throw new ConsumerSessionError("Session id must be 1–128 printable ASCII characters without whitespace or commas");
|
|
41
|
-
}
|
|
42
|
-
return value;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function headerSignals(headers) {
|
|
46
|
-
const values = [];
|
|
47
|
-
const entries = typeof headers?.entries === "function"
|
|
48
|
-
? headers.entries() : Object.entries(headers || {});
|
|
49
|
-
for (const [name, value] of entries) {
|
|
50
|
-
if (!CONSUMER_SESSION_HEADERS.includes(name.toLowerCase())) continue;
|
|
51
|
-
if (value === undefined) continue;
|
|
52
|
-
if (Array.isArray(value)) {
|
|
53
|
-
if (value.length !== 1) throw new ConsumerSessionError("Duplicate session headers are not allowed");
|
|
54
|
-
values.push(sessionIdentifier(value[0]));
|
|
55
|
-
} else {
|
|
56
|
-
values.push(sessionIdentifier(value));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return values;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function toolReferences(body) {
|
|
63
|
-
const context = new Set();
|
|
64
|
-
const outputs = [];
|
|
65
|
-
for (const item of Array.isArray(body.input) ? body.input : []) {
|
|
66
|
-
if (!object(item)) continue;
|
|
67
|
-
if (TOOL_CALL_TYPES.has(item.type) && validIdentifier(item.call_id, 256)) context.add(item.call_id);
|
|
68
|
-
if (TOOL_OUTPUT_TYPES.has(item.type)) outputs.push(item.call_id);
|
|
69
|
-
}
|
|
70
|
-
for (const message of Array.isArray(body.messages) ? body.messages : []) {
|
|
71
|
-
if (!object(message)) continue;
|
|
72
|
-
if (message.role === "assistant" && Array.isArray(message.tool_calls)) {
|
|
73
|
-
for (const call of message.tool_calls) {
|
|
74
|
-
if (object(call) && validIdentifier(call.id, 256)) context.add(call.id);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (message.role === "tool") outputs.push(message.tool_call_id);
|
|
78
|
-
}
|
|
79
|
-
return { context, outputs };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* One instance belongs to one authenticated Consumer routing namespace.
|
|
84
|
-
* No prompt, cache key, wallet, or payment request id is treated as a session.
|
|
85
|
-
* Continuation mappings are process-local, bounded, and expire after inactivity.
|
|
86
|
-
*/
|
|
87
|
-
export class ConsumerSessions {
|
|
88
|
-
constructor({ ttlMs = 30 * 60_000, maxEntries = 10_000, now = Date.now, generateId = randomUUID } = {}) {
|
|
89
|
-
if (!Number.isSafeInteger(ttlMs) || ttlMs <= 0 || !Number.isSafeInteger(maxEntries) || maxEntries <= 0) {
|
|
90
|
-
throw new TypeError("Session TTL and capacity must be positive safe integers");
|
|
91
|
-
}
|
|
92
|
-
if (typeof now !== "function" || typeof generateId !== "function") {
|
|
93
|
-
throw new TypeError("Session clock and id generator must be functions");
|
|
94
|
-
}
|
|
95
|
-
this.ttlMs = ttlMs;
|
|
96
|
-
this.maxEntries = maxEntries;
|
|
97
|
-
this.now = now;
|
|
98
|
-
this.generateId = generateId;
|
|
99
|
-
this.entries = new Map();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
get size() {
|
|
103
|
-
this.prune(this.now());
|
|
104
|
-
return this.entries.size;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
prune(now) {
|
|
108
|
-
for (const [key, entry] of this.entries) {
|
|
109
|
-
if (entry.expiresAt <= now) this.entries.delete(key);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
resolve(body, headers = {}) {
|
|
114
|
-
if (!object(body)) throw new ConsumerSessionError("Request body must be a JSON object");
|
|
115
|
-
if (body.metadata != null && !object(body.metadata)) {
|
|
116
|
-
throw new ConsumerSessionError("Request metadata must be a JSON object");
|
|
117
|
-
}
|
|
118
|
-
const signals = headerSignals(headers);
|
|
119
|
-
if (Object.hasOwn(body.metadata || {}, "mycomesh_session_id")) {
|
|
120
|
-
signals.push(sessionIdentifier(body.metadata.mycomesh_session_id));
|
|
121
|
-
}
|
|
122
|
-
if (body.conversation != null) {
|
|
123
|
-
const value = object(body.conversation) ? body.conversation.id : body.conversation;
|
|
124
|
-
signals.push(sessionIdentifier(value));
|
|
125
|
-
}
|
|
126
|
-
if (new Set(signals).size > 1) throw new ConsumerSessionError("Explicit session identifiers disagree");
|
|
127
|
-
|
|
128
|
-
const now = this.now();
|
|
129
|
-
this.prune(now);
|
|
130
|
-
const continuationSessions = new Set();
|
|
131
|
-
const matchedKeys = new Set();
|
|
132
|
-
const resolveReference = (kind, value, selfContained = false) => {
|
|
133
|
-
if (!validIdentifier(value, 256)) {
|
|
134
|
-
throw new ConsumerSessionError("Continuation reference must be a nonempty ASCII identifier", 409);
|
|
135
|
-
}
|
|
136
|
-
const key = `${kind}:${value}`;
|
|
137
|
-
const entry = this.entries.get(key);
|
|
138
|
-
// Full-history SDK/Codex requests may include old call+output pairs and
|
|
139
|
-
// are already supported statelessly by the Provider backend. An explicit
|
|
140
|
-
// new session may fork that history without inheriting its old route.
|
|
141
|
-
if (selfContained && signals.length) return;
|
|
142
|
-
if (!entry && selfContained) return;
|
|
143
|
-
if (!entry || !entry.sessionId) {
|
|
144
|
-
throw new ConsumerSessionError("Continuation is unknown, expired, or ambiguous; its original route is unavailable", 409);
|
|
145
|
-
}
|
|
146
|
-
continuationSessions.add(entry.sessionId);
|
|
147
|
-
matchedKeys.add(key);
|
|
148
|
-
};
|
|
149
|
-
if (body.previous_response_id != null) resolveReference("response", body.previous_response_id);
|
|
150
|
-
const { context, outputs } = toolReferences(body);
|
|
151
|
-
for (const callId of outputs) resolveReference("call", callId, context.has(callId));
|
|
152
|
-
if (continuationSessions.size > 1) {
|
|
153
|
-
throw new ConsumerSessionError("Continuation references belong to different sessions", 409);
|
|
154
|
-
}
|
|
155
|
-
const previousSession = continuationSessions.values().next().value;
|
|
156
|
-
if (previousSession && signals.length && previousSession !== signals[0]) {
|
|
157
|
-
throw new ConsumerSessionError("Explicit session does not match the continuation's original session", 409);
|
|
158
|
-
}
|
|
159
|
-
const id = signals[0] || previousSession || sessionIdentifier(this.generateId());
|
|
160
|
-
for (const key of matchedKeys) {
|
|
161
|
-
const entry = this.entries.get(key);
|
|
162
|
-
this.entries.delete(key);
|
|
163
|
-
this.entries.set(key, { ...entry, expiresAt: now + this.ttlMs });
|
|
164
|
-
}
|
|
165
|
-
return {
|
|
166
|
-
id,
|
|
167
|
-
body: { ...body, metadata: { ...body.metadata, mycomesh_session_id: id } },
|
|
168
|
-
explicit: signals.length > 0,
|
|
169
|
-
requiresOriginalRoute: matchedKeys.size > 0,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Accept either a raw OpenAI response or relayInference's {status, payload}.
|
|
174
|
-
// Call only after the actual request succeeded on the selected route.
|
|
175
|
-
remember(result, sessionId) {
|
|
176
|
-
sessionIdentifier(sessionId);
|
|
177
|
-
if (!object(result) || (typeof result.status === "number" && (result.status < 200 || result.status >= 300))) return 0;
|
|
178
|
-
const payload = object(result.payload) ? result.payload : result;
|
|
179
|
-
if (payload.error) return 0;
|
|
180
|
-
const now = this.now();
|
|
181
|
-
this.prune(now);
|
|
182
|
-
let remembered = 0;
|
|
183
|
-
const save = (kind, value) => {
|
|
184
|
-
if (!validIdentifier(value, 256)) return;
|
|
185
|
-
const key = `${kind}:${value}`;
|
|
186
|
-
const previous = this.entries.get(key);
|
|
187
|
-
// A reused upstream id must not silently transfer a continuation route.
|
|
188
|
-
const resolvedSession = previous && previous.sessionId !== sessionId ? null : sessionId;
|
|
189
|
-
this.entries.delete(key);
|
|
190
|
-
this.entries.set(key, { sessionId: resolvedSession, expiresAt: now + this.ttlMs });
|
|
191
|
-
while (this.entries.size > this.maxEntries) this.entries.delete(this.entries.keys().next().value);
|
|
192
|
-
remembered += 1;
|
|
193
|
-
};
|
|
194
|
-
save("response", payload.id);
|
|
195
|
-
for (const item of Array.isArray(payload.output) ? payload.output : []) {
|
|
196
|
-
if (object(item) && TOOL_CALL_TYPES.has(item.type)) save("call", item.call_id);
|
|
197
|
-
}
|
|
198
|
-
for (const choice of Array.isArray(payload.choices) ? payload.choices : []) {
|
|
199
|
-
for (const call of Array.isArray(choice?.message?.tool_calls) ? choice.message.tool_calls : []) {
|
|
200
|
-
if (object(call)) save("call", call.id);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return remembered;
|
|
204
|
-
}
|
|
205
|
-
}
|
package/src/release.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Package versions are kept in one runtime module so every executable reports
|
|
2
|
-
// the same version as the package metadata that ships it.
|
|
3
|
-
export const CONSUMER_RELEASE_VERSION = "0.1.52";
|
|
4
|
-
export const PROVIDER_RELEASE_VERSION = "0.1.38";
|
|
5
|
-
|
|
6
|
-
// Provider release pins are intentionally unbound in the source tree. The
|
|
7
|
-
// release candidate builder injects the commit and immutable OCI digest into a
|
|
8
|
-
// temporary Provider package after the image has been published. Keeping these
|
|
9
|
-
// values null prevents a source checkout from pretending that a not-yet-built
|
|
10
|
-
// image belongs to the current source.
|
|
11
|
-
export const PROVIDER_RELEASE_SOURCE_COMMIT = null;
|
|
12
|
-
export const PROVIDER_RELEASE_IMAGE = null;
|
|
File without changes
|