@nshipster/sosumi 1.0.8 → 1.1.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 +48 -0
- package/package.json +1 -1
- package/public/SKILL.md +1 -0
- package/public/index.html +52 -5
- package/public/llms.txt +17 -2
- package/public/page.css +104 -0
- package/public/privacy.html +111 -0
- package/public/robots.txt +2 -0
- package/public/sitemap.xml +9 -0
- package/public/support.html +65 -0
- package/public/terms.html +73 -0
- package/src/index.ts +205 -10
- package/src/lib/a2a.ts +676 -5
- package/src/lib/ard.ts +77 -0
- package/src/lib/auth.ts +105 -0
- package/src/lib/did.ts +40 -0
- package/src/lib/hig/render.ts +1 -1
- package/src/lib/hig/types.ts +1 -1
- package/src/lib/identity.ts +4 -0
- package/src/lib/mcp.ts +13 -2
- package/src/lib/reference/render.ts +47 -2
- package/src/lib/types.ts +11 -0
- package/src/lib/webbotauth.ts +19 -120
- package/wrangler.jsonc +14 -2
package/src/index.ts
CHANGED
|
@@ -5,7 +5,24 @@ import { cache } from "hono/cache"
|
|
|
5
5
|
import { cors } from "hono/cors"
|
|
6
6
|
import { HTTPException } from "hono/http-exception"
|
|
7
7
|
import { trimTrailingSlash } from "hono/trailing-slash"
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
A2A_MEDIA_TYPE,
|
|
10
|
+
A2A_MESSAGE_PATH,
|
|
11
|
+
A2AError,
|
|
12
|
+
buildAgentCard,
|
|
13
|
+
capabilityError,
|
|
14
|
+
createA2AErrorResponse,
|
|
15
|
+
handleA2AMessage,
|
|
16
|
+
parseA2AListTasksQuery,
|
|
17
|
+
pushNotificationsNotSupported,
|
|
18
|
+
readA2ACapabilityText,
|
|
19
|
+
readA2AJsonBody,
|
|
20
|
+
taskNotFound,
|
|
21
|
+
unsupportedOperation,
|
|
22
|
+
validateA2ARequestHeaders,
|
|
23
|
+
} from "./lib/a2a"
|
|
24
|
+
import { AI_CATALOG_MEDIA_TYPE, buildAiCatalog } from "./lib/ard"
|
|
25
|
+
import { buildDidDocument, DID_DOCUMENT_MEDIA_TYPE } from "./lib/did"
|
|
9
26
|
import {
|
|
10
27
|
decodeExternalTargetPath,
|
|
11
28
|
ExternalAccessError,
|
|
@@ -47,10 +64,47 @@ interface Env {
|
|
|
47
64
|
EXTERNAL_DOC_HOST_BLOCKLIST?: string
|
|
48
65
|
WEB_BOT_AUTH_KEY?: string
|
|
49
66
|
SIGNATURE_AGENT?: string
|
|
67
|
+
MCP_REGISTRY_PUBLIC_KEY?: string
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
const app = new Hono<{ Bindings: Env }>()
|
|
53
71
|
|
|
72
|
+
function requestedA2AVersion(request: Request): string | null {
|
|
73
|
+
return request.headers.get("A2A-Version") ?? new URL(request.url).searchParams.get("A2A-Version")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function fixedA2AError(request: Request, error: A2AError): Response {
|
|
77
|
+
try {
|
|
78
|
+
validateA2ARequestHeaders(
|
|
79
|
+
request.method === "POST" ? request.headers.get("Content-Type") : null,
|
|
80
|
+
requestedA2AVersion(request),
|
|
81
|
+
)
|
|
82
|
+
return createA2AErrorResponse(error)
|
|
83
|
+
} catch (headerError) {
|
|
84
|
+
return createA2AErrorResponse(headerError)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function taskIdFromPath(path: string): string {
|
|
89
|
+
const taskPath = path.slice("/tasks/".length)
|
|
90
|
+
const encodedTaskId = taskPath.split(/[:/]/, 1)[0] ?? ""
|
|
91
|
+
try {
|
|
92
|
+
return decodeURIComponent(encodedTaskId) || "unknown"
|
|
93
|
+
} catch {
|
|
94
|
+
return encodedTaskId || "unknown"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isMarkdownContentRoute(path: string): boolean {
|
|
99
|
+
return (
|
|
100
|
+
path.startsWith("/documentation/") ||
|
|
101
|
+
path.startsWith("/external/") ||
|
|
102
|
+
path === "/design/human-interface-guidelines" ||
|
|
103
|
+
path.startsWith("/design/human-interface-guidelines/") ||
|
|
104
|
+
path.startsWith("/videos/play/")
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
54
108
|
app.use("*", async (c, next) => {
|
|
55
109
|
// Prime Web Bot Auth signing before any route or outbound fetch runs.
|
|
56
110
|
configureWebBotAuth(c.env)
|
|
@@ -63,6 +117,7 @@ app.use("*", async (c, next) => {
|
|
|
63
117
|
c.header("X-XSS-Protection", "1; mode=block")
|
|
64
118
|
c.header("Referrer-Policy", "strict-origin-when-cross-origin")
|
|
65
119
|
c.header("Permissions-Policy", "camera=(), microphone=(), geolocation=()")
|
|
120
|
+
c.header("Content-Usage", "train-ai=n, search=y")
|
|
66
121
|
|
|
67
122
|
// Performance headers
|
|
68
123
|
c.header("Vary", "Accept")
|
|
@@ -72,18 +127,30 @@ app.use("*", async (c, next) => {
|
|
|
72
127
|
c.header("Cache-Control", "no-store")
|
|
73
128
|
}
|
|
74
129
|
|
|
130
|
+
const links = ['</llms.txt>; rel="describedby"']
|
|
131
|
+
|
|
75
132
|
if (c.req.path === "/") {
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
].join(", "),
|
|
133
|
+
links.unshift(
|
|
134
|
+
'</.well-known/api-catalog>; rel="api-catalog"',
|
|
135
|
+
`</.well-known/ard.json>; rel="ard"; type="${AI_CATALOG_MEDIA_TYPE}"`,
|
|
136
|
+
`</.well-known/ai-catalog.json>; rel="ai-catalog"; type="${AI_CATALOG_MEDIA_TYPE}"`,
|
|
137
|
+
'</.well-known/agent-card.json>; rel="service-desc"; type="application/json"',
|
|
138
|
+
'</.well-known/mcp/server-card.json>; rel="service-desc"; type="application/json"',
|
|
139
|
+
'</SKILL.md>; rel="service-doc"',
|
|
140
|
+
'</llms.txt>; rel="alternate"; type="text/markdown"',
|
|
85
141
|
)
|
|
86
142
|
}
|
|
143
|
+
|
|
144
|
+
if (isMarkdownContentRoute(c.req.path)) {
|
|
145
|
+
links.push(`<${c.req.path}>; rel="alternate"; type="text/markdown"`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const existingLink = c.res.headers.get("Link")
|
|
149
|
+
if (existingLink) {
|
|
150
|
+
links.unshift(existingLink)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
c.header("Link", links.join(", "))
|
|
87
154
|
})
|
|
88
155
|
|
|
89
156
|
app.use("*", cors())
|
|
@@ -188,6 +255,67 @@ const discoveryHeaders = {
|
|
|
188
255
|
"Cache-Control": "public, max-age=300, s-maxage=600",
|
|
189
256
|
} as const
|
|
190
257
|
|
|
258
|
+
const SECURITY_TXT_EXPIRY_MS = 364 * 24 * 60 * 60 * 1000 // 364 days
|
|
259
|
+
|
|
260
|
+
app.get("/.well-known/security.txt", (c) => {
|
|
261
|
+
const expires = new Date(Date.now() + SECURITY_TXT_EXPIRY_MS).toISOString()
|
|
262
|
+
const origin = new URL(c.req.url).origin
|
|
263
|
+
|
|
264
|
+
return c.text(
|
|
265
|
+
[
|
|
266
|
+
"Contact: mailto:info@sosumi.ai",
|
|
267
|
+
`Expires: ${expires}`,
|
|
268
|
+
`Canonical: ${origin}/.well-known/security.txt`,
|
|
269
|
+
"Preferred-Languages: en",
|
|
270
|
+
"",
|
|
271
|
+
].join("\n"),
|
|
272
|
+
200,
|
|
273
|
+
{
|
|
274
|
+
...discoveryHeaders,
|
|
275
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
// Proves control of the sosumi.ai namespace to the MCP Registry
|
|
281
|
+
// (`mcp-publisher login http`). The registry doesn't follow redirects.
|
|
282
|
+
app.get("/.well-known/mcp-registry-auth", (c) => {
|
|
283
|
+
const publicKey = c.env.MCP_REGISTRY_PUBLIC_KEY?.trim()
|
|
284
|
+
const headers = {
|
|
285
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
286
|
+
"Cache-Control": "no-store",
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!publicKey) {
|
|
290
|
+
return c.text("Not Found", 404, headers)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return c.text(`v=MCPv1; k=ed25519; p=${publicKey}`, 200, headers)
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
app.on("GET", ["/.well-known/ard.json", "/.well-known/ai-catalog.json"], (c) => {
|
|
297
|
+
const origin = new URL(c.req.url).origin
|
|
298
|
+
|
|
299
|
+
return c.json(buildAiCatalog(origin), 200, {
|
|
300
|
+
...discoveryHeaders,
|
|
301
|
+
"Content-Type": `${AI_CATALOG_MEDIA_TYPE}; charset=utf-8`,
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
app.get("/.well-known/did.json", async (c) => {
|
|
306
|
+
const document = await buildDidDocument()
|
|
307
|
+
if (!document) {
|
|
308
|
+
return c.json({ error: "DID signing key is unavailable." }, 404, {
|
|
309
|
+
"Cache-Control": "no-store",
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return c.json(document, 200, {
|
|
314
|
+
...discoveryHeaders,
|
|
315
|
+
"Content-Type": `${DID_DOCUMENT_MEDIA_TYPE}; charset=utf-8`,
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
191
319
|
app.get("/.well-known/api-catalog", (c) => {
|
|
192
320
|
const origin = new URL(c.req.url).origin
|
|
193
321
|
|
|
@@ -250,6 +378,73 @@ app.get("/.well-known/agent-card.json", (c) => {
|
|
|
250
378
|
})
|
|
251
379
|
})
|
|
252
380
|
|
|
381
|
+
app.post(A2A_MESSAGE_PATH, async (c) => {
|
|
382
|
+
try {
|
|
383
|
+
validateA2ARequestHeaders(c.req.header("Content-Type") ?? null, requestedA2AVersion(c.req.raw))
|
|
384
|
+
|
|
385
|
+
const input = await readA2AJsonBody(c.req.raw)
|
|
386
|
+
|
|
387
|
+
const response = await handleA2AMessage(input, async (endpoint, outputMode) => {
|
|
388
|
+
const capabilityResponse = await app.request(
|
|
389
|
+
new URL(endpoint, c.req.url).toString(),
|
|
390
|
+
{ headers: { Accept: outputMode } },
|
|
391
|
+
c.env,
|
|
392
|
+
)
|
|
393
|
+
const text = await readA2ACapabilityText(capabilityResponse)
|
|
394
|
+
if (!capabilityResponse.ok) {
|
|
395
|
+
throw capabilityError(
|
|
396
|
+
capabilityResponse.status,
|
|
397
|
+
text || `Sosumi capability request failed with status ${capabilityResponse.status}.`,
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
return text
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
return c.json(response, 200, {
|
|
404
|
+
"Content-Type": A2A_MEDIA_TYPE,
|
|
405
|
+
"Cache-Control": "no-store",
|
|
406
|
+
})
|
|
407
|
+
} catch (error) {
|
|
408
|
+
if (!(error instanceof A2AError)) {
|
|
409
|
+
console.error("A2A message execution failed", error)
|
|
410
|
+
}
|
|
411
|
+
return createA2AErrorResponse(error)
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
app.post("/message:stream", (c) =>
|
|
416
|
+
fixedA2AError(c.req.raw, unsupportedOperation("Streaming messages")),
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
app.get("/tasks", (c) => {
|
|
420
|
+
try {
|
|
421
|
+
validateA2ARequestHeaders(null, requestedA2AVersion(c.req.raw))
|
|
422
|
+
const pageSize = parseA2AListTasksQuery(new URL(c.req.url).searchParams)
|
|
423
|
+
return c.json({ tasks: [], nextPageToken: "", pageSize, totalSize: 0 }, 200, {
|
|
424
|
+
"Content-Type": A2A_MEDIA_TYPE,
|
|
425
|
+
"Cache-Control": "no-store",
|
|
426
|
+
})
|
|
427
|
+
} catch (error) {
|
|
428
|
+
return createA2AErrorResponse(error)
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
app.all("/tasks", (c) => fixedA2AError(c.req.raw, unsupportedOperation("This task operation")))
|
|
433
|
+
|
|
434
|
+
app.all("/tasks/*", (c) => {
|
|
435
|
+
if (c.req.path.includes("/pushNotificationConfigs")) {
|
|
436
|
+
return fixedA2AError(c.req.raw, pushNotificationsNotSupported())
|
|
437
|
+
}
|
|
438
|
+
if (c.req.path.endsWith(":subscribe")) {
|
|
439
|
+
return fixedA2AError(c.req.raw, unsupportedOperation("Task subscriptions"))
|
|
440
|
+
}
|
|
441
|
+
return fixedA2AError(c.req.raw, taskNotFound(taskIdFromPath(c.req.path)))
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
app.get("/extendedAgentCard", (c) =>
|
|
445
|
+
fixedA2AError(c.req.raw, unsupportedOperation("Extended Agent Cards")),
|
|
446
|
+
)
|
|
447
|
+
|
|
253
448
|
app.get("/webmcp/manifest.json", (c) =>
|
|
254
449
|
c.json(buildWebMcpManifest(), 200, {
|
|
255
450
|
...discoveryHeaders,
|