deepspace 0.10.1 → 0.10.2
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/CHANGELOG.md +8 -0
- package/dist/chunk-T7V4GUIN.js +1638 -0
- package/dist/chunk-T7V4GUIN.js.map +1 -0
- package/dist/cli.js +731 -324
- package/dist/cli.js.map +1 -1
- package/dist/documentation-client-core.d.ts +18 -0
- package/dist/documentation-client-core.js +395 -0
- package/dist/documentation-client-core.js.map +1 -0
- package/dist/documentation-default-renderer.cjs +124 -0
- package/dist/documentation-react.d.ts +2 -0
- package/dist/documentation-react.js +35 -0
- package/dist/documentation-react.js.map +1 -0
- package/dist/documentation-runtime.js +71 -0
- package/dist/documentation-server-core.d.ts +11 -0
- package/dist/documentation-server-core.js +48 -0
- package/dist/documentation-server-core.js.map +1 -0
- package/dist/documentation.d.ts +366 -0
- package/dist/documentation.js +3953 -0
- package/dist/documentation.js.map +1 -0
- package/dist/feature-installer.js +145 -0
- package/dist/feature-installer.js.map +1 -1
- package/dist/index.d.ts +177 -1
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -1
- package/dist/public-BGRX_Cjd.d.ts +234 -0
- package/dist/schema.d.ts +186 -0
- package/dist/schema.js +610 -0
- package/dist/schema.js.map +1 -0
- package/dist/server.d.ts +83 -4
- package/dist/server.js +296 -1
- package/dist/server.js.map +1 -1
- package/dist/testing.d.ts +5 -8
- package/dist/testing.js +48 -14
- package/dist/testing.js.map +1 -1
- package/dist/worker.d.ts +249 -5
- package/dist/worker.js +1438 -19
- package/dist/worker.js.map +1 -1
- package/features/admin-page/src/admin-schema.ts +1 -1
- package/features/ai-chat/feature.json +2 -2
- package/features/ai-chat/src/ChatPanel.tsx +6 -11
- package/features/ai-chat/src/ai-chat-schema.ts +1 -2
- package/features/canvas/src/canvas-schema.ts +1 -1
- package/features/documentation/content/index.mdx +14 -0
- package/features/documentation/content/quickstart.mdx +12 -0
- package/features/documentation/documentation.json +18 -0
- package/features/documentation/feature.json +68 -0
- package/features/{docs → documents}/feature.json +27 -27
- package/features/{docs/src/DocsEditorPage.tsx → documents/src/DocumentsEditorPage.tsx} +74 -74
- package/features/{docs/src/DocsListPage.tsx → documents/src/DocumentsListPage.tsx} +102 -102
- package/features/{docs/src/DocsOutlinePanel.tsx → documents/src/DocumentsOutlinePanel.tsx} +9 -9
- package/features/{docs/src/DocsPresence.tsx → documents/src/DocumentsPresence.tsx} +18 -18
- package/features/{docs → documents}/src/InviteDialog.tsx +21 -21
- package/features/{docs → documents}/src/LibrarySidebar.tsx +25 -25
- package/features/documents/src/__tests__/documents-presence-access.test.ts +71 -0
- package/features/{docs/src/docs-library-types.ts → documents/src/documents-library-types.ts} +3 -3
- package/features/{docs/src/docs-schema.ts → documents/src/documents-schema.ts} +6 -6
- package/features/{docs/src/docs-ui.css → documents/src/documents-ui.css} +89 -89
- package/features/{docs → documents}/src/editor/DocEditorSurface.tsx +11 -11
- package/features/{docs/src/editor/DocsTiptapToolbar.tsx → documents/src/editor/DocumentsTiptapToolbar.tsx} +21 -21
- package/features/{docs → documents}/src/editor/useDocEditor.ts +4 -4
- package/features/{docs/src/use-docs-presence-access.ts → documents/src/use-documents-presence-access.ts} +51 -51
- package/features/file-attachments/src/attachments-schema.ts +1 -1
- package/features/items/src/items-schema.ts +1 -1
- package/features/leaderboard/src/leaderboard-schema.ts +1 -1
- package/features/messaging/src/ChatMultiPage.tsx +1 -1
- package/features/messaging/src/ChatPage.tsx +1 -1
- package/features/messaging/src/messaging-schema.ts +2 -2
- package/features/tasks/src/tasks-schema.ts +1 -1
- package/features/testing/src/test-schema.ts +2 -2
- package/package.json +26 -1
- package/features/docs/src/__tests__/docs-presence-access.test.ts +0 -71
package/dist/worker.js
CHANGED
|
@@ -2174,11 +2174,11 @@ async function handleToolsRequest(ctx, request, path) {
|
|
|
2174
2174
|
}
|
|
2175
2175
|
if (path.startsWith("tools/describe/")) {
|
|
2176
2176
|
const toolName = path.replace("tools/describe/", "");
|
|
2177
|
-
const
|
|
2178
|
-
if (!
|
|
2177
|
+
const tool3 = BUILT_IN_TOOLS.find((t) => t.name === toolName);
|
|
2178
|
+
if (!tool3) {
|
|
2179
2179
|
return Response.json({ error: `Tool not found: ${toolName}` }, { status: 404, headers: CORS_HEADERS });
|
|
2180
2180
|
}
|
|
2181
|
-
return Response.json({ tool:
|
|
2181
|
+
return Response.json({ tool: tool3 }, { headers: CORS_HEADERS });
|
|
2182
2182
|
}
|
|
2183
2183
|
if (path.startsWith("tools/execute") && request.method === "POST") {
|
|
2184
2184
|
return handleToolExecute(ctx, request);
|
|
@@ -2195,8 +2195,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
2195
2195
|
{ status: 400, headers: CORS_HEADERS }
|
|
2196
2196
|
);
|
|
2197
2197
|
}
|
|
2198
|
-
const { tool:
|
|
2199
|
-
if (!
|
|
2198
|
+
const { tool: tool3, params = {} } = body;
|
|
2199
|
+
if (!tool3) {
|
|
2200
2200
|
return Response.json(
|
|
2201
2201
|
{ success: false, error: 'Missing "tool" in request body' },
|
|
2202
2202
|
{ status: 400, headers: CORS_HEADERS }
|
|
@@ -2213,8 +2213,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
2213
2213
|
userRole = user.role;
|
|
2214
2214
|
}
|
|
2215
2215
|
}
|
|
2216
|
-
if (
|
|
2217
|
-
const result2 = executeYjsTool(ctx,
|
|
2216
|
+
if (tool3.startsWith("yjs.")) {
|
|
2217
|
+
const result2 = executeYjsTool(ctx, tool3, params, userId, userRole);
|
|
2218
2218
|
const status2 = result2.success ? 200 : result2.error?.includes("not found") ? 404 : 400;
|
|
2219
2219
|
return Response.json(result2, { status: status2, headers: CORS_HEADERS });
|
|
2220
2220
|
}
|
|
@@ -2225,7 +2225,7 @@ async function handleToolExecute(ctx, request) {
|
|
|
2225
2225
|
getPermissionContext: ctx.getPermissionContext,
|
|
2226
2226
|
send: ctx.send
|
|
2227
2227
|
};
|
|
2228
|
-
const result = await executeTool(recordCtx,
|
|
2228
|
+
const result = await executeTool(recordCtx, tool3, params, userId, userRole, isAppAction);
|
|
2229
2229
|
const status = result.success ? 200 : result.error?.includes("not found") ? 404 : 400;
|
|
2230
2230
|
return Response.json(result, { status, headers: CORS_HEADERS });
|
|
2231
2231
|
}
|
|
@@ -5105,7 +5105,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
|
|
|
5105
5105
|
"queue",
|
|
5106
5106
|
"browser_rendering",
|
|
5107
5107
|
"analytics_engine",
|
|
5108
|
-
"hyperdrive"
|
|
5108
|
+
"hyperdrive",
|
|
5109
|
+
"ratelimit"
|
|
5109
5110
|
]);
|
|
5110
5111
|
var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
5111
5112
|
"ASSETS",
|
|
@@ -5113,6 +5114,7 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
|
|
|
5113
5114
|
"API_WORKER",
|
|
5114
5115
|
"DEEPSPACE_APP_ID",
|
|
5115
5116
|
"DEEPSPACE_RESOURCE_ID",
|
|
5117
|
+
"DEEPSPACE_RELEASE_OPERATION_ID",
|
|
5116
5118
|
"APP_NAME",
|
|
5117
5119
|
"OWNER_USER_ID",
|
|
5118
5120
|
"AUTH_JWT_PUBLIC_KEY",
|
|
@@ -5203,6 +5205,17 @@ function requiredFieldError(b) {
|
|
|
5203
5205
|
return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
|
|
5204
5206
|
}
|
|
5205
5207
|
return null;
|
|
5208
|
+
case "ratelimit":
|
|
5209
|
+
if (!/^\d+$/u.test(b.namespace_id) || Number(b.namespace_id) <= 0) {
|
|
5210
|
+
return `ratelimit binding "${b.name}" requires a positive integer namespace_id string`;
|
|
5211
|
+
}
|
|
5212
|
+
if (!Number.isInteger(b.simple?.limit) || b.simple.limit <= 0) {
|
|
5213
|
+
return `ratelimit binding "${b.name}" requires simple.limit (positive integer)`;
|
|
5214
|
+
}
|
|
5215
|
+
if (b.simple.period !== 10 && b.simple.period !== 60) {
|
|
5216
|
+
return `ratelimit binding "${b.name}" requires simple.period (10 or 60)`;
|
|
5217
|
+
}
|
|
5218
|
+
return null;
|
|
5206
5219
|
case "ai":
|
|
5207
5220
|
case "browser_rendering":
|
|
5208
5221
|
case "analytics_engine":
|
|
@@ -5280,6 +5293,15 @@ function bindingManifestFromOutputConfig(outputConfig) {
|
|
|
5280
5293
|
const id = pluckString(h, "id");
|
|
5281
5294
|
if (name && id) out.push({ type: "hyperdrive", name, id });
|
|
5282
5295
|
}
|
|
5296
|
+
for (const r of asObjectArray(outputConfig.ratelimits)) {
|
|
5297
|
+
const name = pluckString(r, "name");
|
|
5298
|
+
const namespace_id = pluckString(r, "namespace_id");
|
|
5299
|
+
const simple = isObject(r.simple) ? r.simple : null;
|
|
5300
|
+
const limit = pluckNumber(simple, "limit");
|
|
5301
|
+
const period = pluckNumber(simple, "period");
|
|
5302
|
+
if (!name || !namespace_id || limit === null || period !== 10 && period !== 60) continue;
|
|
5303
|
+
out.push({ type: "ratelimit", name, namespace_id, simple: { limit, period } });
|
|
5304
|
+
}
|
|
5283
5305
|
return out;
|
|
5284
5306
|
}
|
|
5285
5307
|
function isObject(v) {
|
|
@@ -6022,6 +6044,132 @@ function validateAppMigrationIds(value) {
|
|
|
6022
6044
|
return { valid: true, ids };
|
|
6023
6045
|
}
|
|
6024
6046
|
|
|
6047
|
+
// src/shared/ai-models.ts
|
|
6048
|
+
var DEEPSPACE_MODEL_CATALOG_PROVENANCE = {
|
|
6049
|
+
version: "2026-08-04",
|
|
6050
|
+
verifiedAt: "2026-08-04",
|
|
6051
|
+
sources: {
|
|
6052
|
+
anthropic: "https://platform.claude.com/docs/en/api/beta/models/list",
|
|
6053
|
+
openai: "https://developers.openai.com/api/docs/models/all",
|
|
6054
|
+
cerebras: "https://inference-docs.cerebras.ai/models/overview"
|
|
6055
|
+
}
|
|
6056
|
+
};
|
|
6057
|
+
var MULTI_STEP_AGENT_PROFILES = ["application", "documentation"];
|
|
6058
|
+
function createMultiStepModelFactory(provider) {
|
|
6059
|
+
return (model) => ({
|
|
6060
|
+
...model,
|
|
6061
|
+
...provider,
|
|
6062
|
+
agentSupport: "multi-step",
|
|
6063
|
+
agentProfiles: MULTI_STEP_AGENT_PROFILES
|
|
6064
|
+
});
|
|
6065
|
+
}
|
|
6066
|
+
var anthropicModel = createMultiStepModelFactory({
|
|
6067
|
+
provider: "anthropic",
|
|
6068
|
+
providerLabel: "Anthropic",
|
|
6069
|
+
transport: "messages"
|
|
6070
|
+
});
|
|
6071
|
+
var openAIModel = createMultiStepModelFactory({
|
|
6072
|
+
provider: "openai",
|
|
6073
|
+
providerLabel: "OpenAI",
|
|
6074
|
+
transport: "chat-completions"
|
|
6075
|
+
});
|
|
6076
|
+
var DEEPSPACE_AI_MODELS = [
|
|
6077
|
+
anthropicModel({
|
|
6078
|
+
id: "claude-fable-5",
|
|
6079
|
+
label: "Claude Fable 5",
|
|
6080
|
+
family: "Claude 5",
|
|
6081
|
+
recommendation: "frontier"
|
|
6082
|
+
}),
|
|
6083
|
+
anthropicModel({
|
|
6084
|
+
id: "claude-opus-5",
|
|
6085
|
+
label: "Claude Opus 5",
|
|
6086
|
+
family: "Claude 5",
|
|
6087
|
+
recommendation: "frontier"
|
|
6088
|
+
}),
|
|
6089
|
+
anthropicModel({
|
|
6090
|
+
id: "claude-sonnet-5",
|
|
6091
|
+
label: "Claude Sonnet 5",
|
|
6092
|
+
family: "Claude 5",
|
|
6093
|
+
recommendation: "balanced"
|
|
6094
|
+
}),
|
|
6095
|
+
anthropicModel({
|
|
6096
|
+
id: "claude-haiku-4-5",
|
|
6097
|
+
label: "Claude Haiku 4.5",
|
|
6098
|
+
family: "Claude 4.5",
|
|
6099
|
+
recommendation: "fast"
|
|
6100
|
+
}),
|
|
6101
|
+
openAIModel({
|
|
6102
|
+
id: "gpt-5.6-sol",
|
|
6103
|
+
label: "GPT-5.6 Sol",
|
|
6104
|
+
family: "GPT-5.6",
|
|
6105
|
+
recommendation: "frontier"
|
|
6106
|
+
}),
|
|
6107
|
+
openAIModel({
|
|
6108
|
+
id: "gpt-5.6-terra",
|
|
6109
|
+
label: "GPT-5.6 Terra",
|
|
6110
|
+
family: "GPT-5.6",
|
|
6111
|
+
recommendation: "balanced"
|
|
6112
|
+
}),
|
|
6113
|
+
openAIModel({
|
|
6114
|
+
id: "gpt-5.6-luna",
|
|
6115
|
+
label: "GPT-5.6 Luna",
|
|
6116
|
+
family: "GPT-5.6",
|
|
6117
|
+
recommendation: "fast"
|
|
6118
|
+
}),
|
|
6119
|
+
{
|
|
6120
|
+
id: "gpt-oss-120b",
|
|
6121
|
+
label: "GPT-OSS 120B",
|
|
6122
|
+
provider: "cerebras",
|
|
6123
|
+
providerLabel: "Cerebras",
|
|
6124
|
+
family: "GPT-OSS",
|
|
6125
|
+
agentSupport: "single-step",
|
|
6126
|
+
agentProfiles: [],
|
|
6127
|
+
transport: "chat-completions",
|
|
6128
|
+
recommendation: "limited",
|
|
6129
|
+
note: "Available for direct generation; the current proxy adapter has not passed multi-step tool-result continuation."
|
|
6130
|
+
}
|
|
6131
|
+
];
|
|
6132
|
+
var DEEPSPACE_AI_DEFAULTS = {
|
|
6133
|
+
agent: "claude-sonnet-5",
|
|
6134
|
+
directGeneration: "claude-sonnet-5",
|
|
6135
|
+
summarization: "claude-haiku-4-5"
|
|
6136
|
+
};
|
|
6137
|
+
var DEEPSPACE_AGENT_PROFILES = {
|
|
6138
|
+
application: {
|
|
6139
|
+
id: "application",
|
|
6140
|
+
defaultModel: DEEPSPACE_AI_DEFAULTS.agent,
|
|
6141
|
+
maxSteps: 21,
|
|
6142
|
+
maxToolCalls: 20,
|
|
6143
|
+
allowedTools: "application-defined"
|
|
6144
|
+
},
|
|
6145
|
+
documentation: {
|
|
6146
|
+
id: "documentation",
|
|
6147
|
+
defaultModel: DEEPSPACE_AI_DEFAULTS.agent,
|
|
6148
|
+
// Permit up to twenty completed read-only tools plus one final synthesis
|
|
6149
|
+
// step. The runner disables tools once the tool-call budget is exhausted.
|
|
6150
|
+
maxSteps: 21,
|
|
6151
|
+
maxToolCalls: 20,
|
|
6152
|
+
allowedTools: ["documentation_search", "documentation_read"]
|
|
6153
|
+
}
|
|
6154
|
+
};
|
|
6155
|
+
function getDeepSpaceAIModel(modelId) {
|
|
6156
|
+
if (typeof modelId !== "string") return null;
|
|
6157
|
+
return DEEPSPACE_AI_MODELS.find((model) => model.id === modelId) ?? null;
|
|
6158
|
+
}
|
|
6159
|
+
function listDeepSpaceAgentModels(profileId = "application") {
|
|
6160
|
+
const defaultModel = DEEPSPACE_AGENT_PROFILES[profileId].defaultModel;
|
|
6161
|
+
return DEEPSPACE_AI_MODELS.filter(
|
|
6162
|
+
(model) => model.agentProfiles.includes(profileId)
|
|
6163
|
+
).sort((left, right) => Number(right.id === defaultModel) - Number(left.id === defaultModel));
|
|
6164
|
+
}
|
|
6165
|
+
function resolveDeepSpaceAgentModel(modelId, profileId = "application") {
|
|
6166
|
+
const profile = DEEPSPACE_AGENT_PROFILES[profileId];
|
|
6167
|
+
const resolvedId = modelId === void 0 ? profile.defaultModel : modelId;
|
|
6168
|
+
const model = getDeepSpaceAIModel(resolvedId);
|
|
6169
|
+
if (!model || !model.agentProfiles.includes(profileId)) return null;
|
|
6170
|
+
return { modelId: model.id, provider: model.provider, model, profile };
|
|
6171
|
+
}
|
|
6172
|
+
|
|
6025
6173
|
// src/server/utils/cors.ts
|
|
6026
6174
|
function matchOrigin(origin, patterns) {
|
|
6027
6175
|
if (!origin) return null;
|
|
@@ -6393,7 +6541,7 @@ function authWorkerFetch(env, path, init) {
|
|
|
6393
6541
|
function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
6394
6542
|
const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
|
|
6395
6543
|
const room = env.RECORD_ROOMS.get(roomIdObj);
|
|
6396
|
-
async function executeTool3(
|
|
6544
|
+
async function executeTool3(tool3, params) {
|
|
6397
6545
|
const response = await room.fetch(
|
|
6398
6546
|
new Request("https://internal/api/tools/execute", {
|
|
6399
6547
|
method: "POST",
|
|
@@ -6402,12 +6550,12 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
|
6402
6550
|
"X-User-Id": ownerUserId,
|
|
6403
6551
|
"X-App-Action": "true"
|
|
6404
6552
|
},
|
|
6405
|
-
body: JSON.stringify({ tool:
|
|
6553
|
+
body: JSON.stringify({ tool: tool3, params })
|
|
6406
6554
|
})
|
|
6407
6555
|
);
|
|
6408
6556
|
const result = await response.json();
|
|
6409
6557
|
if (!result.success) {
|
|
6410
|
-
throw new Error(`RecordRoom tool ${
|
|
6558
|
+
throw new Error(`RecordRoom tool ${tool3} failed: ${result.error || "Unknown error"}`);
|
|
6411
6559
|
}
|
|
6412
6560
|
return result.data;
|
|
6413
6561
|
}
|
|
@@ -6529,6 +6677,162 @@ function createDeepSpaceAI(env, provider, options = {}) {
|
|
|
6529
6677
|
}
|
|
6530
6678
|
}
|
|
6531
6679
|
|
|
6680
|
+
// src/server/utils/agent.ts
|
|
6681
|
+
import {
|
|
6682
|
+
stepCountIs,
|
|
6683
|
+
streamText
|
|
6684
|
+
} from "ai";
|
|
6685
|
+
var DeepSpaceAgentModelError = class extends Error {
|
|
6686
|
+
constructor(modelId, profile) {
|
|
6687
|
+
super(
|
|
6688
|
+
`Model ${typeof modelId === "string" ? JSON.stringify(modelId) : String(modelId)} is not compatible with the ${profile} agent profile`
|
|
6689
|
+
);
|
|
6690
|
+
this.modelId = modelId;
|
|
6691
|
+
this.profile = profile;
|
|
6692
|
+
this.name = "DeepSpaceAgentModelError";
|
|
6693
|
+
}
|
|
6694
|
+
code = "unsupported_agent_model";
|
|
6695
|
+
};
|
|
6696
|
+
var DeepSpaceAgentProfileError = class extends Error {
|
|
6697
|
+
constructor(profile, toolName) {
|
|
6698
|
+
super(`The ${profile} agent profile does not allow tool ${JSON.stringify(toolName)}`);
|
|
6699
|
+
this.profile = profile;
|
|
6700
|
+
this.toolName = toolName;
|
|
6701
|
+
this.name = "DeepSpaceAgentProfileError";
|
|
6702
|
+
}
|
|
6703
|
+
code = "agent_profile_violation";
|
|
6704
|
+
};
|
|
6705
|
+
function streamDeepSpaceAgent(env, options) {
|
|
6706
|
+
const { profile, modelId, authToken, ...streamOptions } = options;
|
|
6707
|
+
const selection = resolveDeepSpaceAgentModel(modelId, profile);
|
|
6708
|
+
if (!selection) throw new DeepSpaceAgentModelError(modelId, profile);
|
|
6709
|
+
if (selection.profile.allowedTools !== "application-defined") {
|
|
6710
|
+
const allowed = selection.profile.allowedTools;
|
|
6711
|
+
for (const toolName of Object.keys(streamOptions.tools ?? {})) {
|
|
6712
|
+
if (!allowed.includes(toolName)) {
|
|
6713
|
+
throw new DeepSpaceAgentProfileError(profile, toolName);
|
|
6714
|
+
}
|
|
6715
|
+
}
|
|
6716
|
+
}
|
|
6717
|
+
const provider = createDeepSpaceAI(env, selection.provider, authToken ? { authToken } : {});
|
|
6718
|
+
const { prepareStep, tools, ...remainingStreamOptions } = streamOptions;
|
|
6719
|
+
const boundedTools = limitToolExecutions(tools, selection.profile.maxToolCalls);
|
|
6720
|
+
const result = streamText({
|
|
6721
|
+
...remainingStreamOptions,
|
|
6722
|
+
...boundedTools ? { tools: boundedTools } : {},
|
|
6723
|
+
model: provider(selection.modelId),
|
|
6724
|
+
stopWhen: stepCountIs(selection.profile.maxSteps),
|
|
6725
|
+
prepareStep: enforceToolCallLimit(prepareStep, selection.profile.maxToolCalls),
|
|
6726
|
+
...selection.provider === "openai" ? { providerOptions: { openai: { reasoningEffort: "none" } } } : {}
|
|
6727
|
+
});
|
|
6728
|
+
return { selection, result };
|
|
6729
|
+
}
|
|
6730
|
+
function enforceToolCallLimit(prepareStep, maxToolCalls) {
|
|
6731
|
+
if (maxToolCalls === void 0) return prepareStep;
|
|
6732
|
+
return async (context) => {
|
|
6733
|
+
const prepared = await prepareStep?.(context);
|
|
6734
|
+
const completedToolCalls = context.steps.reduce(
|
|
6735
|
+
(count, step) => count + step.toolCalls.length,
|
|
6736
|
+
0
|
|
6737
|
+
);
|
|
6738
|
+
if (completedToolCalls < maxToolCalls) return prepared;
|
|
6739
|
+
return { ...prepared, activeTools: [], toolChoice: "none" };
|
|
6740
|
+
};
|
|
6741
|
+
}
|
|
6742
|
+
function limitToolExecutions(tools, maxToolCalls) {
|
|
6743
|
+
if (!tools || maxToolCalls === void 0) return tools;
|
|
6744
|
+
let executions = 0;
|
|
6745
|
+
return Object.fromEntries(
|
|
6746
|
+
Object.entries(tools).map(([name, definition]) => {
|
|
6747
|
+
if (typeof definition.execute !== "function") return [name, definition];
|
|
6748
|
+
const execute = definition.execute;
|
|
6749
|
+
return [
|
|
6750
|
+
name,
|
|
6751
|
+
{
|
|
6752
|
+
...definition,
|
|
6753
|
+
execute: (...args) => {
|
|
6754
|
+
if (executions >= maxToolCalls) {
|
|
6755
|
+
return Promise.resolve({
|
|
6756
|
+
error: `The agent tool-call limit of ${maxToolCalls} has been reached`
|
|
6757
|
+
});
|
|
6758
|
+
}
|
|
6759
|
+
executions++;
|
|
6760
|
+
return execute(...args);
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
];
|
|
6764
|
+
})
|
|
6765
|
+
);
|
|
6766
|
+
}
|
|
6767
|
+
function deepSpaceAgentErrorSummary(error, context = {}) {
|
|
6768
|
+
const details = [];
|
|
6769
|
+
const contextFields = [
|
|
6770
|
+
context.profile ? `profile=${context.profile}` : void 0,
|
|
6771
|
+
context.provider ? `provider=${safeLogToken(context.provider)}` : void 0,
|
|
6772
|
+
context.modelId ? `model=${safeLogToken(context.modelId)}` : void 0
|
|
6773
|
+
].filter((part) => Boolean(part));
|
|
6774
|
+
if (contextFields.length > 0) details.push(contextFields.join(" "));
|
|
6775
|
+
const pending = [error];
|
|
6776
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6777
|
+
while (pending.length > 0 && details.length < 8) {
|
|
6778
|
+
const current = pending.shift();
|
|
6779
|
+
if (current == null || visited.has(current)) continue;
|
|
6780
|
+
visited.add(current);
|
|
6781
|
+
if (typeof current === "object") {
|
|
6782
|
+
const record = current;
|
|
6783
|
+
const parts = [
|
|
6784
|
+
typeof record.name === "string" ? `name=${safeLogToken(record.name)}` : current instanceof Error ? `name=${safeLogToken(current.name)}` : void 0,
|
|
6785
|
+
safeErrorMessage(current, record),
|
|
6786
|
+
typeof record.statusCode === "number" ? `status=${record.statusCode}` : void 0,
|
|
6787
|
+
typeof record.code === "string" ? `code=${safeLogToken(record.code)}` : void 0,
|
|
6788
|
+
typeof record.requestId === "string" ? `requestId=${safeLogToken(record.requestId)}` : void 0,
|
|
6789
|
+
...safeProviderResponseMetadata(record.responseBody)
|
|
6790
|
+
].filter((part) => Boolean(part));
|
|
6791
|
+
details.push(parts.join(" ") || Object.prototype.toString.call(current));
|
|
6792
|
+
if (record.lastError != null) pending.push(record.lastError);
|
|
6793
|
+
if (record.cause != null) pending.push(record.cause);
|
|
6794
|
+
if (Array.isArray(record.errors)) pending.push(...record.errors);
|
|
6795
|
+
continue;
|
|
6796
|
+
}
|
|
6797
|
+
details.push(String(current));
|
|
6798
|
+
}
|
|
6799
|
+
return details.join(" <- ") || "Unknown error";
|
|
6800
|
+
}
|
|
6801
|
+
function safeErrorMessage(current, record) {
|
|
6802
|
+
if (record.responseBody !== void 0) return void 0;
|
|
6803
|
+
const message = current instanceof Error ? current.message : record.message;
|
|
6804
|
+
if (typeof message !== "string" || !message.trim()) return void 0;
|
|
6805
|
+
return `message=${safeLogText(message)}`;
|
|
6806
|
+
}
|
|
6807
|
+
function safeProviderResponseMetadata(responseBody) {
|
|
6808
|
+
if (typeof responseBody !== "string") return [];
|
|
6809
|
+
try {
|
|
6810
|
+
const parsed = JSON.parse(responseBody);
|
|
6811
|
+
const error = parsed.error && typeof parsed.error === "object" ? parsed.error : void 0;
|
|
6812
|
+
const code = typeof error?.code === "string" ? error.code : typeof error?.type === "string" ? error.type : void 0;
|
|
6813
|
+
const requestId = typeof parsed.request_id === "string" ? parsed.request_id : void 0;
|
|
6814
|
+
return [
|
|
6815
|
+
code ? `upstreamCode=${safeLogToken(code)}` : void 0,
|
|
6816
|
+
requestId ? `upstreamRequestId=${safeLogToken(requestId)}` : void 0
|
|
6817
|
+
].filter((part) => Boolean(part));
|
|
6818
|
+
} catch {
|
|
6819
|
+
return [];
|
|
6820
|
+
}
|
|
6821
|
+
}
|
|
6822
|
+
function safeLogToken(value) {
|
|
6823
|
+
return value.replace(/[^a-zA-Z0-9._:/-]/g, "_").slice(0, 120);
|
|
6824
|
+
}
|
|
6825
|
+
function safeLogText(value) {
|
|
6826
|
+
const withoutControlCharacters = Array.from(value, (character) => {
|
|
6827
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
6828
|
+
return codePoint <= 31 || codePoint === 127 ? " " : character;
|
|
6829
|
+
}).join("");
|
|
6830
|
+
return withoutControlCharacters.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]").replace(
|
|
6831
|
+
/\b(api[_ -]?key|authorization|password|secret|token)\b\s*[:=]\s*\S+/gi,
|
|
6832
|
+
"$1=[redacted]"
|
|
6833
|
+
).trim().slice(0, 300);
|
|
6834
|
+
}
|
|
6835
|
+
|
|
6532
6836
|
// src/server/utils/composio-tools.ts
|
|
6533
6837
|
import { tool, jsonSchema } from "ai";
|
|
6534
6838
|
var EMPTY_SCHEMA = { type: "object", properties: {} };
|
|
@@ -6980,7 +7284,7 @@ function makeDefaultSummarizer(env, options = {}) {
|
|
|
6980
7284
|
}
|
|
6981
7285
|
|
|
6982
7286
|
// src/server/utils/chat-history.ts
|
|
6983
|
-
async function executeTool2(stub, userId,
|
|
7287
|
+
async function executeTool2(stub, userId, tool3, params) {
|
|
6984
7288
|
const res = await stub.fetch(new Request("https://internal/api/tools/execute", {
|
|
6985
7289
|
method: "POST",
|
|
6986
7290
|
headers: {
|
|
@@ -6988,11 +7292,11 @@ async function executeTool2(stub, userId, tool2, params) {
|
|
|
6988
7292
|
"X-User-Id": userId,
|
|
6989
7293
|
"X-App-Action": "true"
|
|
6990
7294
|
},
|
|
6991
|
-
body: JSON.stringify({ tool:
|
|
7295
|
+
body: JSON.stringify({ tool: tool3, params })
|
|
6992
7296
|
}));
|
|
6993
7297
|
const body = await res.json();
|
|
6994
7298
|
if (!body.success) {
|
|
6995
|
-
throw new Error(`chat-history ${
|
|
7299
|
+
throw new Error(`chat-history ${tool3} failed: ${body.error ?? "unknown error"}`);
|
|
6996
7300
|
}
|
|
6997
7301
|
return body.data;
|
|
6998
7302
|
}
|
|
@@ -7207,6 +7511,1109 @@ function splitStatements(sql) {
|
|
|
7207
7511
|
return sql.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7208
7512
|
}
|
|
7209
7513
|
|
|
7514
|
+
// src/documentation/worker/routes.ts
|
|
7515
|
+
import { Hono } from "hono";
|
|
7516
|
+
|
|
7517
|
+
// src/documentation/resource-path.ts
|
|
7518
|
+
function canonicalNativeDocumentationRequestPath(pathname) {
|
|
7519
|
+
try {
|
|
7520
|
+
return pathname.split("/").map((segment) => encodeURIComponent(decodeURIComponent(segment))).join("/");
|
|
7521
|
+
} catch {
|
|
7522
|
+
return null;
|
|
7523
|
+
}
|
|
7524
|
+
}
|
|
7525
|
+
|
|
7526
|
+
// src/documentation/worker/assistant.ts
|
|
7527
|
+
import { tool as tool2 } from "ai";
|
|
7528
|
+
import { z } from "zod/v4";
|
|
7529
|
+
|
|
7530
|
+
// src/documentation/types.ts
|
|
7531
|
+
var DOCUMENTATION_MANIFEST_VERSION = 2;
|
|
7532
|
+
var DocumentationError = class extends Error {
|
|
7533
|
+
code;
|
|
7534
|
+
diagnostics;
|
|
7535
|
+
constructor(message, code, diagnostics = []) {
|
|
7536
|
+
super(message);
|
|
7537
|
+
this.name = "DocumentationError";
|
|
7538
|
+
this.code = code;
|
|
7539
|
+
this.diagnostics = diagnostics;
|
|
7540
|
+
}
|
|
7541
|
+
};
|
|
7542
|
+
|
|
7543
|
+
// src/documentation/routing.ts
|
|
7544
|
+
function collapseRoute(value) {
|
|
7545
|
+
let route = `/${value}`.replace(/\/{2,}/g, "/");
|
|
7546
|
+
if (route.length > 1) route = route.replace(/\/$/, "");
|
|
7547
|
+
return route;
|
|
7548
|
+
}
|
|
7549
|
+
function normalizeRoute(value) {
|
|
7550
|
+
assertSafeRoute(value);
|
|
7551
|
+
let route = collapseRoute(value);
|
|
7552
|
+
route = route.replace(/\.(?:md|mdx)$/i, "");
|
|
7553
|
+
route = route.replace(/\/index$/i, "");
|
|
7554
|
+
return route || "/";
|
|
7555
|
+
}
|
|
7556
|
+
function normalizeDocumentationBasePath(value) {
|
|
7557
|
+
if (!value || value === "/") return "";
|
|
7558
|
+
const normalized = normalizeRoute(value);
|
|
7559
|
+
if (normalized === "/_documentation") {
|
|
7560
|
+
throw new DocumentationError("The internal /_documentation namespace cannot be a public documentation base", "documentation_base_invalid");
|
|
7561
|
+
}
|
|
7562
|
+
return normalized;
|
|
7563
|
+
}
|
|
7564
|
+
function documentationPublicPath(basePath, path) {
|
|
7565
|
+
const base = normalizeDocumentationBasePath(basePath);
|
|
7566
|
+
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
7567
|
+
if (suffix === "/") return base || "/";
|
|
7568
|
+
return `${base}${suffix}`;
|
|
7569
|
+
}
|
|
7570
|
+
function assertSafeRoute(value) {
|
|
7571
|
+
if (value.includes("\\") || value.includes("\0")) throw unsafeRoute(value);
|
|
7572
|
+
let decoded = value;
|
|
7573
|
+
for (let pass = 0; pass < 5; pass++) {
|
|
7574
|
+
let next;
|
|
7575
|
+
try {
|
|
7576
|
+
next = decodeURIComponent(decoded);
|
|
7577
|
+
} catch {
|
|
7578
|
+
throw unsafeRoute(value);
|
|
7579
|
+
}
|
|
7580
|
+
if (next === decoded) break;
|
|
7581
|
+
decoded = next;
|
|
7582
|
+
}
|
|
7583
|
+
if (decoded.includes("\\") || decoded.includes("\0") || decoded.split("/").some((segment) => segment === "." || segment === "..")) throw unsafeRoute(value);
|
|
7584
|
+
}
|
|
7585
|
+
function unsafeRoute(value) {
|
|
7586
|
+
return new DocumentationError(
|
|
7587
|
+
`Documentation route contains an unsafe path segment: ${value}`,
|
|
7588
|
+
"documentation_route_invalid",
|
|
7589
|
+
[{ code: "route_path_escape", message: value }]
|
|
7590
|
+
);
|
|
7591
|
+
}
|
|
7592
|
+
|
|
7593
|
+
// src/documentation/worker/assistant.ts
|
|
7594
|
+
var MAX_CHUNKS = 1e4;
|
|
7595
|
+
var MAX_CHUNK_CHARS = 2500;
|
|
7596
|
+
var MAX_CORPUS_CHARS = 12e6;
|
|
7597
|
+
var SEARCH_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
7598
|
+
"a",
|
|
7599
|
+
"about",
|
|
7600
|
+
"an",
|
|
7601
|
+
"and",
|
|
7602
|
+
"are",
|
|
7603
|
+
"can",
|
|
7604
|
+
"could",
|
|
7605
|
+
"do",
|
|
7606
|
+
"does",
|
|
7607
|
+
"for",
|
|
7608
|
+
"from",
|
|
7609
|
+
"has",
|
|
7610
|
+
"have",
|
|
7611
|
+
"how",
|
|
7612
|
+
"i",
|
|
7613
|
+
"in",
|
|
7614
|
+
"is",
|
|
7615
|
+
"it",
|
|
7616
|
+
"me",
|
|
7617
|
+
"my",
|
|
7618
|
+
"of",
|
|
7619
|
+
"on",
|
|
7620
|
+
"or",
|
|
7621
|
+
"our",
|
|
7622
|
+
"should",
|
|
7623
|
+
"the",
|
|
7624
|
+
"this",
|
|
7625
|
+
"to",
|
|
7626
|
+
"was",
|
|
7627
|
+
"we",
|
|
7628
|
+
"were",
|
|
7629
|
+
"what",
|
|
7630
|
+
"when",
|
|
7631
|
+
"where",
|
|
7632
|
+
"which",
|
|
7633
|
+
"will",
|
|
7634
|
+
"with",
|
|
7635
|
+
"would",
|
|
7636
|
+
"you",
|
|
7637
|
+
"your"
|
|
7638
|
+
]);
|
|
7639
|
+
var indexedChunkCache = /* @__PURE__ */ new WeakMap();
|
|
7640
|
+
function parseDocumentationCorpus(value) {
|
|
7641
|
+
if (!Array.isArray(value)) throw new Error("Documentation assistant index must be an array");
|
|
7642
|
+
if (value.length > MAX_CHUNKS) {
|
|
7643
|
+
throw new Error(`Documentation assistant index exceeds ${MAX_CHUNKS} chunks`);
|
|
7644
|
+
}
|
|
7645
|
+
const chunks = [];
|
|
7646
|
+
let totalChars2 = 0;
|
|
7647
|
+
for (let index = 0; index < value.length; index++) {
|
|
7648
|
+
const raw = value[index];
|
|
7649
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7650
|
+
throw new Error(`Documentation assistant chunk ${index} must be an object`);
|
|
7651
|
+
}
|
|
7652
|
+
const chunk = raw;
|
|
7653
|
+
if (typeof chunk.id !== "string" || !chunk.id || typeof chunk.route !== "string" || !chunk.route.startsWith("/") || typeof chunk.title !== "string" || !chunk.title || typeof chunk.text !== "string" || !chunk.text) {
|
|
7654
|
+
throw new Error(`Documentation assistant chunk ${index} has invalid required fields`);
|
|
7655
|
+
}
|
|
7656
|
+
if (chunk.text.length > MAX_CHUNK_CHARS) {
|
|
7657
|
+
throw new Error(`Documentation assistant chunk ${chunk.id} exceeds ${MAX_CHUNK_CHARS} characters`);
|
|
7658
|
+
}
|
|
7659
|
+
totalChars2 += chunk.text.length;
|
|
7660
|
+
if (totalChars2 > MAX_CORPUS_CHARS) {
|
|
7661
|
+
throw new Error(`Documentation assistant corpus exceeds ${MAX_CORPUS_CHARS} characters`);
|
|
7662
|
+
}
|
|
7663
|
+
chunks.push({
|
|
7664
|
+
id: chunk.id,
|
|
7665
|
+
route: chunk.route,
|
|
7666
|
+
title: chunk.title,
|
|
7667
|
+
...typeof chunk.heading === "string" && chunk.heading ? { heading: chunk.heading } : {},
|
|
7668
|
+
text: chunk.text
|
|
7669
|
+
});
|
|
7670
|
+
}
|
|
7671
|
+
return chunks;
|
|
7672
|
+
}
|
|
7673
|
+
function searchDocumentationCorpus(corpus, query, limit = 6) {
|
|
7674
|
+
const normalized = normalize(query);
|
|
7675
|
+
const terms = [
|
|
7676
|
+
...new Set(
|
|
7677
|
+
normalized.split(" ").filter((term) => term.length > 1 && !SEARCH_STOP_WORDS.has(term))
|
|
7678
|
+
)
|
|
7679
|
+
];
|
|
7680
|
+
if (!terms.length) return [];
|
|
7681
|
+
const requiredTerms = new Set(terms);
|
|
7682
|
+
return corpus.map((chunk) => {
|
|
7683
|
+
const { text, textTerms, title, titleTerms } = indexChunk(chunk);
|
|
7684
|
+
const textCounts = countTerms(textTerms, requiredTerms);
|
|
7685
|
+
let score = 0;
|
|
7686
|
+
if (title === normalized) score += 120;
|
|
7687
|
+
else if (title.includes(normalized)) score += 55;
|
|
7688
|
+
if (text.includes(normalized)) score += 30;
|
|
7689
|
+
for (const term of terms) {
|
|
7690
|
+
if (titleTerms.has(term)) score += 14;
|
|
7691
|
+
score += Math.min(6, textCounts.get(term) ?? 0) * 3;
|
|
7692
|
+
}
|
|
7693
|
+
const matchedTerms = terms.filter(
|
|
7694
|
+
(term) => titleTerms.has(term) || textCounts.has(term)
|
|
7695
|
+
).length;
|
|
7696
|
+
score += matchedTerms * matchedTerms * 4;
|
|
7697
|
+
score += proximityBonus([...titleTerms, ...textTerms], terms);
|
|
7698
|
+
return {
|
|
7699
|
+
route: chunk.route,
|
|
7700
|
+
title: chunk.title,
|
|
7701
|
+
...chunk.heading ? { heading: chunk.heading } : {},
|
|
7702
|
+
excerpt: excerpt(chunk.text, terms),
|
|
7703
|
+
score
|
|
7704
|
+
};
|
|
7705
|
+
}).filter((result) => result.score > 0).sort((left, right) => right.score - left.score || left.route.localeCompare(right.route)).slice(0, Math.max(1, Math.min(limit, 10)));
|
|
7706
|
+
}
|
|
7707
|
+
function readDocumentationRoute(corpus, route) {
|
|
7708
|
+
const normalizedRoute = collapseRoute(route);
|
|
7709
|
+
return corpus.filter((chunk) => chunk.route === normalizedRoute).slice(0, 12).map(({ route: chunkRoute, title, heading, text }) => ({
|
|
7710
|
+
route: chunkRoute,
|
|
7711
|
+
title,
|
|
7712
|
+
...heading ? { heading } : {},
|
|
7713
|
+
text
|
|
7714
|
+
}));
|
|
7715
|
+
}
|
|
7716
|
+
function buildDocumentationAssistantTools(corpus, basePath = "/docs") {
|
|
7717
|
+
return {
|
|
7718
|
+
documentation_search: tool2({
|
|
7719
|
+
description: "Search the current documentation. Use this before answering factual or how-to questions.",
|
|
7720
|
+
inputSchema: z.object({
|
|
7721
|
+
query: z.string().trim().min(2).max(300),
|
|
7722
|
+
limit: z.number().int().min(1).max(10).optional()
|
|
7723
|
+
}),
|
|
7724
|
+
execute: async ({ query, limit }) => ({
|
|
7725
|
+
results: searchDocumentationCorpus(corpus, query, limit ?? 6).map((result) => ({
|
|
7726
|
+
...result,
|
|
7727
|
+
url: documentationPublicPath(basePath, result.route)
|
|
7728
|
+
}))
|
|
7729
|
+
})
|
|
7730
|
+
}),
|
|
7731
|
+
documentation_read: tool2({
|
|
7732
|
+
description: "Read bounded source chunks for one exact documentation route returned by documentation_search.",
|
|
7733
|
+
inputSchema: z.object({ route: z.string().trim().startsWith("/").max(500) }),
|
|
7734
|
+
execute: async ({ route }) => ({
|
|
7735
|
+
route: collapseRoute(route),
|
|
7736
|
+
url: documentationPublicPath(basePath, route),
|
|
7737
|
+
chunks: readDocumentationRoute(corpus, route)
|
|
7738
|
+
})
|
|
7739
|
+
})
|
|
7740
|
+
};
|
|
7741
|
+
}
|
|
7742
|
+
function buildDocumentationAssistantPrompt(appName, currentRoute) {
|
|
7743
|
+
return [
|
|
7744
|
+
`You are the public documentation assistant for "${appName}" on DeepSpace.`,
|
|
7745
|
+
"Answer only from the documentation available through documentation_search and documentation_read.",
|
|
7746
|
+
"Search before answering unless the user is only greeting you.",
|
|
7747
|
+
"Use documentation_read when search excerpts are insufficient or ambiguous.",
|
|
7748
|
+
"Never claim access to application records, integrations, private data, or mutation tools.",
|
|
7749
|
+
"If the documentation does not support an answer, say so plainly and suggest the closest documented page.",
|
|
7750
|
+
"Cite factual answers with Markdown links to the exact documentation routes returned by tools.",
|
|
7751
|
+
"Do not invent URLs, APIs, configuration keys, versions, or behavior.",
|
|
7752
|
+
"Keep answers concise, practical, and explicit about uncertainty.",
|
|
7753
|
+
...currentRoute ? [`The reader is currently viewing ${collapseRoute(currentRoute)}.`] : []
|
|
7754
|
+
].join("\n");
|
|
7755
|
+
}
|
|
7756
|
+
function normalize(value) {
|
|
7757
|
+
return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter(Boolean).map(canonicalSearchTerm).join(" ");
|
|
7758
|
+
}
|
|
7759
|
+
function canonicalSearchTerm(term) {
|
|
7760
|
+
if (SEARCH_STOP_WORDS.has(term)) return term;
|
|
7761
|
+
if (["doc", "docs", "document", "documents", "documentation"].includes(term)) return "documentation";
|
|
7762
|
+
if (["builds", "building", "built", "compile", "compiled", "compiles", "compiling"].includes(term)) {
|
|
7763
|
+
return "build";
|
|
7764
|
+
}
|
|
7765
|
+
return term.length > 3 && term.endsWith("s") && !term.endsWith("ss") ? term.slice(0, -1) : term;
|
|
7766
|
+
}
|
|
7767
|
+
function indexChunk(chunk) {
|
|
7768
|
+
const cached = indexedChunkCache.get(chunk);
|
|
7769
|
+
if (cached) return cached;
|
|
7770
|
+
const title = normalize(`${chunk.title} ${chunk.heading ?? ""}`);
|
|
7771
|
+
const text = normalize(chunk.text);
|
|
7772
|
+
const indexed = {
|
|
7773
|
+
text,
|
|
7774
|
+
textTerms: text.split(" "),
|
|
7775
|
+
title,
|
|
7776
|
+
titleTerms: new Set(title.split(" "))
|
|
7777
|
+
};
|
|
7778
|
+
indexedChunkCache.set(chunk, indexed);
|
|
7779
|
+
return indexed;
|
|
7780
|
+
}
|
|
7781
|
+
function countTerms(tokens, required) {
|
|
7782
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7783
|
+
for (const token of tokens) {
|
|
7784
|
+
if (required.has(token)) counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
7785
|
+
}
|
|
7786
|
+
return counts;
|
|
7787
|
+
}
|
|
7788
|
+
function proximityBonus(tokens, terms) {
|
|
7789
|
+
const required = new Set(terms);
|
|
7790
|
+
if (required.size < 2) return 0;
|
|
7791
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7792
|
+
let matched = 0;
|
|
7793
|
+
let start = 0;
|
|
7794
|
+
let smallestWindow = Number.POSITIVE_INFINITY;
|
|
7795
|
+
for (let end = 0; end < tokens.length; end++) {
|
|
7796
|
+
const token = tokens[end] ?? "";
|
|
7797
|
+
if (required.has(token)) {
|
|
7798
|
+
const count = counts.get(token) ?? 0;
|
|
7799
|
+
counts.set(token, count + 1);
|
|
7800
|
+
if (count === 0) matched++;
|
|
7801
|
+
}
|
|
7802
|
+
while (matched === required.size && start <= end) {
|
|
7803
|
+
smallestWindow = Math.min(smallestWindow, end - start + 1);
|
|
7804
|
+
const first = tokens[start] ?? "";
|
|
7805
|
+
if (required.has(first)) {
|
|
7806
|
+
const count = counts.get(first) ?? 0;
|
|
7807
|
+
counts.set(first, count - 1);
|
|
7808
|
+
if (count === 1) matched--;
|
|
7809
|
+
}
|
|
7810
|
+
start++;
|
|
7811
|
+
}
|
|
7812
|
+
}
|
|
7813
|
+
return Number.isFinite(smallestWindow) ? Math.max(0, 42 - smallestWindow * 2) : 0;
|
|
7814
|
+
}
|
|
7815
|
+
function excerpt(text, terms) {
|
|
7816
|
+
const lower = text.toLowerCase();
|
|
7817
|
+
const first = terms.map((term) => lower.indexOf(term)).filter((index) => index >= 0).sort((left, right) => left - right)[0] ?? 0;
|
|
7818
|
+
const start = Math.max(0, first - 160);
|
|
7819
|
+
const end = Math.min(text.length, start + 520);
|
|
7820
|
+
return `${start > 0 ? "\u2026" : ""}${text.slice(start, end).trim()}${end < text.length ? "\u2026" : ""}`;
|
|
7821
|
+
}
|
|
7822
|
+
|
|
7823
|
+
// src/documentation/worker/published-corpus.ts
|
|
7824
|
+
var cachedCorpus;
|
|
7825
|
+
async function loadDocumentationPublishedManifest(env, requestUrl = "https://assets.local/", assetBasePath = "/_documentation") {
|
|
7826
|
+
const response = await env.ASSETS.fetch(
|
|
7827
|
+
new Request(new URL(`${assetBasePath}/manifest.json`, requestUrl))
|
|
7828
|
+
);
|
|
7829
|
+
if (!response.ok) return null;
|
|
7830
|
+
const value = await response.json().catch(() => null);
|
|
7831
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
7832
|
+
const manifest = value;
|
|
7833
|
+
const assistant = parseAssistantPolicy(manifest.assistant);
|
|
7834
|
+
const mcp = parseMcpPolicy(manifest.mcp);
|
|
7835
|
+
const routes = parsePublishedRoutes(manifest.routes);
|
|
7836
|
+
const resources = parsePublishedPaths(manifest.resources, true);
|
|
7837
|
+
if (manifest.version !== DOCUMENTATION_MANIFEST_VERSION || typeof manifest.sourceHash !== "string" || typeof manifest.name !== "string" || !assistant || !mcp || !routes || !resources)
|
|
7838
|
+
return null;
|
|
7839
|
+
return {
|
|
7840
|
+
sourceHash: manifest.sourceHash,
|
|
7841
|
+
name: manifest.name,
|
|
7842
|
+
routes,
|
|
7843
|
+
resources,
|
|
7844
|
+
assistant,
|
|
7845
|
+
mcp
|
|
7846
|
+
};
|
|
7847
|
+
}
|
|
7848
|
+
function parsePublishedRoutes(value) {
|
|
7849
|
+
return parsePublishedPaths(value, false);
|
|
7850
|
+
}
|
|
7851
|
+
function parsePublishedPaths(value, allowEmpty) {
|
|
7852
|
+
if (!Array.isArray(value) || !allowEmpty && value.length === 0) return null;
|
|
7853
|
+
if (value.some((path) => typeof path !== "string" || !path.startsWith("/"))) return null;
|
|
7854
|
+
if (value.some((path) => canonicalNativeDocumentationRequestPath(path) !== path))
|
|
7855
|
+
return null;
|
|
7856
|
+
return [...new Set(value)];
|
|
7857
|
+
}
|
|
7858
|
+
async function loadDocumentationPublishedCorpus(env, sourceHash, requestUrl = "https://assets.local/", assetBasePath = "/_documentation") {
|
|
7859
|
+
if (cachedCorpus?.sourceHash === sourceHash) return cachedCorpus.chunks;
|
|
7860
|
+
const response = await env.ASSETS.fetch(
|
|
7861
|
+
new Request(new URL(`${assetBasePath}/assistant-index.json`, requestUrl))
|
|
7862
|
+
);
|
|
7863
|
+
if (!response.ok) throw new Error(`assistant-index.json returned ${response.status}`);
|
|
7864
|
+
const chunks = parseDocumentationCorpus(await response.json());
|
|
7865
|
+
cachedCorpus = { sourceHash, chunks };
|
|
7866
|
+
return chunks;
|
|
7867
|
+
}
|
|
7868
|
+
async function resolveDocumentationPublishedCorpus(env, sourceHash, requestUrl = "https://assets.local/", assetBasePath = "/_documentation") {
|
|
7869
|
+
try {
|
|
7870
|
+
return {
|
|
7871
|
+
ok: true,
|
|
7872
|
+
chunks: await loadDocumentationPublishedCorpus(env, sourceHash, requestUrl, assetBasePath)
|
|
7873
|
+
};
|
|
7874
|
+
} catch (error) {
|
|
7875
|
+
return { ok: false, error };
|
|
7876
|
+
}
|
|
7877
|
+
}
|
|
7878
|
+
async function readDocumentationPublishedPage(env, corpus, requestedRoute, requestUrl = "https://assets.local/", assetBasePath = "/_documentation") {
|
|
7879
|
+
const route = collapseRoute(requestedRoute.split("#", 1)[0] ?? "/");
|
|
7880
|
+
const firstChunk = corpus.find((chunk) => chunk.route === route);
|
|
7881
|
+
if (!firstChunk) return null;
|
|
7882
|
+
const pathname = route === "/" ? `${assetBasePath}/index.md` : `${assetBasePath}${route}.md`;
|
|
7883
|
+
const response = await env.ASSETS.fetch(new Request(new URL(pathname, requestUrl)));
|
|
7884
|
+
if (!response.ok) return null;
|
|
7885
|
+
const markdown = await response.text();
|
|
7886
|
+
if (markdown.length > 5e5) throw new Error("Documentation page exceeds 500000 characters");
|
|
7887
|
+
return { route, title: firstChunk.title, markdown };
|
|
7888
|
+
}
|
|
7889
|
+
function parseAssistantPolicy(value) {
|
|
7890
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
7891
|
+
const policy = value;
|
|
7892
|
+
if (!["disabled", "public", "authenticated"].includes(String(policy.access))) return null;
|
|
7893
|
+
return {
|
|
7894
|
+
access: policy.access,
|
|
7895
|
+
...typeof policy.model === "string" ? { model: policy.model } : {}
|
|
7896
|
+
};
|
|
7897
|
+
}
|
|
7898
|
+
function parseMcpPolicy(value) {
|
|
7899
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
7900
|
+
const policy = value;
|
|
7901
|
+
if (!["disabled", "public"].includes(String(policy.access))) return null;
|
|
7902
|
+
return { access: policy.access };
|
|
7903
|
+
}
|
|
7904
|
+
|
|
7905
|
+
// src/documentation/worker/public-limiter.ts
|
|
7906
|
+
async function documentationPublicClientKey(value) {
|
|
7907
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
7908
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
7909
|
+
}
|
|
7910
|
+
async function checkDocumentationPublicLimit(env, clientKey, logPrefix) {
|
|
7911
|
+
const clientLimiter = env.DOCUMENTATION_CLIENT_RATE_LIMITER;
|
|
7912
|
+
const appLimiter = env.DOCUMENTATION_APP_RATE_LIMITER;
|
|
7913
|
+
if (!clientLimiter || !appLimiter) return { ok: false, status: 503 };
|
|
7914
|
+
try {
|
|
7915
|
+
const client = await clientLimiter.limit({ key: `${env.DEEPSPACE_APP_ID}:${clientKey}` });
|
|
7916
|
+
if (!client.success) return { ok: false, status: 429 };
|
|
7917
|
+
const app = await appLimiter.limit({ key: env.DEEPSPACE_APP_ID });
|
|
7918
|
+
return app.success ? { ok: true } : { ok: false, status: 429 };
|
|
7919
|
+
} catch (error) {
|
|
7920
|
+
console.error(`[${logPrefix}] limiter unavailable: ${documentationLimiterErrorSummary(error)}`);
|
|
7921
|
+
return { ok: false, status: 503 };
|
|
7922
|
+
}
|
|
7923
|
+
}
|
|
7924
|
+
function documentationLimiterErrorSummary(error) {
|
|
7925
|
+
if (!error || typeof error !== "object") return String(error);
|
|
7926
|
+
const value = error;
|
|
7927
|
+
return JSON.stringify({
|
|
7928
|
+
name: typeof value.name === "string" ? value.name : void 0,
|
|
7929
|
+
message: typeof value.message === "string" ? value.message : void 0,
|
|
7930
|
+
retryable: value.retryable === true,
|
|
7931
|
+
overloaded: value.overloaded === true,
|
|
7932
|
+
cause: typeof value.cause === "string" ? value.cause : void 0
|
|
7933
|
+
});
|
|
7934
|
+
}
|
|
7935
|
+
|
|
7936
|
+
// src/documentation/worker/request-body.ts
|
|
7937
|
+
var RequestBodyTooLargeError = class extends Error {
|
|
7938
|
+
constructor(maxBytes) {
|
|
7939
|
+
super(`Request body exceeds ${maxBytes} bytes`);
|
|
7940
|
+
this.maxBytes = maxBytes;
|
|
7941
|
+
this.name = "RequestBodyTooLargeError";
|
|
7942
|
+
}
|
|
7943
|
+
};
|
|
7944
|
+
async function readBoundedRequestText(request, maxBytes) {
|
|
7945
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
7946
|
+
throw new TypeError("maxBytes must be a positive safe integer");
|
|
7947
|
+
}
|
|
7948
|
+
const declared = request.headers.get("content-length");
|
|
7949
|
+
if (declared && /^\d+$/.test(declared.trim()) && Number(declared) > maxBytes) {
|
|
7950
|
+
throw new RequestBodyTooLargeError(maxBytes);
|
|
7951
|
+
}
|
|
7952
|
+
if (!request.body) return "";
|
|
7953
|
+
const reader = request.body.getReader();
|
|
7954
|
+
const decoder = new TextDecoder();
|
|
7955
|
+
let bytes = 0;
|
|
7956
|
+
let text = "";
|
|
7957
|
+
while (true) {
|
|
7958
|
+
const chunk = await reader.read();
|
|
7959
|
+
if (chunk.done) break;
|
|
7960
|
+
bytes += chunk.value.byteLength;
|
|
7961
|
+
if (bytes > maxBytes) {
|
|
7962
|
+
await reader.cancel().catch(() => void 0);
|
|
7963
|
+
throw new RequestBodyTooLargeError(maxBytes);
|
|
7964
|
+
}
|
|
7965
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
7966
|
+
}
|
|
7967
|
+
return text + decoder.decode();
|
|
7968
|
+
}
|
|
7969
|
+
|
|
7970
|
+
// src/documentation/worker/assistant-routes.ts
|
|
7971
|
+
var MAX_QUESTION_CHARS = 4e3;
|
|
7972
|
+
var MAX_HISTORY_MESSAGES = 12;
|
|
7973
|
+
var MAX_HISTORY_CHARS = 2e4;
|
|
7974
|
+
var MAX_REQUEST_BYTES = 128 * 1024;
|
|
7975
|
+
function registerDocumentationAssistantRoutes(app, resolveAuth, publicBasePath = "/docs", assetBasePath = "/_documentation") {
|
|
7976
|
+
app.post(`${publicBasePath}/api/ai`, async (c) => {
|
|
7977
|
+
const manifest = await loadDocumentationPublishedManifest(c.env, c.req.url, assetBasePath);
|
|
7978
|
+
if (!manifest || manifest.assistant.access === "disabled") return c.notFound();
|
|
7979
|
+
let authToken;
|
|
7980
|
+
let publicClientKey;
|
|
7981
|
+
if (manifest.assistant.access === "authenticated") {
|
|
7982
|
+
const auth = await resolveAuth(c.req.raw, c.env);
|
|
7983
|
+
if (!auth) return c.json({ error: "Sign in required" }, 401);
|
|
7984
|
+
const header = c.req.header("Authorization") ?? "";
|
|
7985
|
+
if (!header.startsWith("Bearer ")) return c.json({ error: "Sign in required" }, 401);
|
|
7986
|
+
authToken = header.slice(7);
|
|
7987
|
+
} else {
|
|
7988
|
+
if (!c.env.APP_OWNER_JWT) {
|
|
7989
|
+
return c.json({ error: "Public documentation assistant billing is not configured" }, 503);
|
|
7990
|
+
}
|
|
7991
|
+
publicClientKey = await documentationPublicClientKey(
|
|
7992
|
+
c.req.header("CF-Connecting-IP") ?? "unknown"
|
|
7993
|
+
);
|
|
7994
|
+
}
|
|
7995
|
+
const limitResult = publicClientKey ? await checkDocumentationPublicLimit(c.env, publicClientKey, "documentation-assistant") : void 0;
|
|
7996
|
+
if (limitResult && !limitResult.ok) {
|
|
7997
|
+
return c.json(
|
|
7998
|
+
{
|
|
7999
|
+
error: limitResult.status === 429 ? "Documentation assistant rate limit exceeded. Try again shortly." : "Documentation assistant is temporarily unavailable"
|
|
8000
|
+
},
|
|
8001
|
+
limitResult.status,
|
|
8002
|
+
limitResult.status === 429 ? { "Retry-After": "60" } : void 0
|
|
8003
|
+
);
|
|
8004
|
+
}
|
|
8005
|
+
let body = null;
|
|
8006
|
+
try {
|
|
8007
|
+
const raw = await readBoundedRequestText(c.req.raw, MAX_REQUEST_BYTES);
|
|
8008
|
+
const parsed = JSON.parse(raw);
|
|
8009
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
8010
|
+
body = parsed;
|
|
8011
|
+
}
|
|
8012
|
+
} catch (error) {
|
|
8013
|
+
if (error instanceof RequestBodyTooLargeError) {
|
|
8014
|
+
return c.json({ error: `request exceeds ${MAX_REQUEST_BYTES} bytes` }, 413);
|
|
8015
|
+
}
|
|
8016
|
+
return c.json({ error: "request body must be valid JSON" }, 400);
|
|
8017
|
+
}
|
|
8018
|
+
const question = typeof body?.question === "string" ? body.question.trim() : "";
|
|
8019
|
+
if (!question) return c.json({ error: "question is required" }, 400);
|
|
8020
|
+
if (question.length > MAX_QUESTION_CHARS) {
|
|
8021
|
+
return c.json({ error: `question exceeds ${MAX_QUESTION_CHARS} characters` }, 413);
|
|
8022
|
+
}
|
|
8023
|
+
const route = typeof body?.route === "string" && body.route.startsWith("/") ? body.route.slice(0, 500) : void 0;
|
|
8024
|
+
let history;
|
|
8025
|
+
try {
|
|
8026
|
+
history = normalizeDocumentationAssistantHistory(body?.messages);
|
|
8027
|
+
} catch (error) {
|
|
8028
|
+
return c.json({ error: error instanceof Error ? error.message : "messages are invalid" }, 400);
|
|
8029
|
+
}
|
|
8030
|
+
const corpusResult = await resolveDocumentationPublishedCorpus(
|
|
8031
|
+
c.env,
|
|
8032
|
+
manifest.sourceHash,
|
|
8033
|
+
c.req.url,
|
|
8034
|
+
assetBasePath
|
|
8035
|
+
);
|
|
8036
|
+
if (!corpusResult.ok) {
|
|
8037
|
+
console.error("[documentation-assistant] corpus unavailable:", corpusResult.error);
|
|
8038
|
+
return c.json({ error: "Documentation corpus is unavailable" }, 503);
|
|
8039
|
+
}
|
|
8040
|
+
const corpus = corpusResult.chunks;
|
|
8041
|
+
const selectedModel = resolveDeepSpaceAgentModel(manifest.assistant.model, "documentation");
|
|
8042
|
+
if (!selectedModel) {
|
|
8043
|
+
console.error(
|
|
8044
|
+
"[documentation-assistant] unsupported configured model:",
|
|
8045
|
+
manifest.assistant.model
|
|
8046
|
+
);
|
|
8047
|
+
return c.json({ error: "Documentation assistant model is not supported" }, 503);
|
|
8048
|
+
}
|
|
8049
|
+
const diagnosticContext = {
|
|
8050
|
+
profile: "documentation",
|
|
8051
|
+
provider: selectedModel.provider,
|
|
8052
|
+
modelId: selectedModel.modelId
|
|
8053
|
+
};
|
|
8054
|
+
let result;
|
|
8055
|
+
try {
|
|
8056
|
+
result = streamDeepSpaceAgent(c.env, {
|
|
8057
|
+
profile: "documentation",
|
|
8058
|
+
modelId: selectedModel.modelId,
|
|
8059
|
+
...authToken ? { authToken } : {},
|
|
8060
|
+
system: buildDocumentationAssistantPrompt(manifest.name || c.env.APP_NAME, route),
|
|
8061
|
+
messages: [...history, { role: "user", content: question }],
|
|
8062
|
+
tools: buildDocumentationAssistantTools(corpus, publicBasePath),
|
|
8063
|
+
abortSignal: c.req.raw.signal,
|
|
8064
|
+
onError: ({ error }) => {
|
|
8065
|
+
console.error(
|
|
8066
|
+
`[documentation-assistant] stream error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
|
|
8067
|
+
);
|
|
8068
|
+
}
|
|
8069
|
+
}).result;
|
|
8070
|
+
} catch (error) {
|
|
8071
|
+
console.error(
|
|
8072
|
+
`[documentation-assistant] agent unavailable: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
|
|
8073
|
+
);
|
|
8074
|
+
return c.json({ error: "Documentation assistant is not configured" }, 503);
|
|
8075
|
+
}
|
|
8076
|
+
return result.toUIMessageStreamResponse({
|
|
8077
|
+
sendReasoning: false,
|
|
8078
|
+
onError: (error) => {
|
|
8079
|
+
console.error(
|
|
8080
|
+
`[documentation-assistant] response error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
|
|
8081
|
+
);
|
|
8082
|
+
return "The documentation assistant could not complete this response.";
|
|
8083
|
+
}
|
|
8084
|
+
});
|
|
8085
|
+
});
|
|
8086
|
+
}
|
|
8087
|
+
function normalizeDocumentationAssistantHistory(value) {
|
|
8088
|
+
if (value === void 0) return [];
|
|
8089
|
+
if (!Array.isArray(value)) throw new TypeError("messages must be an array");
|
|
8090
|
+
if (value.length > MAX_HISTORY_MESSAGES) {
|
|
8091
|
+
throw new TypeError(`messages exceeds ${MAX_HISTORY_MESSAGES} turns`);
|
|
8092
|
+
}
|
|
8093
|
+
const messages = [];
|
|
8094
|
+
let totalChars2 = 0;
|
|
8095
|
+
for (const [index, item] of value.entries()) {
|
|
8096
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
8097
|
+
throw new TypeError(`messages[${index}] must be an object`);
|
|
8098
|
+
}
|
|
8099
|
+
const candidate = item;
|
|
8100
|
+
const expectedRole = index % 2 === 0 ? "user" : "assistant";
|
|
8101
|
+
if (candidate.role !== expectedRole) {
|
|
8102
|
+
throw new TypeError(`messages[${index}].role must be ${expectedRole}`);
|
|
8103
|
+
}
|
|
8104
|
+
const content = typeof candidate.content === "string" ? candidate.content.trim() : "";
|
|
8105
|
+
if (!content) throw new TypeError(`messages[${index}].content is required`);
|
|
8106
|
+
if (content.length > MAX_QUESTION_CHARS) {
|
|
8107
|
+
throw new TypeError(`messages[${index}].content exceeds ${MAX_QUESTION_CHARS} characters`);
|
|
8108
|
+
}
|
|
8109
|
+
totalChars2 += content.length;
|
|
8110
|
+
if (totalChars2 > MAX_HISTORY_CHARS) {
|
|
8111
|
+
throw new TypeError(`messages exceeds ${MAX_HISTORY_CHARS} characters`);
|
|
8112
|
+
}
|
|
8113
|
+
messages.push({ role: expectedRole, content });
|
|
8114
|
+
}
|
|
8115
|
+
if (messages.length % 2 !== 0) {
|
|
8116
|
+
throw new TypeError("messages must end with a completed assistant response");
|
|
8117
|
+
}
|
|
8118
|
+
return messages;
|
|
8119
|
+
}
|
|
8120
|
+
function registerDocumentationStaticRoutes(app, publicBasePath = "/docs", assetBasePath = "/_documentation") {
|
|
8121
|
+
const serve = async (c, next) => {
|
|
8122
|
+
const manifest = await loadDocumentationPublishedManifest(c.env, c.req.url, assetBasePath);
|
|
8123
|
+
if (!manifest) return next();
|
|
8124
|
+
const requestUrl = new URL(c.req.url);
|
|
8125
|
+
const pathname = requestUrl.pathname;
|
|
8126
|
+
const publishedPath = documentationRequestPath(pathname, publicBasePath);
|
|
8127
|
+
let response;
|
|
8128
|
+
let status;
|
|
8129
|
+
if (publishedPath && isPublishedDocumentationRequest(publishedPath, manifest.routes, manifest.resources)) {
|
|
8130
|
+
requestUrl.pathname = documentationAssetPath(publishedPath, manifest.resources, assetBasePath);
|
|
8131
|
+
response = await fetchDocumentationAsset(c.env, c.req.raw, requestUrl, assetBasePath);
|
|
8132
|
+
status = response.status;
|
|
8133
|
+
} else {
|
|
8134
|
+
response = new Response(null, { status: 404 });
|
|
8135
|
+
status = 404;
|
|
8136
|
+
}
|
|
8137
|
+
if (status === 404) {
|
|
8138
|
+
const url = new URL(c.req.url);
|
|
8139
|
+
url.pathname = `${assetBasePath}/404.html`;
|
|
8140
|
+
response = await fetchDocumentationAsset(c.env, c.req.raw, url, assetBasePath);
|
|
8141
|
+
status = 404;
|
|
8142
|
+
}
|
|
8143
|
+
return secureDocumentationResponse(response, status, pathname);
|
|
8144
|
+
};
|
|
8145
|
+
app.get(publicBasePath || "/", serve);
|
|
8146
|
+
app.get(`${publicBasePath}/*`, serve);
|
|
8147
|
+
}
|
|
8148
|
+
function isPublishedDocumentationRequest(publishedPath, routes, resources) {
|
|
8149
|
+
return routes.includes(publishedPath) || resources.includes(publishedPath);
|
|
8150
|
+
}
|
|
8151
|
+
function documentationRequestPath(pathname, publicBasePath) {
|
|
8152
|
+
const logical = pathname === publicBasePath || !publicBasePath && pathname === "/" ? "/" : pathname.slice(publicBasePath.length) || "/";
|
|
8153
|
+
const clean = logical === "/" ? "/" : logical.replace(/\/$/, "");
|
|
8154
|
+
return canonicalNativeDocumentationRequestPath(clean);
|
|
8155
|
+
}
|
|
8156
|
+
async function fetchDocumentationAsset(env, request, initialUrl, assetBasePath) {
|
|
8157
|
+
let url = initialUrl;
|
|
8158
|
+
for (let redirects = 0; redirects < 3; redirects++) {
|
|
8159
|
+
const response = await env.ASSETS.fetch(new Request(url, request));
|
|
8160
|
+
const location = response.headers.get("Location");
|
|
8161
|
+
if (response.status < 300 || response.status >= 400 || !location) return response;
|
|
8162
|
+
const next = new URL(location, url);
|
|
8163
|
+
if (next.origin !== url.origin || !next.pathname.startsWith(`${assetBasePath}/`)) {
|
|
8164
|
+
return new Response("Invalid documentation asset redirect", { status: 502 });
|
|
8165
|
+
}
|
|
8166
|
+
await response.body?.cancel();
|
|
8167
|
+
url = next;
|
|
8168
|
+
}
|
|
8169
|
+
return new Response("Documentation asset redirect loop", { status: 508 });
|
|
8170
|
+
}
|
|
8171
|
+
function documentationAssetPath(publishedPath, resources, assetBasePath) {
|
|
8172
|
+
if (publishedPath === "/") return `${assetBasePath}/index.html`;
|
|
8173
|
+
return resources.includes(publishedPath) ? `${assetBasePath}${publishedPath}` : `${assetBasePath}${publishedPath}/index.html`;
|
|
8174
|
+
}
|
|
8175
|
+
function secureDocumentationResponse(response, status, pathname) {
|
|
8176
|
+
const headers = new Headers(response.headers);
|
|
8177
|
+
if (status !== 404 && pathname.endsWith(".md")) {
|
|
8178
|
+
headers.set("Content-Type", "text/markdown; charset=utf-8");
|
|
8179
|
+
}
|
|
8180
|
+
if (headers.get("Content-Type")?.toLowerCase().includes("text/html")) {
|
|
8181
|
+
const cacheControl = headers.get("Cache-Control") ?? "public, max-age=0, must-revalidate";
|
|
8182
|
+
if (!/(?:^|,)\s*no-transform(?:\s*,|$)/i.test(cacheControl)) {
|
|
8183
|
+
headers.set("Cache-Control", `${cacheControl}, no-transform`);
|
|
8184
|
+
}
|
|
8185
|
+
}
|
|
8186
|
+
headers.set("X-Content-Type-Options", "nosniff");
|
|
8187
|
+
headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
8188
|
+
headers.set(
|
|
8189
|
+
"Content-Security-Policy",
|
|
8190
|
+
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data: https:; connect-src 'self' https:; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'"
|
|
8191
|
+
);
|
|
8192
|
+
return new Response(response.body, { status, headers });
|
|
8193
|
+
}
|
|
8194
|
+
|
|
8195
|
+
// src/documentation/text.ts
|
|
8196
|
+
function documentationSubject(name) {
|
|
8197
|
+
const normalized = name.trim();
|
|
8198
|
+
if (!normalized || /^documentation$/i.test(normalized)) return "documentation";
|
|
8199
|
+
return /(?:\bdocs?|documentation)$/i.test(normalized) ? normalized : `${normalized} documentation`;
|
|
8200
|
+
}
|
|
8201
|
+
|
|
8202
|
+
// src/documentation/worker/mcp/protocol.ts
|
|
8203
|
+
var MCP_SERVER_VERSION = "1.0.0";
|
|
8204
|
+
var CURRENT_PROTOCOL_VERSION = "2025-11-25";
|
|
8205
|
+
var SUPPORTED_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PROTOCOL_VERSION, "2025-06-18", "2025-03-26"]);
|
|
8206
|
+
function initializeResponse(request) {
|
|
8207
|
+
const params = objectValue(request.params);
|
|
8208
|
+
const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : CURRENT_PROTOCOL_VERSION;
|
|
8209
|
+
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : CURRENT_PROTOCOL_VERSION;
|
|
8210
|
+
return mcpResult(request.id, {
|
|
8211
|
+
protocolVersion,
|
|
8212
|
+
capabilities: {
|
|
8213
|
+
tools: { listChanged: false },
|
|
8214
|
+
resources: { subscribe: false, listChanged: false }
|
|
8215
|
+
},
|
|
8216
|
+
serverInfo: {
|
|
8217
|
+
name: "DeepSpace Documentation",
|
|
8218
|
+
title: "DeepSpace documentation",
|
|
8219
|
+
version: MCP_SERVER_VERSION
|
|
8220
|
+
},
|
|
8221
|
+
instructions: "Search the published documentation before answering, then read exact pages when full context is needed."
|
|
8222
|
+
});
|
|
8223
|
+
}
|
|
8224
|
+
function hasValidOrigin(request) {
|
|
8225
|
+
const origin = request.headers.get("Origin");
|
|
8226
|
+
if (!origin) return true;
|
|
8227
|
+
try {
|
|
8228
|
+
return new URL(origin).origin === new URL(request.url).origin;
|
|
8229
|
+
} catch {
|
|
8230
|
+
return false;
|
|
8231
|
+
}
|
|
8232
|
+
}
|
|
8233
|
+
function parseRequest(raw) {
|
|
8234
|
+
let value;
|
|
8235
|
+
try {
|
|
8236
|
+
value = JSON.parse(raw);
|
|
8237
|
+
} catch {
|
|
8238
|
+
return { ok: false, message: "Invalid JSON" };
|
|
8239
|
+
}
|
|
8240
|
+
const candidate = objectValue(value);
|
|
8241
|
+
if (!candidate || candidate.jsonrpc !== "2.0" || typeof candidate.method !== "string") {
|
|
8242
|
+
return { ok: false, message: "Invalid JSON-RPC request" };
|
|
8243
|
+
}
|
|
8244
|
+
if (candidate.id !== void 0 && candidate.id !== null && typeof candidate.id !== "string" && typeof candidate.id !== "number")
|
|
8245
|
+
return { ok: false, message: "Invalid JSON-RPC id" };
|
|
8246
|
+
return {
|
|
8247
|
+
ok: true,
|
|
8248
|
+
request: {
|
|
8249
|
+
jsonrpc: "2.0",
|
|
8250
|
+
method: candidate.method,
|
|
8251
|
+
...candidate.id !== void 0 ? { id: candidate.id } : {},
|
|
8252
|
+
...candidate.params !== void 0 ? { params: candidate.params } : {}
|
|
8253
|
+
}
|
|
8254
|
+
};
|
|
8255
|
+
}
|
|
8256
|
+
function objectValue(value) {
|
|
8257
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8258
|
+
}
|
|
8259
|
+
function toolResult(id, value) {
|
|
8260
|
+
return mcpResult(id, {
|
|
8261
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
8262
|
+
structuredContent: value,
|
|
8263
|
+
isError: false
|
|
8264
|
+
});
|
|
8265
|
+
}
|
|
8266
|
+
function toolError(id, message) {
|
|
8267
|
+
return mcpResult(id, {
|
|
8268
|
+
content: [{ type: "text", text: message }],
|
|
8269
|
+
isError: true
|
|
8270
|
+
});
|
|
8271
|
+
}
|
|
8272
|
+
function mcpResult(id, result) {
|
|
8273
|
+
return jsonResponse({ jsonrpc: "2.0", id, result });
|
|
8274
|
+
}
|
|
8275
|
+
function mcpError(id, code, message, status = 200, headers) {
|
|
8276
|
+
return jsonResponse({ jsonrpc: "2.0", id, error: { code, message } }, status, headers);
|
|
8277
|
+
}
|
|
8278
|
+
function jsonResponse(value, status = 200, headers) {
|
|
8279
|
+
return new Response(JSON.stringify(value), {
|
|
8280
|
+
status,
|
|
8281
|
+
headers: {
|
|
8282
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
8283
|
+
...mcpSecurityHeaders(),
|
|
8284
|
+
...headers
|
|
8285
|
+
}
|
|
8286
|
+
});
|
|
8287
|
+
}
|
|
8288
|
+
function mcpSecurityHeaders() {
|
|
8289
|
+
return {
|
|
8290
|
+
"Cache-Control": "no-store",
|
|
8291
|
+
"X-Content-Type-Options": "nosniff",
|
|
8292
|
+
"Referrer-Policy": "no-referrer"
|
|
8293
|
+
};
|
|
8294
|
+
}
|
|
8295
|
+
|
|
8296
|
+
// src/documentation/worker/mcp/tools.ts
|
|
8297
|
+
var DOCUMENTATION_TOOLS = [
|
|
8298
|
+
{
|
|
8299
|
+
name: "documentation_search",
|
|
8300
|
+
title: "Search documentation",
|
|
8301
|
+
description: "Search the published documentation and return ranked, linked excerpts.",
|
|
8302
|
+
inputSchema: {
|
|
8303
|
+
type: "object",
|
|
8304
|
+
properties: {
|
|
8305
|
+
query: { type: "string", minLength: 2, maxLength: 300 },
|
|
8306
|
+
limit: { type: "integer", minimum: 1, maximum: 10, default: 6 }
|
|
8307
|
+
},
|
|
8308
|
+
required: ["query"],
|
|
8309
|
+
additionalProperties: false
|
|
8310
|
+
},
|
|
8311
|
+
outputSchema: {
|
|
8312
|
+
type: "object",
|
|
8313
|
+
properties: { results: { type: "array" } },
|
|
8314
|
+
required: ["results"]
|
|
8315
|
+
},
|
|
8316
|
+
annotations: {
|
|
8317
|
+
readOnlyHint: true,
|
|
8318
|
+
destructiveHint: false,
|
|
8319
|
+
idempotentHint: true,
|
|
8320
|
+
openWorldHint: false
|
|
8321
|
+
}
|
|
8322
|
+
},
|
|
8323
|
+
{
|
|
8324
|
+
name: "documentation_read",
|
|
8325
|
+
title: "Read documentation page",
|
|
8326
|
+
description: "Read one complete published documentation page as Markdown.",
|
|
8327
|
+
inputSchema: {
|
|
8328
|
+
type: "object",
|
|
8329
|
+
properties: {
|
|
8330
|
+
route: { type: "string", minLength: 1, maxLength: 500, pattern: "^/" }
|
|
8331
|
+
},
|
|
8332
|
+
required: ["route"],
|
|
8333
|
+
additionalProperties: false
|
|
8334
|
+
},
|
|
8335
|
+
outputSchema: {
|
|
8336
|
+
type: "object",
|
|
8337
|
+
properties: {
|
|
8338
|
+
route: { type: "string" },
|
|
8339
|
+
title: { type: "string" },
|
|
8340
|
+
url: { type: "string" },
|
|
8341
|
+
markdown: { type: "string" }
|
|
8342
|
+
},
|
|
8343
|
+
required: ["route", "title", "url", "markdown"]
|
|
8344
|
+
},
|
|
8345
|
+
annotations: {
|
|
8346
|
+
readOnlyHint: true,
|
|
8347
|
+
destructiveHint: false,
|
|
8348
|
+
idempotentHint: true,
|
|
8349
|
+
openWorldHint: false
|
|
8350
|
+
}
|
|
8351
|
+
}
|
|
8352
|
+
];
|
|
8353
|
+
async function callDocumentationTool(rawRequest, request, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
|
|
8354
|
+
const params = objectValue(request.params);
|
|
8355
|
+
const name = typeof params?.name === "string" ? params.name : "";
|
|
8356
|
+
const args = objectValue(params?.arguments) ?? {};
|
|
8357
|
+
const origin = new URL(rawRequest.url).origin;
|
|
8358
|
+
if (name === "documentation_search") {
|
|
8359
|
+
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
8360
|
+
const limit = args.limit === void 0 ? 6 : args.limit;
|
|
8361
|
+
if (query.length < 2 || query.length > 300 || !Number.isInteger(limit) || Number(limit) < 1 || Number(limit) > 10) {
|
|
8362
|
+
return mcpError(request.id, -32602, "Invalid documentation_search arguments");
|
|
8363
|
+
}
|
|
8364
|
+
const value = {
|
|
8365
|
+
results: searchDocumentationCorpus(corpus, query, Number(limit)).map((result) => ({
|
|
8366
|
+
...result,
|
|
8367
|
+
url: new URL(documentationPublicPath(basePath, result.route), origin).toString()
|
|
8368
|
+
}))
|
|
8369
|
+
};
|
|
8370
|
+
return toolResult(request.id, value);
|
|
8371
|
+
}
|
|
8372
|
+
if (name === "documentation_read") {
|
|
8373
|
+
const route = typeof args.route === "string" ? args.route.trim() : "";
|
|
8374
|
+
if (!route.startsWith("/") || route.length > 500) {
|
|
8375
|
+
return mcpError(request.id, -32602, "Invalid documentation_read arguments");
|
|
8376
|
+
}
|
|
8377
|
+
try {
|
|
8378
|
+
const page = await readDocumentationPublishedPage(
|
|
8379
|
+
env,
|
|
8380
|
+
corpus,
|
|
8381
|
+
route,
|
|
8382
|
+
rawRequest.url,
|
|
8383
|
+
assetBasePath
|
|
8384
|
+
);
|
|
8385
|
+
if (!page) return toolError(request.id, `No published documentation page exists at ${route}`);
|
|
8386
|
+
return toolResult(request.id, {
|
|
8387
|
+
...page,
|
|
8388
|
+
url: new URL(documentationPublicPath(basePath, page.route), origin).toString()
|
|
8389
|
+
});
|
|
8390
|
+
} catch (error) {
|
|
8391
|
+
console.error("[documentation-mcp] page unavailable:", error);
|
|
8392
|
+
return toolError(request.id, "The documentation page could not be read");
|
|
8393
|
+
}
|
|
8394
|
+
}
|
|
8395
|
+
return mcpError(request.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
|
|
8396
|
+
}
|
|
8397
|
+
|
|
8398
|
+
// src/documentation/worker/mcp-routes.ts
|
|
8399
|
+
var MAX_REQUEST_CHARS = 64e3;
|
|
8400
|
+
var MAX_REQUEST_BYTES2 = MAX_REQUEST_CHARS * 4;
|
|
8401
|
+
var MAX_SKILL_CHARS = 256e3;
|
|
8402
|
+
var DOCUMENTATION_BASE_PATH = "/docs";
|
|
8403
|
+
function registerDocumentationMcpRoutes(app, publicBasePath = DOCUMENTATION_BASE_PATH, assetBasePath = "/_documentation") {
|
|
8404
|
+
const mcpPath = documentationPublicPath(publicBasePath, "/mcp");
|
|
8405
|
+
app.get(mcpPath, async (c) => {
|
|
8406
|
+
if (!await publicManifest(c.req.raw, c.env, assetBasePath)) return c.notFound();
|
|
8407
|
+
return new Response(null, {
|
|
8408
|
+
status: 405,
|
|
8409
|
+
headers: { Allow: "POST", ...mcpSecurityHeaders() }
|
|
8410
|
+
});
|
|
8411
|
+
});
|
|
8412
|
+
app.post(mcpPath, async (c) => {
|
|
8413
|
+
const manifest = await publicManifest(c.req.raw, c.env, assetBasePath);
|
|
8414
|
+
if (!manifest) return c.notFound();
|
|
8415
|
+
if (!hasValidOrigin(c.req.raw)) {
|
|
8416
|
+
return mcpError(null, -32e3, "Invalid Origin", 403);
|
|
8417
|
+
}
|
|
8418
|
+
let raw;
|
|
8419
|
+
try {
|
|
8420
|
+
raw = await readBoundedRequestText(c.req.raw, MAX_REQUEST_BYTES2);
|
|
8421
|
+
} catch (error) {
|
|
8422
|
+
if (error instanceof RequestBodyTooLargeError) {
|
|
8423
|
+
return mcpError(null, -32600, `Request exceeds ${MAX_REQUEST_BYTES2} bytes`, 413);
|
|
8424
|
+
}
|
|
8425
|
+
return mcpError(null, -32700, "Unable to read request body", 400);
|
|
8426
|
+
}
|
|
8427
|
+
if (raw.length > MAX_REQUEST_CHARS) {
|
|
8428
|
+
return mcpError(null, -32600, `Request exceeds ${MAX_REQUEST_CHARS} characters`, 413);
|
|
8429
|
+
}
|
|
8430
|
+
const parsed = parseRequest(raw);
|
|
8431
|
+
if (!parsed.ok) return mcpError(null, -32700, parsed.message, 400);
|
|
8432
|
+
const request = parsed.request;
|
|
8433
|
+
if (request.method !== "initialize") {
|
|
8434
|
+
const protocolVersion = c.req.header("MCP-Protocol-Version");
|
|
8435
|
+
if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
|
|
8436
|
+
return mcpError(request.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
|
|
8437
|
+
}
|
|
8438
|
+
}
|
|
8439
|
+
const requestId = request.id;
|
|
8440
|
+
if (requestId === void 0) return new Response(null, { status: 202 });
|
|
8441
|
+
const requestWithId = { ...request, id: requestId };
|
|
8442
|
+
if (request.method === "initialize") return initializeResponse(requestWithId);
|
|
8443
|
+
if (request.method === "ping") return mcpResult(requestId, {});
|
|
8444
|
+
if (request.method === "tools/list") {
|
|
8445
|
+
return mcpResult(requestId, { tools: DOCUMENTATION_TOOLS });
|
|
8446
|
+
}
|
|
8447
|
+
if (request.method === "resources/list") {
|
|
8448
|
+
return mcpResult(requestId, {
|
|
8449
|
+
resources: [skillResource(c.req.raw, manifest, publicBasePath)]
|
|
8450
|
+
});
|
|
8451
|
+
}
|
|
8452
|
+
if (request.method === "resources/read") {
|
|
8453
|
+
return readSkillResource(
|
|
8454
|
+
c.req.raw,
|
|
8455
|
+
requestWithId,
|
|
8456
|
+
manifest,
|
|
8457
|
+
c.env,
|
|
8458
|
+
publicBasePath,
|
|
8459
|
+
assetBasePath
|
|
8460
|
+
);
|
|
8461
|
+
}
|
|
8462
|
+
if (request.method !== "tools/call") {
|
|
8463
|
+
return mcpError(requestId, -32601, `Method not found: ${request.method}`);
|
|
8464
|
+
}
|
|
8465
|
+
const clientKey = await documentationPublicClientKey(
|
|
8466
|
+
c.req.header("CF-Connecting-IP") ?? "unknown"
|
|
8467
|
+
);
|
|
8468
|
+
const limitResult = await checkDocumentationPublicLimit(c.env, clientKey, "documentation-mcp");
|
|
8469
|
+
if (!limitResult.ok) {
|
|
8470
|
+
return mcpError(
|
|
8471
|
+
requestId,
|
|
8472
|
+
limitResult.status === 429 ? -32029 : -32002,
|
|
8473
|
+
limitResult.status === 429 ? "Documentation tool rate limit exceeded. Try again shortly." : "Documentation tools are temporarily unavailable",
|
|
8474
|
+
limitResult.status,
|
|
8475
|
+
limitResult.status === 429 ? { "Retry-After": "60" } : void 0
|
|
8476
|
+
);
|
|
8477
|
+
}
|
|
8478
|
+
const corpusResult = await resolveDocumentationPublishedCorpus(
|
|
8479
|
+
c.env,
|
|
8480
|
+
manifest.sourceHash,
|
|
8481
|
+
c.req.url,
|
|
8482
|
+
assetBasePath
|
|
8483
|
+
);
|
|
8484
|
+
if (!corpusResult.ok) {
|
|
8485
|
+
console.error("[documentation-mcp] corpus unavailable:", corpusResult.error);
|
|
8486
|
+
return mcpError(requestId, -32002, "Documentation corpus is unavailable", 503);
|
|
8487
|
+
}
|
|
8488
|
+
const corpus = corpusResult.chunks;
|
|
8489
|
+
return callDocumentationTool(
|
|
8490
|
+
c.req.raw,
|
|
8491
|
+
requestWithId,
|
|
8492
|
+
corpus,
|
|
8493
|
+
c.env,
|
|
8494
|
+
publicBasePath,
|
|
8495
|
+
assetBasePath
|
|
8496
|
+
);
|
|
8497
|
+
});
|
|
8498
|
+
for (const path of [
|
|
8499
|
+
"/.well-known/mcp",
|
|
8500
|
+
"/.well-known/mcp.json",
|
|
8501
|
+
"/.well-known/mcp/server-card.json"
|
|
8502
|
+
]) {
|
|
8503
|
+
app.get(documentationPublicPath(publicBasePath, path), async (c) => {
|
|
8504
|
+
const manifest = await publicManifest(c.req.raw, c.env, assetBasePath);
|
|
8505
|
+
if (!manifest) return c.notFound();
|
|
8506
|
+
return discoveryResponse(c.req.raw, manifest, publicBasePath);
|
|
8507
|
+
});
|
|
8508
|
+
}
|
|
8509
|
+
app.get(
|
|
8510
|
+
documentationPublicPath(publicBasePath, "/.well-known/mcp/server-cards.json"),
|
|
8511
|
+
async (c) => {
|
|
8512
|
+
const manifest = await publicManifest(c.req.raw, c.env, assetBasePath);
|
|
8513
|
+
if (!manifest) return c.notFound();
|
|
8514
|
+
return jsonResponse({ servers: [serverCard(c.req.raw, manifest, publicBasePath)] }, 200, {
|
|
8515
|
+
"Cache-Control": "public, max-age=300"
|
|
8516
|
+
});
|
|
8517
|
+
}
|
|
8518
|
+
);
|
|
8519
|
+
}
|
|
8520
|
+
async function readSkillResource(rawRequest, request, manifest, env, publicBasePath, assetBasePath) {
|
|
8521
|
+
const params = objectValue(request.params);
|
|
8522
|
+
const requestedUri = typeof params?.uri === "string" ? params.uri : "";
|
|
8523
|
+
const resource = skillResource(rawRequest, manifest, publicBasePath);
|
|
8524
|
+
if (requestedUri !== resource.uri) {
|
|
8525
|
+
return mcpError(request.id, -32602, "Unknown documentation resource");
|
|
8526
|
+
}
|
|
8527
|
+
const assetUrl = new URL(`${assetBasePath}/skill.md`, rawRequest.url);
|
|
8528
|
+
const response = await env.ASSETS.fetch(new Request(assetUrl, { method: "GET" }));
|
|
8529
|
+
if (!response.ok) return mcpError(request.id, -32002, "Documentation skill is unavailable", 503);
|
|
8530
|
+
const text = await response.text();
|
|
8531
|
+
if (text.length > MAX_SKILL_CHARS) {
|
|
8532
|
+
return mcpError(request.id, -32002, "Documentation skill exceeds the published size limit", 503);
|
|
8533
|
+
}
|
|
8534
|
+
return mcpResult(request.id, {
|
|
8535
|
+
contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
|
|
8536
|
+
});
|
|
8537
|
+
}
|
|
8538
|
+
function skillResource(request, manifest, publicBasePath) {
|
|
8539
|
+
const subject = documentationSubject(manifest.name);
|
|
8540
|
+
return {
|
|
8541
|
+
uri: new URL(`${publicBasePath}/skill.md`, request.url).toString(),
|
|
8542
|
+
name: `${subject} skill`,
|
|
8543
|
+
title: `Use ${manifest.name}`,
|
|
8544
|
+
description: `Agent instructions grounded in the published ${subject}.`,
|
|
8545
|
+
mimeType: "text/markdown"
|
|
8546
|
+
};
|
|
8547
|
+
}
|
|
8548
|
+
async function publicManifest(request, env, assetBasePath) {
|
|
8549
|
+
const manifest = await loadDocumentationPublishedManifest(env, request.url, assetBasePath);
|
|
8550
|
+
return manifest?.mcp.access === "public" ? manifest : null;
|
|
8551
|
+
}
|
|
8552
|
+
function discoveryResponse(request, manifest, publicBasePath) {
|
|
8553
|
+
return jsonResponse(serverCard(request, manifest, publicBasePath), 200, {
|
|
8554
|
+
"Cache-Control": "public, max-age=300"
|
|
8555
|
+
});
|
|
8556
|
+
}
|
|
8557
|
+
function serverCard(request, manifest, publicBasePath) {
|
|
8558
|
+
return {
|
|
8559
|
+
name: manifest.name,
|
|
8560
|
+
description: `Search and read the published ${documentationSubject(manifest.name)}.`,
|
|
8561
|
+
version: MCP_SERVER_VERSION,
|
|
8562
|
+
protocolVersion: CURRENT_PROTOCOL_VERSION,
|
|
8563
|
+
transport: {
|
|
8564
|
+
type: "streamable-http",
|
|
8565
|
+
url: new URL(`${publicBasePath}/mcp`, request.url).toString()
|
|
8566
|
+
},
|
|
8567
|
+
authentication: { required: false },
|
|
8568
|
+
capabilities: {
|
|
8569
|
+
tools: DOCUMENTATION_TOOLS.map(({ name, title, description }) => ({
|
|
8570
|
+
name,
|
|
8571
|
+
title,
|
|
8572
|
+
description
|
|
8573
|
+
})),
|
|
8574
|
+
resources: [skillResource(request, manifest, publicBasePath)]
|
|
8575
|
+
}
|
|
8576
|
+
};
|
|
8577
|
+
}
|
|
8578
|
+
|
|
8579
|
+
// src/documentation/worker/routes.ts
|
|
8580
|
+
function registerDeepSpaceDocumentation(app, options) {
|
|
8581
|
+
app.use(
|
|
8582
|
+
"*",
|
|
8583
|
+
async (c, next) => isPrivateDocumentationPath(new URL(c.req.url).pathname) ? c.notFound() : next()
|
|
8584
|
+
);
|
|
8585
|
+
const domains = new Set(
|
|
8586
|
+
options.config?.domains?.map((domain) => domain.trim().toLowerCase()) ?? []
|
|
8587
|
+
);
|
|
8588
|
+
if (domains.size) {
|
|
8589
|
+
const root = new Hono();
|
|
8590
|
+
registerDocumentationAssistantRoutes(root, options.resolveAuth, "", "/_documentation-root");
|
|
8591
|
+
registerDocumentationMcpRoutes(root, "", "/_documentation-root");
|
|
8592
|
+
registerDocumentationStaticRoutes(root, "", "/_documentation-root");
|
|
8593
|
+
app.all("*", (c, next) => {
|
|
8594
|
+
const url = new URL(c.req.url);
|
|
8595
|
+
if (!domains.has(url.hostname.toLowerCase()) || url.pathname === "/api/auth" || url.pathname.startsWith("/api/auth/"))
|
|
8596
|
+
return next();
|
|
8597
|
+
return root.fetch(c.req.raw, c.env);
|
|
8598
|
+
});
|
|
8599
|
+
}
|
|
8600
|
+
registerDocumentationAssistantRoutes(app, options.resolveAuth);
|
|
8601
|
+
registerDocumentationMcpRoutes(app);
|
|
8602
|
+
registerDocumentationStaticRoutes(app);
|
|
8603
|
+
}
|
|
8604
|
+
function isPrivateDocumentationPath(pathname) {
|
|
8605
|
+
let decoded = pathname;
|
|
8606
|
+
for (let round = 0; round < 3; round += 1) {
|
|
8607
|
+
const next = decoded.replace(
|
|
8608
|
+
/%([0-9a-f]{2})/giu,
|
|
8609
|
+
(_sequence, hex) => String.fromCharCode(Number.parseInt(hex, 16))
|
|
8610
|
+
);
|
|
8611
|
+
if (next === decoded) break;
|
|
8612
|
+
decoded = next;
|
|
8613
|
+
}
|
|
8614
|
+
return /^\/_documentation(?:-root)?(?:\/|$)/u.test(decoded.replace(/\/{2,}/gu, "/"));
|
|
8615
|
+
}
|
|
8616
|
+
|
|
7210
8617
|
// src/server/auth/jwtVerifier.ts
|
|
7211
8618
|
import { importSPKI, jwtVerify } from "jose";
|
|
7212
8619
|
|
|
@@ -7581,7 +8988,7 @@ function shouldThrottleClientError(now = Date.now()) {
|
|
|
7581
8988
|
windowCount += 1;
|
|
7582
8989
|
return windowCount > THROTTLE_MAX;
|
|
7583
8990
|
}
|
|
7584
|
-
function
|
|
8991
|
+
function jsonResponse2(body, status) {
|
|
7585
8992
|
return new Response(JSON.stringify(body), {
|
|
7586
8993
|
status,
|
|
7587
8994
|
headers: { "Content-Type": "application/json" }
|
|
@@ -7597,16 +9004,16 @@ async function handleClientErrorReport(request) {
|
|
|
7597
9004
|
}
|
|
7598
9005
|
const declared = Number(request.headers.get("content-length"));
|
|
7599
9006
|
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
7600
|
-
return
|
|
9007
|
+
return jsonResponse2({ error: "payload_too_large" }, 413);
|
|
7601
9008
|
}
|
|
7602
9009
|
let body;
|
|
7603
9010
|
try {
|
|
7604
9011
|
body = await request.json();
|
|
7605
9012
|
} catch {
|
|
7606
|
-
return
|
|
9013
|
+
return jsonResponse2({ error: "invalid_json" }, 400);
|
|
7607
9014
|
}
|
|
7608
9015
|
const report = normalizeClientErrorReport(body);
|
|
7609
|
-
if (!report) return
|
|
9016
|
+
if (!report) return jsonResponse2({ error: "invalid_report" }, 400);
|
|
7610
9017
|
console.error(clientErrorToLogLine(report));
|
|
7611
9018
|
return new Response(null, { status: 204 });
|
|
7612
9019
|
}
|
|
@@ -7649,11 +9056,17 @@ export {
|
|
|
7649
9056
|
COST_RATES,
|
|
7650
9057
|
CanvasRoom,
|
|
7651
9058
|
CronRoom,
|
|
9059
|
+
DEEPSPACE_AGENT_PROFILES,
|
|
9060
|
+
DEEPSPACE_AI_DEFAULTS,
|
|
9061
|
+
DEEPSPACE_AI_MODELS,
|
|
9062
|
+
DEEPSPACE_MODEL_CATALOG_PROVENANCE,
|
|
7652
9063
|
DEFAULT_CONTEXT_CONFIG,
|
|
7653
9064
|
DEFAULT_DO_MANIFEST,
|
|
7654
9065
|
DEFAULT_QUERY_LIMIT,
|
|
7655
9066
|
DEPLOYED_APPS_DEFAULTS,
|
|
7656
9067
|
DIRECTORY_SCHEMAS,
|
|
9068
|
+
DeepSpaceAgentModelError,
|
|
9069
|
+
DeepSpaceAgentProfileError,
|
|
7657
9070
|
GLOBAL_DO_TYPES,
|
|
7658
9071
|
GLOBAL_DO_TYPE_NAMES,
|
|
7659
9072
|
GameRoom,
|
|
@@ -7722,6 +9135,7 @@ export {
|
|
|
7722
9135
|
createScopedR2Handler,
|
|
7723
9136
|
dataToColumnValues,
|
|
7724
9137
|
decodeJwtPayload,
|
|
9138
|
+
deepSpaceAgentErrorSummary,
|
|
7725
9139
|
deleteChatCascade,
|
|
7726
9140
|
dispatch,
|
|
7727
9141
|
encode,
|
|
@@ -7731,6 +9145,7 @@ export {
|
|
|
7731
9145
|
encodeUpdate,
|
|
7732
9146
|
enqueueJob,
|
|
7733
9147
|
getChat,
|
|
9148
|
+
getDeepSpaceAIModel,
|
|
7734
9149
|
getGlobalDOSchemas,
|
|
7735
9150
|
getGlobalDOType,
|
|
7736
9151
|
getMessageType,
|
|
@@ -7745,6 +9160,7 @@ export {
|
|
|
7745
9160
|
isTestAccountEmail,
|
|
7746
9161
|
isTestAccountTier,
|
|
7747
9162
|
lintSchema,
|
|
9163
|
+
listDeepSpaceAgentModels,
|
|
7748
9164
|
loadMessages,
|
|
7749
9165
|
makeDefaultSummarizer,
|
|
7750
9166
|
matchOrigin,
|
|
@@ -7760,13 +9176,16 @@ export {
|
|
|
7760
9176
|
readVarUint,
|
|
7761
9177
|
readVarUint8Array,
|
|
7762
9178
|
registerClientErrorRoute,
|
|
9179
|
+
registerDeepSpaceDocumentation,
|
|
7763
9180
|
registryErrorJson,
|
|
7764
9181
|
registryStub,
|
|
7765
9182
|
resolveAppName,
|
|
7766
9183
|
resolveColumn,
|
|
9184
|
+
resolveDeepSpaceAgentModel,
|
|
7767
9185
|
rowToData,
|
|
7768
9186
|
runMigrations,
|
|
7769
9187
|
serverBuild,
|
|
9188
|
+
streamDeepSpaceAgent,
|
|
7770
9189
|
timingSafeEqualHex,
|
|
7771
9190
|
timingSafeEqualStrings,
|
|
7772
9191
|
toUint8Array,
|