deepspace 0.9.2 → 0.10.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/CHANGELOG.md +31 -0
- package/dist/chunk-DQ4G6VL6.js +1535 -0
- package/dist/chunk-DQ4G6VL6.js.map +1 -0
- package/dist/cli.js +6009 -398
- package/dist/cli.js.map +1 -1
- package/dist/docs-client-core.d.ts +18 -0
- package/dist/docs-client-core.js +386 -0
- package/dist/docs-client-core.js.map +1 -0
- package/dist/docs-react.d.ts +2 -0
- package/dist/docs-react.js +35 -0
- package/dist/docs-react.js.map +1 -0
- package/dist/docs-runtime.js +71 -0
- package/dist/docs-server-core.d.ts +11 -0
- package/dist/docs-server-core.js +48 -0
- package/dist/docs-server-core.js.map +1 -0
- package/dist/docs.d.ts +350 -0
- package/dist/docs.js +5139 -0
- package/dist/docs.js.map +1 -0
- package/dist/index.d.ts +179 -1
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -1
- package/dist/public-Cp1X8ZQ3.d.ts +230 -0
- package/dist/server.d.ts +240 -13
- package/dist/server.js +1313 -25
- package/dist/server.js.map +1 -1
- package/dist/worker.d.ts +378 -13
- package/dist/worker.js +1377 -29
- package/dist/worker.js.map +1 -1
- package/features/ai-chat/feature.json +1 -1
- package/features/ai-chat/src/ChatPanel.tsx +7 -11
- package/package.json +21 -1
package/dist/worker.js
CHANGED
|
@@ -340,6 +340,16 @@ var ROLE_ANONYMOUS = "viewer";
|
|
|
340
340
|
var ROLE_DEFAULT = "member";
|
|
341
341
|
var ROLE_ADMIN = "admin";
|
|
342
342
|
|
|
343
|
+
// src/shared/users-schema.ts
|
|
344
|
+
var USERS_COLUMNS = [
|
|
345
|
+
{ name: "email", storage: "text", interpretation: "plain" },
|
|
346
|
+
{ name: "name", storage: "text", interpretation: "plain" },
|
|
347
|
+
{ name: "imageUrl", storage: "text", interpretation: "plain" },
|
|
348
|
+
{ name: "role", storage: "text", interpretation: "plain" },
|
|
349
|
+
{ name: "createdAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
350
|
+
{ name: "lastSeenAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
351
|
+
];
|
|
352
|
+
|
|
343
353
|
// src/server/schemas/registry.ts
|
|
344
354
|
function collectionTableName(name) {
|
|
345
355
|
return `c_${name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
|
|
@@ -540,14 +550,6 @@ var SYSTEM_MANAGED_COLUMNS = /* @__PURE__ */ new Set([
|
|
|
540
550
|
"createdAt",
|
|
541
551
|
"lastSeenAt"
|
|
542
552
|
]);
|
|
543
|
-
var USERS_COLUMNS = [
|
|
544
|
-
{ name: "email", storage: "text", interpretation: "plain" },
|
|
545
|
-
{ name: "name", storage: "text", interpretation: "plain" },
|
|
546
|
-
{ name: "imageUrl", storage: "text", interpretation: "plain" },
|
|
547
|
-
{ name: "role", storage: "text", interpretation: "plain" },
|
|
548
|
-
{ name: "createdAt", storage: "text", interpretation: { kind: "datetime" } },
|
|
549
|
-
{ name: "lastSeenAt", storage: "text", interpretation: { kind: "datetime" } }
|
|
550
|
-
];
|
|
551
553
|
var BASE_USERS_SCHEMA = {
|
|
552
554
|
name: "users",
|
|
553
555
|
columns: [...USERS_COLUMNS],
|
|
@@ -2174,11 +2176,11 @@ async function handleToolsRequest(ctx, request, path) {
|
|
|
2174
2176
|
}
|
|
2175
2177
|
if (path.startsWith("tools/describe/")) {
|
|
2176
2178
|
const toolName = path.replace("tools/describe/", "");
|
|
2177
|
-
const
|
|
2178
|
-
if (!
|
|
2179
|
+
const tool3 = BUILT_IN_TOOLS.find((t) => t.name === toolName);
|
|
2180
|
+
if (!tool3) {
|
|
2179
2181
|
return Response.json({ error: `Tool not found: ${toolName}` }, { status: 404, headers: CORS_HEADERS });
|
|
2180
2182
|
}
|
|
2181
|
-
return Response.json({ tool:
|
|
2183
|
+
return Response.json({ tool: tool3 }, { headers: CORS_HEADERS });
|
|
2182
2184
|
}
|
|
2183
2185
|
if (path.startsWith("tools/execute") && request.method === "POST") {
|
|
2184
2186
|
return handleToolExecute(ctx, request);
|
|
@@ -2195,8 +2197,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
2195
2197
|
{ status: 400, headers: CORS_HEADERS }
|
|
2196
2198
|
);
|
|
2197
2199
|
}
|
|
2198
|
-
const { tool:
|
|
2199
|
-
if (!
|
|
2200
|
+
const { tool: tool3, params = {} } = body;
|
|
2201
|
+
if (!tool3) {
|
|
2200
2202
|
return Response.json(
|
|
2201
2203
|
{ success: false, error: 'Missing "tool" in request body' },
|
|
2202
2204
|
{ status: 400, headers: CORS_HEADERS }
|
|
@@ -2213,8 +2215,8 @@ async function handleToolExecute(ctx, request) {
|
|
|
2213
2215
|
userRole = user.role;
|
|
2214
2216
|
}
|
|
2215
2217
|
}
|
|
2216
|
-
if (
|
|
2217
|
-
const result2 = executeYjsTool(ctx,
|
|
2218
|
+
if (tool3.startsWith("yjs.")) {
|
|
2219
|
+
const result2 = executeYjsTool(ctx, tool3, params, userId, userRole);
|
|
2218
2220
|
const status2 = result2.success ? 200 : result2.error?.includes("not found") ? 404 : 400;
|
|
2219
2221
|
return Response.json(result2, { status: status2, headers: CORS_HEADERS });
|
|
2220
2222
|
}
|
|
@@ -2225,7 +2227,7 @@ async function handleToolExecute(ctx, request) {
|
|
|
2225
2227
|
getPermissionContext: ctx.getPermissionContext,
|
|
2226
2228
|
send: ctx.send
|
|
2227
2229
|
};
|
|
2228
|
-
const result = await executeTool(recordCtx,
|
|
2230
|
+
const result = await executeTool(recordCtx, tool3, params, userId, userRole, isAppAction);
|
|
2229
2231
|
const status = result.success ? 200 : result.error?.includes("not found") ? 404 : 400;
|
|
2230
2232
|
return Response.json(result, { status, headers: CORS_HEADERS });
|
|
2231
2233
|
}
|
|
@@ -6022,6 +6024,170 @@ function validateAppMigrationIds(value) {
|
|
|
6022
6024
|
return { valid: true, ids };
|
|
6023
6025
|
}
|
|
6024
6026
|
|
|
6027
|
+
// src/shared/ai-models.ts
|
|
6028
|
+
var DEEPSPACE_MODEL_CATALOG_PROVENANCE = {
|
|
6029
|
+
version: "2026-08-03",
|
|
6030
|
+
verifiedAt: "2026-08-03",
|
|
6031
|
+
sources: {
|
|
6032
|
+
anthropic: "https://platform.claude.com/docs/en/api/beta/models/list",
|
|
6033
|
+
openai: "https://developers.openai.com/api/docs/models/all",
|
|
6034
|
+
cerebras: "https://inference-docs.cerebras.ai/models/overview"
|
|
6035
|
+
}
|
|
6036
|
+
};
|
|
6037
|
+
var MULTI_STEP_AGENT_PROFILES = ["application", "documentation"];
|
|
6038
|
+
function createMultiStepModelFactory(provider) {
|
|
6039
|
+
return (model) => ({
|
|
6040
|
+
...model,
|
|
6041
|
+
...provider,
|
|
6042
|
+
agentSupport: "multi-step",
|
|
6043
|
+
agentProfiles: MULTI_STEP_AGENT_PROFILES
|
|
6044
|
+
});
|
|
6045
|
+
}
|
|
6046
|
+
var anthropicModel = createMultiStepModelFactory({
|
|
6047
|
+
provider: "anthropic",
|
|
6048
|
+
providerLabel: "Anthropic",
|
|
6049
|
+
transport: "messages"
|
|
6050
|
+
});
|
|
6051
|
+
var openAIModel = createMultiStepModelFactory({
|
|
6052
|
+
provider: "openai",
|
|
6053
|
+
providerLabel: "OpenAI",
|
|
6054
|
+
transport: "chat-completions"
|
|
6055
|
+
});
|
|
6056
|
+
var DEEPSPACE_AI_MODELS = [
|
|
6057
|
+
anthropicModel({
|
|
6058
|
+
id: "claude-fable-5",
|
|
6059
|
+
label: "Claude Fable 5",
|
|
6060
|
+
family: "Claude 5",
|
|
6061
|
+
recommendation: "frontier"
|
|
6062
|
+
}),
|
|
6063
|
+
anthropicModel({
|
|
6064
|
+
id: "claude-opus-5",
|
|
6065
|
+
label: "Claude Opus 5",
|
|
6066
|
+
family: "Claude 5",
|
|
6067
|
+
recommendation: "frontier"
|
|
6068
|
+
}),
|
|
6069
|
+
anthropicModel({
|
|
6070
|
+
id: "claude-sonnet-5",
|
|
6071
|
+
label: "Claude Sonnet 5",
|
|
6072
|
+
family: "Claude 5",
|
|
6073
|
+
recommendation: "balanced"
|
|
6074
|
+
}),
|
|
6075
|
+
anthropicModel({
|
|
6076
|
+
id: "claude-haiku-4-5",
|
|
6077
|
+
label: "Claude Haiku 4.5",
|
|
6078
|
+
family: "Claude 4.5",
|
|
6079
|
+
recommendation: "fast"
|
|
6080
|
+
}),
|
|
6081
|
+
openAIModel({
|
|
6082
|
+
id: "gpt-5.6-sol",
|
|
6083
|
+
label: "GPT-5.6 Sol",
|
|
6084
|
+
family: "GPT-5.6",
|
|
6085
|
+
recommendation: "frontier"
|
|
6086
|
+
}),
|
|
6087
|
+
openAIModel({
|
|
6088
|
+
id: "gpt-5.6-terra",
|
|
6089
|
+
label: "GPT-5.6 Terra",
|
|
6090
|
+
family: "GPT-5.6",
|
|
6091
|
+
recommendation: "balanced"
|
|
6092
|
+
}),
|
|
6093
|
+
openAIModel({
|
|
6094
|
+
id: "gpt-5.6-luna",
|
|
6095
|
+
label: "GPT-5.6 Luna",
|
|
6096
|
+
family: "GPT-5.6",
|
|
6097
|
+
recommendation: "fast"
|
|
6098
|
+
}),
|
|
6099
|
+
{
|
|
6100
|
+
id: "gpt-oss-120b",
|
|
6101
|
+
label: "GPT-OSS 120B",
|
|
6102
|
+
provider: "cerebras",
|
|
6103
|
+
providerLabel: "Cerebras",
|
|
6104
|
+
family: "GPT-OSS",
|
|
6105
|
+
agentSupport: "single-step",
|
|
6106
|
+
agentProfiles: [],
|
|
6107
|
+
transport: "chat-completions",
|
|
6108
|
+
recommendation: "limited",
|
|
6109
|
+
note: "Available for direct generation; the current proxy adapter has not passed multi-step tool-result continuation."
|
|
6110
|
+
}
|
|
6111
|
+
];
|
|
6112
|
+
var DEEPSPACE_AI_DEFAULTS = {
|
|
6113
|
+
agent: "claude-sonnet-5",
|
|
6114
|
+
directGeneration: "claude-sonnet-5",
|
|
6115
|
+
summarization: "claude-haiku-4-5"
|
|
6116
|
+
};
|
|
6117
|
+
var DEEPSPACE_AGENT_PROFILES = {
|
|
6118
|
+
application: {
|
|
6119
|
+
id: "application",
|
|
6120
|
+
defaultModel: DEEPSPACE_AI_DEFAULTS.agent,
|
|
6121
|
+
maxSteps: 5,
|
|
6122
|
+
allowedTools: "application-defined"
|
|
6123
|
+
},
|
|
6124
|
+
documentation: {
|
|
6125
|
+
id: "documentation",
|
|
6126
|
+
defaultModel: DEEPSPACE_AI_DEFAULTS.agent,
|
|
6127
|
+
// Permit up to twenty completed read-only tools plus one final synthesis
|
|
6128
|
+
// step. The runner disables tools once the tool-call budget is exhausted.
|
|
6129
|
+
maxSteps: 21,
|
|
6130
|
+
maxToolCalls: 20,
|
|
6131
|
+
allowedTools: ["docs_search", "docs_read"]
|
|
6132
|
+
}
|
|
6133
|
+
};
|
|
6134
|
+
function getDeepSpaceAIModel(modelId) {
|
|
6135
|
+
if (typeof modelId !== "string") return null;
|
|
6136
|
+
return DEEPSPACE_AI_MODELS.find((model) => model.id === modelId) ?? null;
|
|
6137
|
+
}
|
|
6138
|
+
function listDeepSpaceAgentModels(profileId = "application") {
|
|
6139
|
+
const defaultModel = DEEPSPACE_AGENT_PROFILES[profileId].defaultModel;
|
|
6140
|
+
return DEEPSPACE_AI_MODELS.filter(
|
|
6141
|
+
(model) => model.agentProfiles.includes(profileId)
|
|
6142
|
+
).sort((left, right) => Number(right.id === defaultModel) - Number(left.id === defaultModel));
|
|
6143
|
+
}
|
|
6144
|
+
function resolveDeepSpaceAgentModel(modelId, profileId = "application") {
|
|
6145
|
+
const profile = DEEPSPACE_AGENT_PROFILES[profileId];
|
|
6146
|
+
const resolvedId = modelId === void 0 ? profile.defaultModel : modelId;
|
|
6147
|
+
const model = getDeepSpaceAIModel(resolvedId);
|
|
6148
|
+
if (!model || !model.agentProfiles.includes(profileId)) return null;
|
|
6149
|
+
return { modelId: model.id, provider: model.provider, model, profile };
|
|
6150
|
+
}
|
|
6151
|
+
|
|
6152
|
+
// src/shared/docs-routing.ts
|
|
6153
|
+
var DOCS_RESOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
6154
|
+
"7z",
|
|
6155
|
+
"avif",
|
|
6156
|
+
"css",
|
|
6157
|
+
"csv",
|
|
6158
|
+
"gif",
|
|
6159
|
+
"gz",
|
|
6160
|
+
"ico",
|
|
6161
|
+
"jpeg",
|
|
6162
|
+
"jpg",
|
|
6163
|
+
"js",
|
|
6164
|
+
"json",
|
|
6165
|
+
"map",
|
|
6166
|
+
"md",
|
|
6167
|
+
"mjs",
|
|
6168
|
+
"mp3",
|
|
6169
|
+
"mp4",
|
|
6170
|
+
"pdf",
|
|
6171
|
+
"png",
|
|
6172
|
+
"svg",
|
|
6173
|
+
"tar",
|
|
6174
|
+
"txt",
|
|
6175
|
+
"wasm",
|
|
6176
|
+
"webm",
|
|
6177
|
+
"webp",
|
|
6178
|
+
"woff",
|
|
6179
|
+
"woff2",
|
|
6180
|
+
"xml",
|
|
6181
|
+
"yaml",
|
|
6182
|
+
"yml",
|
|
6183
|
+
"zip"
|
|
6184
|
+
]);
|
|
6185
|
+
function isDocsResourcePath(pathname) {
|
|
6186
|
+
const leaf = pathname.slice(pathname.lastIndexOf("/") + 1);
|
|
6187
|
+
const extension = leaf.includes(".") ? leaf.slice(leaf.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
6188
|
+
return extension !== "" && DOCS_RESOURCE_EXTENSIONS.has(extension);
|
|
6189
|
+
}
|
|
6190
|
+
|
|
6025
6191
|
// src/server/utils/cors.ts
|
|
6026
6192
|
function matchOrigin(origin, patterns) {
|
|
6027
6193
|
if (!origin) return null;
|
|
@@ -6393,7 +6559,7 @@ function authWorkerFetch(env, path, init) {
|
|
|
6393
6559
|
function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
6394
6560
|
const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
|
|
6395
6561
|
const room = env.RECORD_ROOMS.get(roomIdObj);
|
|
6396
|
-
async function executeTool3(
|
|
6562
|
+
async function executeTool3(tool3, params) {
|
|
6397
6563
|
const response = await room.fetch(
|
|
6398
6564
|
new Request("https://internal/api/tools/execute", {
|
|
6399
6565
|
method: "POST",
|
|
@@ -6402,12 +6568,12 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
|
|
|
6402
6568
|
"X-User-Id": ownerUserId,
|
|
6403
6569
|
"X-App-Action": "true"
|
|
6404
6570
|
},
|
|
6405
|
-
body: JSON.stringify({ tool:
|
|
6571
|
+
body: JSON.stringify({ tool: tool3, params })
|
|
6406
6572
|
})
|
|
6407
6573
|
);
|
|
6408
6574
|
const result = await response.json();
|
|
6409
6575
|
if (!result.success) {
|
|
6410
|
-
throw new Error(`RecordRoom tool ${
|
|
6576
|
+
throw new Error(`RecordRoom tool ${tool3} failed: ${result.error || "Unknown error"}`);
|
|
6411
6577
|
}
|
|
6412
6578
|
return result.data;
|
|
6413
6579
|
}
|
|
@@ -6529,6 +6695,158 @@ function createDeepSpaceAI(env, provider, options = {}) {
|
|
|
6529
6695
|
}
|
|
6530
6696
|
}
|
|
6531
6697
|
|
|
6698
|
+
// src/server/utils/agent.ts
|
|
6699
|
+
import {
|
|
6700
|
+
stepCountIs,
|
|
6701
|
+
streamText
|
|
6702
|
+
} from "ai";
|
|
6703
|
+
var DeepSpaceAgentModelError = class extends Error {
|
|
6704
|
+
constructor(modelId, profile) {
|
|
6705
|
+
super(
|
|
6706
|
+
`Model ${typeof modelId === "string" ? JSON.stringify(modelId) : String(modelId)} is not compatible with the ${profile} agent profile`
|
|
6707
|
+
);
|
|
6708
|
+
this.modelId = modelId;
|
|
6709
|
+
this.profile = profile;
|
|
6710
|
+
this.name = "DeepSpaceAgentModelError";
|
|
6711
|
+
}
|
|
6712
|
+
code = "unsupported_agent_model";
|
|
6713
|
+
};
|
|
6714
|
+
var DeepSpaceAgentProfileError = class extends Error {
|
|
6715
|
+
constructor(profile, toolName) {
|
|
6716
|
+
super(`The ${profile} agent profile does not allow tool ${JSON.stringify(toolName)}`);
|
|
6717
|
+
this.profile = profile;
|
|
6718
|
+
this.toolName = toolName;
|
|
6719
|
+
this.name = "DeepSpaceAgentProfileError";
|
|
6720
|
+
}
|
|
6721
|
+
code = "agent_profile_violation";
|
|
6722
|
+
};
|
|
6723
|
+
function streamDeepSpaceAgent(env, options) {
|
|
6724
|
+
const { profile, modelId, authToken, ...streamOptions } = options;
|
|
6725
|
+
const selection = resolveDeepSpaceAgentModel(modelId, profile);
|
|
6726
|
+
if (!selection) throw new DeepSpaceAgentModelError(modelId, profile);
|
|
6727
|
+
if (selection.profile.allowedTools !== "application-defined") {
|
|
6728
|
+
const allowed = selection.profile.allowedTools;
|
|
6729
|
+
for (const toolName of Object.keys(streamOptions.tools ?? {})) {
|
|
6730
|
+
if (!allowed.includes(toolName)) {
|
|
6731
|
+
throw new DeepSpaceAgentProfileError(profile, toolName);
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
6734
|
+
}
|
|
6735
|
+
const provider = createDeepSpaceAI(
|
|
6736
|
+
env,
|
|
6737
|
+
selection.provider,
|
|
6738
|
+
authToken ? { authToken } : {}
|
|
6739
|
+
);
|
|
6740
|
+
const { prepareStep, tools, ...remainingStreamOptions } = streamOptions;
|
|
6741
|
+
const boundedTools = limitToolExecutions(tools, selection.profile.maxToolCalls);
|
|
6742
|
+
const result = streamText({
|
|
6743
|
+
...remainingStreamOptions,
|
|
6744
|
+
...boundedTools ? { tools: boundedTools } : {},
|
|
6745
|
+
model: provider(selection.modelId),
|
|
6746
|
+
stopWhen: stepCountIs(selection.profile.maxSteps),
|
|
6747
|
+
prepareStep: enforceToolCallLimit(prepareStep, selection.profile.maxToolCalls),
|
|
6748
|
+
...selection.provider === "openai" ? { providerOptions: { openai: { reasoningEffort: "none" } } } : {}
|
|
6749
|
+
});
|
|
6750
|
+
return { selection, result };
|
|
6751
|
+
}
|
|
6752
|
+
function enforceToolCallLimit(prepareStep, maxToolCalls) {
|
|
6753
|
+
if (maxToolCalls === void 0) return prepareStep;
|
|
6754
|
+
return async (context) => {
|
|
6755
|
+
const prepared = await prepareStep?.(context);
|
|
6756
|
+
const completedToolCalls = context.steps.reduce(
|
|
6757
|
+
(count, step) => count + step.toolCalls.length,
|
|
6758
|
+
0
|
|
6759
|
+
);
|
|
6760
|
+
if (completedToolCalls < maxToolCalls) return prepared;
|
|
6761
|
+
return { ...prepared, activeTools: [], toolChoice: "none" };
|
|
6762
|
+
};
|
|
6763
|
+
}
|
|
6764
|
+
function limitToolExecutions(tools, maxToolCalls) {
|
|
6765
|
+
if (!tools || maxToolCalls === void 0) return tools;
|
|
6766
|
+
let executions = 0;
|
|
6767
|
+
return Object.fromEntries(Object.entries(tools).map(([name, definition]) => {
|
|
6768
|
+
if (typeof definition.execute !== "function") return [name, definition];
|
|
6769
|
+
const execute = definition.execute;
|
|
6770
|
+
return [name, {
|
|
6771
|
+
...definition,
|
|
6772
|
+
execute: (...args) => {
|
|
6773
|
+
if (executions >= maxToolCalls) {
|
|
6774
|
+
return Promise.resolve({
|
|
6775
|
+
error: `The agent tool-call limit of ${maxToolCalls} has been reached`
|
|
6776
|
+
});
|
|
6777
|
+
}
|
|
6778
|
+
executions++;
|
|
6779
|
+
return execute(...args);
|
|
6780
|
+
}
|
|
6781
|
+
}];
|
|
6782
|
+
}));
|
|
6783
|
+
}
|
|
6784
|
+
function deepSpaceAgentErrorSummary(error, context = {}) {
|
|
6785
|
+
const details = [];
|
|
6786
|
+
const contextFields = [
|
|
6787
|
+
context.profile ? `profile=${context.profile}` : void 0,
|
|
6788
|
+
context.provider ? `provider=${safeLogToken(context.provider)}` : void 0,
|
|
6789
|
+
context.modelId ? `model=${safeLogToken(context.modelId)}` : void 0
|
|
6790
|
+
].filter((part) => Boolean(part));
|
|
6791
|
+
if (contextFields.length > 0) details.push(contextFields.join(" "));
|
|
6792
|
+
const pending = [error];
|
|
6793
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6794
|
+
while (pending.length > 0 && details.length < 8) {
|
|
6795
|
+
const current = pending.shift();
|
|
6796
|
+
if (current == null || visited.has(current)) continue;
|
|
6797
|
+
visited.add(current);
|
|
6798
|
+
if (typeof current === "object") {
|
|
6799
|
+
const record = current;
|
|
6800
|
+
const parts = [
|
|
6801
|
+
typeof record.name === "string" ? `name=${safeLogToken(record.name)}` : current instanceof Error ? `name=${safeLogToken(current.name)}` : void 0,
|
|
6802
|
+
safeErrorMessage(current, record),
|
|
6803
|
+
typeof record.statusCode === "number" ? `status=${record.statusCode}` : void 0,
|
|
6804
|
+
typeof record.code === "string" ? `code=${safeLogToken(record.code)}` : void 0,
|
|
6805
|
+
typeof record.requestId === "string" ? `requestId=${safeLogToken(record.requestId)}` : void 0,
|
|
6806
|
+
...safeProviderResponseMetadata(record.responseBody)
|
|
6807
|
+
].filter((part) => Boolean(part));
|
|
6808
|
+
details.push(parts.join(" ") || Object.prototype.toString.call(current));
|
|
6809
|
+
if (record.lastError != null) pending.push(record.lastError);
|
|
6810
|
+
if (record.cause != null) pending.push(record.cause);
|
|
6811
|
+
if (Array.isArray(record.errors)) pending.push(...record.errors);
|
|
6812
|
+
continue;
|
|
6813
|
+
}
|
|
6814
|
+
details.push(String(current));
|
|
6815
|
+
}
|
|
6816
|
+
return details.join(" <- ") || "Unknown error";
|
|
6817
|
+
}
|
|
6818
|
+
function safeErrorMessage(current, record) {
|
|
6819
|
+
if (record.responseBody !== void 0) return void 0;
|
|
6820
|
+
const message = current instanceof Error ? current.message : record.message;
|
|
6821
|
+
if (typeof message !== "string" || !message.trim()) return void 0;
|
|
6822
|
+
return `message=${safeLogText(message)}`;
|
|
6823
|
+
}
|
|
6824
|
+
function safeProviderResponseMetadata(responseBody) {
|
|
6825
|
+
if (typeof responseBody !== "string") return [];
|
|
6826
|
+
try {
|
|
6827
|
+
const parsed = JSON.parse(responseBody);
|
|
6828
|
+
const error = parsed.error && typeof parsed.error === "object" ? parsed.error : void 0;
|
|
6829
|
+
const code = typeof error?.code === "string" ? error.code : typeof error?.type === "string" ? error.type : void 0;
|
|
6830
|
+
const requestId = typeof parsed.request_id === "string" ? parsed.request_id : void 0;
|
|
6831
|
+
return [
|
|
6832
|
+
code ? `upstreamCode=${safeLogToken(code)}` : void 0,
|
|
6833
|
+
requestId ? `upstreamRequestId=${safeLogToken(requestId)}` : void 0
|
|
6834
|
+
].filter((part) => Boolean(part));
|
|
6835
|
+
} catch {
|
|
6836
|
+
return [];
|
|
6837
|
+
}
|
|
6838
|
+
}
|
|
6839
|
+
function safeLogToken(value) {
|
|
6840
|
+
return value.replace(/[^a-zA-Z0-9._:/-]/g, "_").slice(0, 120);
|
|
6841
|
+
}
|
|
6842
|
+
function safeLogText(value) {
|
|
6843
|
+
const withoutControlCharacters = Array.from(value, (character) => {
|
|
6844
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
6845
|
+
return codePoint <= 31 || codePoint === 127 ? " " : character;
|
|
6846
|
+
}).join("");
|
|
6847
|
+
return withoutControlCharacters.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]").replace(/\b(api[_ -]?key|authorization|password|secret|token)\b\s*[:=]\s*\S+/gi, "$1=[redacted]").trim().slice(0, 300);
|
|
6848
|
+
}
|
|
6849
|
+
|
|
6532
6850
|
// src/server/utils/composio-tools.ts
|
|
6533
6851
|
import { tool, jsonSchema } from "ai";
|
|
6534
6852
|
var EMPTY_SCHEMA = { type: "object", properties: {} };
|
|
@@ -6965,10 +7283,11 @@ function formatTranscript(messages) {
|
|
|
6965
7283
|
}
|
|
6966
7284
|
function makeDefaultSummarizer(env, options = {}) {
|
|
6967
7285
|
return async (messages) => {
|
|
6968
|
-
const
|
|
7286
|
+
const model = getDeepSpaceAIModel(DEEPSPACE_AI_DEFAULTS.summarization);
|
|
7287
|
+
if (!model) throw new Error("DeepSpace summarization model is missing from the catalog");
|
|
7288
|
+
const provider = createDeepSpaceAI(env, model.provider, { authToken: options.authToken });
|
|
6969
7289
|
const { text } = await generateText({
|
|
6970
|
-
|
|
6971
|
-
model: anthropic("claude-haiku-4-5"),
|
|
7290
|
+
model: provider(model.id),
|
|
6972
7291
|
// 2500 gives the model room to finish a sentence after hitting the
|
|
6973
7292
|
// "under 2000 tokens" soft limit in the prompt.
|
|
6974
7293
|
maxOutputTokens: 2500,
|
|
@@ -6980,7 +7299,7 @@ function makeDefaultSummarizer(env, options = {}) {
|
|
|
6980
7299
|
}
|
|
6981
7300
|
|
|
6982
7301
|
// src/server/utils/chat-history.ts
|
|
6983
|
-
async function executeTool2(stub, userId,
|
|
7302
|
+
async function executeTool2(stub, userId, tool3, params) {
|
|
6984
7303
|
const res = await stub.fetch(new Request("https://internal/api/tools/execute", {
|
|
6985
7304
|
method: "POST",
|
|
6986
7305
|
headers: {
|
|
@@ -6988,11 +7307,11 @@ async function executeTool2(stub, userId, tool2, params) {
|
|
|
6988
7307
|
"X-User-Id": userId,
|
|
6989
7308
|
"X-App-Action": "true"
|
|
6990
7309
|
},
|
|
6991
|
-
body: JSON.stringify({ tool:
|
|
7310
|
+
body: JSON.stringify({ tool: tool3, params })
|
|
6992
7311
|
}));
|
|
6993
7312
|
const body = await res.json();
|
|
6994
7313
|
if (!body.success) {
|
|
6995
|
-
throw new Error(`chat-history ${
|
|
7314
|
+
throw new Error(`chat-history ${tool3} failed: ${body.error ?? "unknown error"}`);
|
|
6996
7315
|
}
|
|
6997
7316
|
return body.data;
|
|
6998
7317
|
}
|
|
@@ -7104,6 +7423,1002 @@ async function appendMessage(stub, msg) {
|
|
|
7104
7423
|
});
|
|
7105
7424
|
}
|
|
7106
7425
|
|
|
7426
|
+
// src/server/utils/docs-assistant.ts
|
|
7427
|
+
import { tool as tool2 } from "ai";
|
|
7428
|
+
import { z } from "zod/v4";
|
|
7429
|
+
var MAX_CHUNKS = 1e4;
|
|
7430
|
+
var MAX_CHUNK_CHARS = 2500;
|
|
7431
|
+
var MAX_CORPUS_CHARS = 12e6;
|
|
7432
|
+
var SEARCH_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
7433
|
+
"a",
|
|
7434
|
+
"about",
|
|
7435
|
+
"an",
|
|
7436
|
+
"and",
|
|
7437
|
+
"are",
|
|
7438
|
+
"can",
|
|
7439
|
+
"could",
|
|
7440
|
+
"do",
|
|
7441
|
+
"does",
|
|
7442
|
+
"for",
|
|
7443
|
+
"from",
|
|
7444
|
+
"has",
|
|
7445
|
+
"have",
|
|
7446
|
+
"how",
|
|
7447
|
+
"i",
|
|
7448
|
+
"in",
|
|
7449
|
+
"is",
|
|
7450
|
+
"it",
|
|
7451
|
+
"me",
|
|
7452
|
+
"my",
|
|
7453
|
+
"of",
|
|
7454
|
+
"on",
|
|
7455
|
+
"or",
|
|
7456
|
+
"our",
|
|
7457
|
+
"should",
|
|
7458
|
+
"the",
|
|
7459
|
+
"this",
|
|
7460
|
+
"to",
|
|
7461
|
+
"was",
|
|
7462
|
+
"we",
|
|
7463
|
+
"were",
|
|
7464
|
+
"what",
|
|
7465
|
+
"when",
|
|
7466
|
+
"where",
|
|
7467
|
+
"which",
|
|
7468
|
+
"will",
|
|
7469
|
+
"with",
|
|
7470
|
+
"would",
|
|
7471
|
+
"you",
|
|
7472
|
+
"your"
|
|
7473
|
+
]);
|
|
7474
|
+
var indexedChunkCache = /* @__PURE__ */ new WeakMap();
|
|
7475
|
+
function parseDocsCorpus(value) {
|
|
7476
|
+
if (!Array.isArray(value)) throw new Error("Docs assistant index must be an array");
|
|
7477
|
+
if (value.length > MAX_CHUNKS) {
|
|
7478
|
+
throw new Error(`Docs assistant index exceeds ${MAX_CHUNKS} chunks`);
|
|
7479
|
+
}
|
|
7480
|
+
const chunks = [];
|
|
7481
|
+
let totalChars2 = 0;
|
|
7482
|
+
for (let index = 0; index < value.length; index++) {
|
|
7483
|
+
const raw = value[index];
|
|
7484
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
7485
|
+
throw new Error(`Docs assistant chunk ${index} must be an object`);
|
|
7486
|
+
}
|
|
7487
|
+
const chunk = raw;
|
|
7488
|
+
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) {
|
|
7489
|
+
throw new Error(`Docs assistant chunk ${index} has invalid required fields`);
|
|
7490
|
+
}
|
|
7491
|
+
if (chunk.text.length > MAX_CHUNK_CHARS) {
|
|
7492
|
+
throw new Error(`Docs assistant chunk ${chunk.id} exceeds ${MAX_CHUNK_CHARS} characters`);
|
|
7493
|
+
}
|
|
7494
|
+
totalChars2 += chunk.text.length;
|
|
7495
|
+
if (totalChars2 > MAX_CORPUS_CHARS) {
|
|
7496
|
+
throw new Error(`Docs assistant corpus exceeds ${MAX_CORPUS_CHARS} characters`);
|
|
7497
|
+
}
|
|
7498
|
+
chunks.push({
|
|
7499
|
+
id: chunk.id,
|
|
7500
|
+
route: chunk.route,
|
|
7501
|
+
title: chunk.title,
|
|
7502
|
+
...typeof chunk.heading === "string" && chunk.heading ? { heading: chunk.heading } : {},
|
|
7503
|
+
text: chunk.text
|
|
7504
|
+
});
|
|
7505
|
+
}
|
|
7506
|
+
return chunks;
|
|
7507
|
+
}
|
|
7508
|
+
function searchDocsCorpus(corpus, query, limit = 6) {
|
|
7509
|
+
const normalized = normalize(query);
|
|
7510
|
+
const terms = [
|
|
7511
|
+
...new Set(
|
|
7512
|
+
normalized.split(" ").filter((term) => term.length > 1 && !SEARCH_STOP_WORDS.has(term))
|
|
7513
|
+
)
|
|
7514
|
+
];
|
|
7515
|
+
if (!terms.length) return [];
|
|
7516
|
+
const requiredTerms = new Set(terms);
|
|
7517
|
+
return corpus.map((chunk) => {
|
|
7518
|
+
const { text, textTerms, title, titleTerms } = indexChunk(chunk);
|
|
7519
|
+
const textCounts = countTerms(textTerms, requiredTerms);
|
|
7520
|
+
let score = 0;
|
|
7521
|
+
if (title === normalized) score += 120;
|
|
7522
|
+
else if (title.includes(normalized)) score += 55;
|
|
7523
|
+
if (text.includes(normalized)) score += 30;
|
|
7524
|
+
for (const term of terms) {
|
|
7525
|
+
if (titleTerms.has(term)) score += 14;
|
|
7526
|
+
score += Math.min(6, textCounts.get(term) ?? 0) * 3;
|
|
7527
|
+
}
|
|
7528
|
+
const matchedTerms = terms.filter(
|
|
7529
|
+
(term) => titleTerms.has(term) || textCounts.has(term)
|
|
7530
|
+
).length;
|
|
7531
|
+
score += matchedTerms * matchedTerms * 4;
|
|
7532
|
+
score += proximityBonus([...titleTerms, ...textTerms], terms);
|
|
7533
|
+
return {
|
|
7534
|
+
route: chunk.route,
|
|
7535
|
+
title: chunk.title,
|
|
7536
|
+
...chunk.heading ? { heading: chunk.heading } : {},
|
|
7537
|
+
excerpt: excerpt(chunk.text, terms),
|
|
7538
|
+
score
|
|
7539
|
+
};
|
|
7540
|
+
}).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)));
|
|
7541
|
+
}
|
|
7542
|
+
function readDocsRoute(corpus, route) {
|
|
7543
|
+
const normalizedRoute = normalizeDocsRoute(route);
|
|
7544
|
+
return corpus.filter((chunk) => chunk.route === normalizedRoute).slice(0, 12).map(({ route: chunkRoute, title, heading, text }) => ({
|
|
7545
|
+
route: chunkRoute,
|
|
7546
|
+
title,
|
|
7547
|
+
...heading ? { heading } : {},
|
|
7548
|
+
text
|
|
7549
|
+
}));
|
|
7550
|
+
}
|
|
7551
|
+
function buildDocsAssistantTools(corpus) {
|
|
7552
|
+
return {
|
|
7553
|
+
docs_search: tool2({
|
|
7554
|
+
description: "Search the current documentation. Use this before answering factual or how-to questions.",
|
|
7555
|
+
inputSchema: z.object({
|
|
7556
|
+
query: z.string().trim().min(2).max(300),
|
|
7557
|
+
limit: z.number().int().min(1).max(10).optional()
|
|
7558
|
+
}),
|
|
7559
|
+
execute: async ({ query, limit }) => ({
|
|
7560
|
+
results: searchDocsCorpus(corpus, query, limit ?? 6)
|
|
7561
|
+
})
|
|
7562
|
+
}),
|
|
7563
|
+
docs_read: tool2({
|
|
7564
|
+
description: "Read bounded source chunks for one exact documentation route returned by docs_search.",
|
|
7565
|
+
inputSchema: z.object({ route: z.string().trim().startsWith("/").max(500) }),
|
|
7566
|
+
execute: async ({ route }) => ({
|
|
7567
|
+
route: normalizeDocsRoute(route),
|
|
7568
|
+
chunks: readDocsRoute(corpus, route)
|
|
7569
|
+
})
|
|
7570
|
+
})
|
|
7571
|
+
};
|
|
7572
|
+
}
|
|
7573
|
+
function buildDocsAssistantPrompt(appName, currentRoute) {
|
|
7574
|
+
return [
|
|
7575
|
+
`You are the public documentation assistant for "${appName}" on DeepSpace.`,
|
|
7576
|
+
"Answer only from the documentation available through docs_search and docs_read.",
|
|
7577
|
+
"Search before answering unless the user is only greeting you.",
|
|
7578
|
+
"Use docs_read when search excerpts are insufficient or ambiguous.",
|
|
7579
|
+
"Never claim access to application records, integrations, private data, or mutation tools.",
|
|
7580
|
+
"If the documentation does not support an answer, say so plainly and suggest the closest documented page.",
|
|
7581
|
+
"Cite factual answers with Markdown links to the exact documentation routes returned by tools.",
|
|
7582
|
+
"Do not invent URLs, APIs, configuration keys, versions, or behavior.",
|
|
7583
|
+
"Keep answers concise, practical, and explicit about uncertainty.",
|
|
7584
|
+
...currentRoute ? [`The reader is currently viewing ${normalizeDocsRoute(currentRoute)}.`] : []
|
|
7585
|
+
].join("\n");
|
|
7586
|
+
}
|
|
7587
|
+
function normalize(value) {
|
|
7588
|
+
return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter(Boolean).map(canonicalSearchTerm).join(" ");
|
|
7589
|
+
}
|
|
7590
|
+
function canonicalSearchTerm(term) {
|
|
7591
|
+
if (SEARCH_STOP_WORDS.has(term)) return term;
|
|
7592
|
+
if (["doc", "docs", "document", "documents", "documentation"].includes(term)) return "docs";
|
|
7593
|
+
if (["builds", "building", "built", "compile", "compiled", "compiles", "compiling"].includes(term)) {
|
|
7594
|
+
return "build";
|
|
7595
|
+
}
|
|
7596
|
+
return term.length > 3 && term.endsWith("s") && !term.endsWith("ss") ? term.slice(0, -1) : term;
|
|
7597
|
+
}
|
|
7598
|
+
function indexChunk(chunk) {
|
|
7599
|
+
const cached = indexedChunkCache.get(chunk);
|
|
7600
|
+
if (cached) return cached;
|
|
7601
|
+
const title = normalize(`${chunk.title} ${chunk.heading ?? ""}`);
|
|
7602
|
+
const text = normalize(chunk.text);
|
|
7603
|
+
const indexed = {
|
|
7604
|
+
text,
|
|
7605
|
+
textTerms: text.split(" "),
|
|
7606
|
+
title,
|
|
7607
|
+
titleTerms: new Set(title.split(" "))
|
|
7608
|
+
};
|
|
7609
|
+
indexedChunkCache.set(chunk, indexed);
|
|
7610
|
+
return indexed;
|
|
7611
|
+
}
|
|
7612
|
+
function normalizeDocsRoute(value) {
|
|
7613
|
+
let route = `/${value}`.replace(/\/{2,}/g, "/");
|
|
7614
|
+
if (route.length > 1) route = route.replace(/\/$/, "");
|
|
7615
|
+
return route;
|
|
7616
|
+
}
|
|
7617
|
+
function countTerms(tokens, required) {
|
|
7618
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7619
|
+
for (const token of tokens) {
|
|
7620
|
+
if (required.has(token)) counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
7621
|
+
}
|
|
7622
|
+
return counts;
|
|
7623
|
+
}
|
|
7624
|
+
function proximityBonus(tokens, terms) {
|
|
7625
|
+
const required = new Set(terms);
|
|
7626
|
+
if (required.size < 2) return 0;
|
|
7627
|
+
const counts = /* @__PURE__ */ new Map();
|
|
7628
|
+
let matched = 0;
|
|
7629
|
+
let start = 0;
|
|
7630
|
+
let smallestWindow = Number.POSITIVE_INFINITY;
|
|
7631
|
+
for (let end = 0; end < tokens.length; end++) {
|
|
7632
|
+
const token = tokens[end] ?? "";
|
|
7633
|
+
if (required.has(token)) {
|
|
7634
|
+
const count = counts.get(token) ?? 0;
|
|
7635
|
+
counts.set(token, count + 1);
|
|
7636
|
+
if (count === 0) matched++;
|
|
7637
|
+
}
|
|
7638
|
+
while (matched === required.size && start <= end) {
|
|
7639
|
+
smallestWindow = Math.min(smallestWindow, end - start + 1);
|
|
7640
|
+
const first = tokens[start] ?? "";
|
|
7641
|
+
if (required.has(first)) {
|
|
7642
|
+
const count = counts.get(first) ?? 0;
|
|
7643
|
+
counts.set(first, count - 1);
|
|
7644
|
+
if (count === 1) matched--;
|
|
7645
|
+
}
|
|
7646
|
+
start++;
|
|
7647
|
+
}
|
|
7648
|
+
}
|
|
7649
|
+
return Number.isFinite(smallestWindow) ? Math.max(0, 42 - smallestWindow * 2) : 0;
|
|
7650
|
+
}
|
|
7651
|
+
function excerpt(text, terms) {
|
|
7652
|
+
const lower = text.toLowerCase();
|
|
7653
|
+
const first = terms.map((term) => lower.indexOf(term)).filter((index) => index >= 0).sort((left, right) => left - right)[0] ?? 0;
|
|
7654
|
+
const start = Math.max(0, first - 160);
|
|
7655
|
+
const end = Math.min(text.length, start + 520);
|
|
7656
|
+
return `${start > 0 ? "\u2026" : ""}${text.slice(start, end).trim()}${end < text.length ? "\u2026" : ""}`;
|
|
7657
|
+
}
|
|
7658
|
+
|
|
7659
|
+
// src/server/utils/docs-assistant-limiter.ts
|
|
7660
|
+
var WINDOW_MS = 6e4;
|
|
7661
|
+
var CLIENT_REQUESTS_PER_WINDOW = 12;
|
|
7662
|
+
var APP_REQUESTS_PER_WINDOW = 120;
|
|
7663
|
+
var MAX_CONCURRENT_REQUESTS = 4;
|
|
7664
|
+
var LEASE_TTL_MS = 2 * 6e4;
|
|
7665
|
+
var DocsAssistantLimiter = class {
|
|
7666
|
+
sql;
|
|
7667
|
+
constructor(state) {
|
|
7668
|
+
this.sql = state.storage.sql;
|
|
7669
|
+
this.sql.exec(`
|
|
7670
|
+
CREATE TABLE IF NOT EXISTS request_windows (
|
|
7671
|
+
scope TEXT NOT NULL,
|
|
7672
|
+
client_key TEXT NOT NULL,
|
|
7673
|
+
window INTEGER NOT NULL,
|
|
7674
|
+
count INTEGER NOT NULL,
|
|
7675
|
+
PRIMARY KEY (scope, client_key, window)
|
|
7676
|
+
);
|
|
7677
|
+
CREATE TABLE IF NOT EXISTS leases (
|
|
7678
|
+
id TEXT PRIMARY KEY,
|
|
7679
|
+
expires_at INTEGER NOT NULL
|
|
7680
|
+
);
|
|
7681
|
+
`);
|
|
7682
|
+
}
|
|
7683
|
+
async fetch(request) {
|
|
7684
|
+
const url = new URL(request.url);
|
|
7685
|
+
if (request.method !== "POST") return Response.json({ error: "method_not_allowed" }, { status: 405 });
|
|
7686
|
+
if (url.pathname === "/acquire") return this.acquire(request);
|
|
7687
|
+
if (url.pathname === "/release") return this.release(request);
|
|
7688
|
+
return Response.json({ error: "not_found" }, { status: 404 });
|
|
7689
|
+
}
|
|
7690
|
+
async acquire(request) {
|
|
7691
|
+
const body = await request.json().catch(() => null);
|
|
7692
|
+
if (!body || typeof body.clientKey !== "string" || body.clientKey.length !== 64) {
|
|
7693
|
+
return Response.json({ error: "invalid_client_key" }, { status: 400 });
|
|
7694
|
+
}
|
|
7695
|
+
const now = Date.now();
|
|
7696
|
+
const window = Math.floor(now / WINDOW_MS);
|
|
7697
|
+
this.sql.exec("DELETE FROM request_windows WHERE window < ?", window - 1);
|
|
7698
|
+
this.sql.exec("DELETE FROM leases WHERE expires_at <= ?", now);
|
|
7699
|
+
if (!this.consumeWindow("client", body.clientKey, window, CLIENT_REQUESTS_PER_WINDOW)) {
|
|
7700
|
+
return Response.json({ ok: false, reason: "client_rate" }, { status: 429 });
|
|
7701
|
+
}
|
|
7702
|
+
if (!this.consumeWindow("app", "all", window, APP_REQUESTS_PER_WINDOW)) {
|
|
7703
|
+
return Response.json({ ok: false, reason: "app_rate" }, { status: 429 });
|
|
7704
|
+
}
|
|
7705
|
+
const concurrent = this.sql.exec("SELECT COUNT(*) AS count FROM leases").one().count;
|
|
7706
|
+
if (concurrent >= MAX_CONCURRENT_REQUESTS) {
|
|
7707
|
+
return Response.json({ ok: false, reason: "app_concurrency" }, { status: 429 });
|
|
7708
|
+
}
|
|
7709
|
+
const leaseId = crypto.randomUUID();
|
|
7710
|
+
this.sql.exec("INSERT INTO leases (id, expires_at) VALUES (?, ?)", leaseId, now + LEASE_TTL_MS);
|
|
7711
|
+
return Response.json({ ok: true, leaseId });
|
|
7712
|
+
}
|
|
7713
|
+
async release(request) {
|
|
7714
|
+
const body = await request.json().catch(() => null);
|
|
7715
|
+
if (!body || typeof body.leaseId !== "string") {
|
|
7716
|
+
return Response.json({ error: "invalid_lease_id" }, { status: 400 });
|
|
7717
|
+
}
|
|
7718
|
+
this.sql.exec("DELETE FROM leases WHERE id = ?", body.leaseId);
|
|
7719
|
+
return Response.json({ ok: true });
|
|
7720
|
+
}
|
|
7721
|
+
consumeWindow(scope, clientKey, window, limit) {
|
|
7722
|
+
const existing = this.sql.exec(
|
|
7723
|
+
"SELECT count FROM request_windows WHERE scope = ? AND client_key = ? AND window = ?",
|
|
7724
|
+
scope,
|
|
7725
|
+
clientKey,
|
|
7726
|
+
window
|
|
7727
|
+
).toArray()[0]?.count ?? 0;
|
|
7728
|
+
if (existing >= limit) return false;
|
|
7729
|
+
this.sql.exec(
|
|
7730
|
+
`INSERT INTO request_windows (scope, client_key, window, count)
|
|
7731
|
+
VALUES (?, ?, ?, 1)
|
|
7732
|
+
ON CONFLICT(scope, client_key, window) DO UPDATE SET count = count + 1`,
|
|
7733
|
+
scope,
|
|
7734
|
+
clientKey,
|
|
7735
|
+
window
|
|
7736
|
+
);
|
|
7737
|
+
return true;
|
|
7738
|
+
}
|
|
7739
|
+
};
|
|
7740
|
+
|
|
7741
|
+
// src/server/utils/docs-published-corpus.ts
|
|
7742
|
+
var cachedCorpus;
|
|
7743
|
+
async function loadDocsPublishedManifest(env, requestUrl = "https://assets.local/") {
|
|
7744
|
+
const response = await env.ASSETS.fetch(new URL("/_docs/manifest.json", requestUrl).toString());
|
|
7745
|
+
if (!response.ok) return null;
|
|
7746
|
+
const value = await response.json().catch(() => null);
|
|
7747
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
7748
|
+
const manifest = value;
|
|
7749
|
+
const assistant = parseAssistantPolicy(manifest.assistant);
|
|
7750
|
+
if (typeof manifest.sourceHash !== "string" || typeof manifest.name !== "string" || !assistant) return null;
|
|
7751
|
+
return {
|
|
7752
|
+
sourceHash: manifest.sourceHash,
|
|
7753
|
+
name: manifest.name,
|
|
7754
|
+
assistant,
|
|
7755
|
+
// Older docs artifacts did not carry an MCP policy. Fail closed until the
|
|
7756
|
+
// docs bundle is rebuilt by an MCP-aware SDK release.
|
|
7757
|
+
mcp: parseMcpPolicy(manifest.mcp) ?? { access: "disabled" }
|
|
7758
|
+
};
|
|
7759
|
+
}
|
|
7760
|
+
async function loadDocsPublishedCorpus(env, sourceHash, requestUrl = "https://assets.local/") {
|
|
7761
|
+
if (cachedCorpus?.sourceHash === sourceHash) return cachedCorpus.chunks;
|
|
7762
|
+
const response = await env.ASSETS.fetch(
|
|
7763
|
+
new URL("/_docs/assistant-index.json", requestUrl).toString()
|
|
7764
|
+
);
|
|
7765
|
+
if (!response.ok) throw new Error(`assistant-index.json returned ${response.status}`);
|
|
7766
|
+
const chunks = parseDocsCorpus(await response.json());
|
|
7767
|
+
cachedCorpus = { sourceHash, chunks };
|
|
7768
|
+
return chunks;
|
|
7769
|
+
}
|
|
7770
|
+
async function resolveDocsPublishedCorpus(env, sourceHash, requestUrl = "https://assets.local/") {
|
|
7771
|
+
try {
|
|
7772
|
+
return { ok: true, chunks: await loadDocsPublishedCorpus(env, sourceHash, requestUrl) };
|
|
7773
|
+
} catch (error) {
|
|
7774
|
+
return { ok: false, error };
|
|
7775
|
+
}
|
|
7776
|
+
}
|
|
7777
|
+
async function readDocsPublishedPage(env, corpus, requestedRoute, requestUrl = "https://assets.local/") {
|
|
7778
|
+
const route = normalizeDocsRoute(requestedRoute.split("#", 1)[0] ?? "/");
|
|
7779
|
+
const firstChunk = corpus.find((chunk) => chunk.route === route);
|
|
7780
|
+
if (!firstChunk) return null;
|
|
7781
|
+
const pathname = route === "/" ? "/_docs/index.md" : `/_docs${route}.md`;
|
|
7782
|
+
const response = await env.ASSETS.fetch(new URL(pathname, requestUrl).toString());
|
|
7783
|
+
if (!response.ok) return null;
|
|
7784
|
+
const markdown = await response.text();
|
|
7785
|
+
if (markdown.length > 5e5) throw new Error("Documentation page exceeds 500000 characters");
|
|
7786
|
+
return { route, title: firstChunk.title, markdown };
|
|
7787
|
+
}
|
|
7788
|
+
function parseAssistantPolicy(value) {
|
|
7789
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
7790
|
+
const policy = value;
|
|
7791
|
+
if (!["disabled", "public", "authenticated"].includes(String(policy.access))) return null;
|
|
7792
|
+
return {
|
|
7793
|
+
access: policy.access,
|
|
7794
|
+
...typeof policy.model === "string" ? { model: policy.model } : {}
|
|
7795
|
+
};
|
|
7796
|
+
}
|
|
7797
|
+
function parseMcpPolicy(value) {
|
|
7798
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
7799
|
+
const policy = value;
|
|
7800
|
+
if (!["disabled", "public"].includes(String(policy.access))) return null;
|
|
7801
|
+
return { access: policy.access };
|
|
7802
|
+
}
|
|
7803
|
+
|
|
7804
|
+
// src/server/utils/docs-public-limiter.ts
|
|
7805
|
+
async function docsPublicClientKey(value) {
|
|
7806
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
|
|
7807
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
7808
|
+
}
|
|
7809
|
+
async function acquireDocsPublicLease(env, clientKey, logPrefix) {
|
|
7810
|
+
if (!env.DOCS_ASSISTANT_LIMITER) return { ok: false, status: 503 };
|
|
7811
|
+
const id = env.DOCS_ASSISTANT_LIMITER.idFromName(env.DEEPSPACE_APP_ID);
|
|
7812
|
+
const stub = env.DOCS_ASSISTANT_LIMITER.get(id);
|
|
7813
|
+
const response = await stub.fetch("https://docs-limiter/acquire", {
|
|
7814
|
+
method: "POST",
|
|
7815
|
+
headers: { "Content-Type": "application/json" },
|
|
7816
|
+
body: JSON.stringify({ clientKey })
|
|
7817
|
+
}).catch((error) => {
|
|
7818
|
+
console.error(`[${logPrefix}] limiter unavailable:`, error);
|
|
7819
|
+
return null;
|
|
7820
|
+
});
|
|
7821
|
+
if (!response?.ok) {
|
|
7822
|
+
return { ok: false, status: response?.status === 429 ? 429 : 503 };
|
|
7823
|
+
}
|
|
7824
|
+
const body = await response.json().catch(() => null);
|
|
7825
|
+
if (!body || typeof body.leaseId !== "string") return { ok: false, status: 503 };
|
|
7826
|
+
return { ok: true, lease: { leaseId: body.leaseId, stub } };
|
|
7827
|
+
}
|
|
7828
|
+
async function releaseDocsPublicLease(lease, logPrefix) {
|
|
7829
|
+
if (!lease) return;
|
|
7830
|
+
await lease.stub.fetch("https://docs-limiter/release", {
|
|
7831
|
+
method: "POST",
|
|
7832
|
+
headers: { "Content-Type": "application/json" },
|
|
7833
|
+
body: JSON.stringify({ leaseId: lease.leaseId })
|
|
7834
|
+
}).catch((error) => console.error(`[${logPrefix}] limiter release failed:`, error));
|
|
7835
|
+
}
|
|
7836
|
+
|
|
7837
|
+
// src/server/utils/request-body.ts
|
|
7838
|
+
var RequestBodyTooLargeError = class extends Error {
|
|
7839
|
+
constructor(maxBytes) {
|
|
7840
|
+
super(`Request body exceeds ${maxBytes} bytes`);
|
|
7841
|
+
this.maxBytes = maxBytes;
|
|
7842
|
+
this.name = "RequestBodyTooLargeError";
|
|
7843
|
+
}
|
|
7844
|
+
};
|
|
7845
|
+
async function readBoundedRequestText(request, maxBytes) {
|
|
7846
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
|
|
7847
|
+
throw new TypeError("maxBytes must be a positive safe integer");
|
|
7848
|
+
}
|
|
7849
|
+
const declared = request.headers.get("content-length");
|
|
7850
|
+
if (declared && /^\d+$/.test(declared.trim()) && Number(declared) > maxBytes) {
|
|
7851
|
+
throw new RequestBodyTooLargeError(maxBytes);
|
|
7852
|
+
}
|
|
7853
|
+
if (!request.body) return "";
|
|
7854
|
+
const reader = request.body.getReader();
|
|
7855
|
+
const decoder = new TextDecoder();
|
|
7856
|
+
let bytes = 0;
|
|
7857
|
+
let text = "";
|
|
7858
|
+
while (true) {
|
|
7859
|
+
const chunk = await reader.read();
|
|
7860
|
+
if (chunk.done) break;
|
|
7861
|
+
bytes += chunk.value.byteLength;
|
|
7862
|
+
if (bytes > maxBytes) {
|
|
7863
|
+
await reader.cancel().catch(() => void 0);
|
|
7864
|
+
throw new RequestBodyTooLargeError(maxBytes);
|
|
7865
|
+
}
|
|
7866
|
+
text += decoder.decode(chunk.value, { stream: true });
|
|
7867
|
+
}
|
|
7868
|
+
return text + decoder.decode();
|
|
7869
|
+
}
|
|
7870
|
+
|
|
7871
|
+
// src/server/utils/docs-assistant-routes.ts
|
|
7872
|
+
var MAX_QUESTION_CHARS = 4e3;
|
|
7873
|
+
var MAX_HISTORY_MESSAGES = 12;
|
|
7874
|
+
var MAX_HISTORY_CHARS = 2e4;
|
|
7875
|
+
var MAX_REQUEST_BYTES = 128 * 1024;
|
|
7876
|
+
function registerDocsAssistantRoutes(app, resolveAuth) {
|
|
7877
|
+
app.post("/api/ai/docs", async (c) => {
|
|
7878
|
+
if (c.req.header("x-deepspace-surface") !== "docs") return c.notFound();
|
|
7879
|
+
const manifest = await loadDocsPublishedManifest(c.env, c.req.url);
|
|
7880
|
+
if (!manifest || manifest.assistant.access === "disabled") return c.notFound();
|
|
7881
|
+
let authToken;
|
|
7882
|
+
let publicClientKey;
|
|
7883
|
+
if (manifest.assistant.access === "authenticated") {
|
|
7884
|
+
const auth = await resolveAuth(c.req.raw, c.env);
|
|
7885
|
+
if (!auth) return c.json({ error: "Sign in required" }, 401);
|
|
7886
|
+
const header = c.req.header("Authorization") ?? "";
|
|
7887
|
+
if (!header.startsWith("Bearer ")) return c.json({ error: "Sign in required" }, 401);
|
|
7888
|
+
authToken = header.slice(7);
|
|
7889
|
+
} else {
|
|
7890
|
+
if (!c.env.APP_OWNER_JWT) {
|
|
7891
|
+
return c.json({ error: "Public documentation assistant billing is not configured" }, 503);
|
|
7892
|
+
}
|
|
7893
|
+
publicClientKey = await docsPublicClientKey(c.req.header("CF-Connecting-IP") ?? "unknown");
|
|
7894
|
+
}
|
|
7895
|
+
let body = null;
|
|
7896
|
+
try {
|
|
7897
|
+
const raw = await readBoundedRequestText(c.req.raw, MAX_REQUEST_BYTES);
|
|
7898
|
+
const parsed = JSON.parse(raw);
|
|
7899
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
7900
|
+
body = parsed;
|
|
7901
|
+
}
|
|
7902
|
+
} catch (error) {
|
|
7903
|
+
if (error instanceof RequestBodyTooLargeError) {
|
|
7904
|
+
return c.json({ error: `request exceeds ${MAX_REQUEST_BYTES} bytes` }, 413);
|
|
7905
|
+
}
|
|
7906
|
+
return c.json({ error: "request body must be valid JSON" }, 400);
|
|
7907
|
+
}
|
|
7908
|
+
const question = typeof body?.question === "string" ? body.question.trim() : "";
|
|
7909
|
+
if (!question) return c.json({ error: "question is required" }, 400);
|
|
7910
|
+
if (question.length > MAX_QUESTION_CHARS) {
|
|
7911
|
+
return c.json({ error: `question exceeds ${MAX_QUESTION_CHARS} characters` }, 413);
|
|
7912
|
+
}
|
|
7913
|
+
const route = typeof body?.route === "string" && body.route.startsWith("/") ? body.route.slice(0, 500) : void 0;
|
|
7914
|
+
let history;
|
|
7915
|
+
try {
|
|
7916
|
+
history = normalizeDocsAssistantHistory(body?.messages);
|
|
7917
|
+
} catch (error) {
|
|
7918
|
+
return c.json({ error: error instanceof Error ? error.message : "messages are invalid" }, 400);
|
|
7919
|
+
}
|
|
7920
|
+
const corpusResult = await resolveDocsPublishedCorpus(c.env, manifest.sourceHash, c.req.url);
|
|
7921
|
+
if (!corpusResult.ok) {
|
|
7922
|
+
console.error("[docs-assistant] corpus unavailable:", corpusResult.error);
|
|
7923
|
+
return c.json({ error: "Documentation corpus is unavailable" }, 503);
|
|
7924
|
+
}
|
|
7925
|
+
const corpus = corpusResult.chunks;
|
|
7926
|
+
const selectedModel = resolveDeepSpaceAgentModel(
|
|
7927
|
+
manifest.assistant.model,
|
|
7928
|
+
"documentation"
|
|
7929
|
+
);
|
|
7930
|
+
if (!selectedModel) {
|
|
7931
|
+
console.error("[docs-assistant] unsupported configured model:", manifest.assistant.model);
|
|
7932
|
+
return c.json({ error: "Documentation assistant model is not supported" }, 503);
|
|
7933
|
+
}
|
|
7934
|
+
const diagnosticContext = {
|
|
7935
|
+
profile: "documentation",
|
|
7936
|
+
provider: selectedModel.provider,
|
|
7937
|
+
modelId: selectedModel.modelId
|
|
7938
|
+
};
|
|
7939
|
+
if (publicClientKey && !c.env.DOCS_ASSISTANT_LIMITER) {
|
|
7940
|
+
return c.json({ error: "Documentation assistant rate limiting is not configured" }, 503);
|
|
7941
|
+
}
|
|
7942
|
+
const leaseResult = publicClientKey ? await acquireDocsPublicLease(c.env, publicClientKey, "docs-assistant") : void 0;
|
|
7943
|
+
if (leaseResult && !leaseResult.ok) {
|
|
7944
|
+
return c.json(
|
|
7945
|
+
{
|
|
7946
|
+
error: leaseResult.status === 429 ? "Documentation assistant rate limit exceeded. Try again shortly." : "Documentation assistant is temporarily unavailable"
|
|
7947
|
+
},
|
|
7948
|
+
leaseResult.status,
|
|
7949
|
+
leaseResult.status === 429 ? { "Retry-After": "60" } : void 0
|
|
7950
|
+
);
|
|
7951
|
+
}
|
|
7952
|
+
const lease = leaseResult?.ok ? leaseResult.lease : void 0;
|
|
7953
|
+
let released = false;
|
|
7954
|
+
const releaseLease = async () => {
|
|
7955
|
+
if (!lease || released) return;
|
|
7956
|
+
released = true;
|
|
7957
|
+
await releaseDocsPublicLease(lease, "docs-assistant");
|
|
7958
|
+
};
|
|
7959
|
+
let result;
|
|
7960
|
+
try {
|
|
7961
|
+
result = streamDeepSpaceAgent(c.env, {
|
|
7962
|
+
profile: "documentation",
|
|
7963
|
+
modelId: selectedModel.modelId,
|
|
7964
|
+
...authToken ? { authToken } : {},
|
|
7965
|
+
system: buildDocsAssistantPrompt(manifest.name || c.env.APP_NAME, route),
|
|
7966
|
+
messages: [...history, { role: "user", content: question }],
|
|
7967
|
+
tools: buildDocsAssistantTools(corpus),
|
|
7968
|
+
abortSignal: c.req.raw.signal,
|
|
7969
|
+
onAbort: () => releaseLease(),
|
|
7970
|
+
onFinish: releaseLease,
|
|
7971
|
+
onError: ({ error }) => {
|
|
7972
|
+
console.error(
|
|
7973
|
+
`[docs-assistant] stream error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
|
|
7974
|
+
);
|
|
7975
|
+
void releaseLease();
|
|
7976
|
+
}
|
|
7977
|
+
}).result;
|
|
7978
|
+
} catch (error) {
|
|
7979
|
+
console.error(
|
|
7980
|
+
`[docs-assistant] agent unavailable: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
|
|
7981
|
+
);
|
|
7982
|
+
await releaseLease();
|
|
7983
|
+
return c.json({ error: "Documentation assistant is not configured" }, 503);
|
|
7984
|
+
}
|
|
7985
|
+
return result.toUIMessageStreamResponse({
|
|
7986
|
+
sendReasoning: false,
|
|
7987
|
+
onError: (error) => {
|
|
7988
|
+
console.error(
|
|
7989
|
+
`[docs-assistant] response error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
|
|
7990
|
+
);
|
|
7991
|
+
void releaseLease();
|
|
7992
|
+
return "The documentation assistant could not complete this response.";
|
|
7993
|
+
}
|
|
7994
|
+
});
|
|
7995
|
+
});
|
|
7996
|
+
}
|
|
7997
|
+
var docsAssistantErrorSummary = deepSpaceAgentErrorSummary;
|
|
7998
|
+
function normalizeDocsAssistantHistory(value) {
|
|
7999
|
+
if (value === void 0) return [];
|
|
8000
|
+
if (!Array.isArray(value)) throw new TypeError("messages must be an array");
|
|
8001
|
+
if (value.length > MAX_HISTORY_MESSAGES) {
|
|
8002
|
+
throw new TypeError(`messages exceeds ${MAX_HISTORY_MESSAGES} turns`);
|
|
8003
|
+
}
|
|
8004
|
+
const messages = [];
|
|
8005
|
+
let totalChars2 = 0;
|
|
8006
|
+
for (const [index, item] of value.entries()) {
|
|
8007
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
8008
|
+
throw new TypeError(`messages[${index}] must be an object`);
|
|
8009
|
+
}
|
|
8010
|
+
const candidate = item;
|
|
8011
|
+
const expectedRole = index % 2 === 0 ? "user" : "assistant";
|
|
8012
|
+
if (candidate.role !== expectedRole) {
|
|
8013
|
+
throw new TypeError(`messages[${index}].role must be ${expectedRole}`);
|
|
8014
|
+
}
|
|
8015
|
+
const content = typeof candidate.content === "string" ? candidate.content.trim() : "";
|
|
8016
|
+
if (!content) throw new TypeError(`messages[${index}].content is required`);
|
|
8017
|
+
if (content.length > MAX_QUESTION_CHARS) {
|
|
8018
|
+
throw new TypeError(`messages[${index}].content exceeds ${MAX_QUESTION_CHARS} characters`);
|
|
8019
|
+
}
|
|
8020
|
+
totalChars2 += content.length;
|
|
8021
|
+
if (totalChars2 > MAX_HISTORY_CHARS) {
|
|
8022
|
+
throw new TypeError(`messages exceeds ${MAX_HISTORY_CHARS} characters`);
|
|
8023
|
+
}
|
|
8024
|
+
messages.push({ role: expectedRole, content });
|
|
8025
|
+
}
|
|
8026
|
+
if (messages.length % 2 !== 0) {
|
|
8027
|
+
throw new TypeError("messages must end with a completed assistant response");
|
|
8028
|
+
}
|
|
8029
|
+
return messages;
|
|
8030
|
+
}
|
|
8031
|
+
function registerDocsStaticRoutes(app) {
|
|
8032
|
+
app.get("/_docs/*", async (c) => {
|
|
8033
|
+
const pathname = new URL(c.req.url).pathname;
|
|
8034
|
+
let response = await c.env.ASSETS.fetch(c.req.raw);
|
|
8035
|
+
let status = response.status;
|
|
8036
|
+
if (status === 404) {
|
|
8037
|
+
const url = new URL(c.req.url);
|
|
8038
|
+
url.pathname = "/_docs/404.html";
|
|
8039
|
+
response = await c.env.ASSETS.fetch(new Request(url.toString(), c.req.raw));
|
|
8040
|
+
status = 404;
|
|
8041
|
+
}
|
|
8042
|
+
return secureDocsResponse(response, status, pathname);
|
|
8043
|
+
});
|
|
8044
|
+
}
|
|
8045
|
+
function secureDocsResponse(response, status, pathname) {
|
|
8046
|
+
const headers = new Headers(response.headers);
|
|
8047
|
+
if (status !== 404 && pathname.endsWith(".md")) {
|
|
8048
|
+
headers.set("Content-Type", "text/markdown; charset=utf-8");
|
|
8049
|
+
}
|
|
8050
|
+
if (headers.get("Content-Type")?.toLowerCase().includes("text/html")) {
|
|
8051
|
+
const cacheControl = headers.get("Cache-Control") ?? "public, max-age=0, must-revalidate";
|
|
8052
|
+
if (!/(?:^|,)\s*no-transform(?:\s*,|$)/i.test(cacheControl)) {
|
|
8053
|
+
headers.set("Cache-Control", `${cacheControl}, no-transform`);
|
|
8054
|
+
}
|
|
8055
|
+
}
|
|
8056
|
+
headers.set("X-Content-Type-Options", "nosniff");
|
|
8057
|
+
headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
8058
|
+
headers.set(
|
|
8059
|
+
"Content-Security-Policy",
|
|
8060
|
+
"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'"
|
|
8061
|
+
);
|
|
8062
|
+
return new Response(response.body, { status, headers });
|
|
8063
|
+
}
|
|
8064
|
+
|
|
8065
|
+
// src/server/utils/docs-mcp/protocol.ts
|
|
8066
|
+
var MCP_SERVER_VERSION = "1.0.0";
|
|
8067
|
+
var CURRENT_PROTOCOL_VERSION = "2025-11-25";
|
|
8068
|
+
var SUPPORTED_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PROTOCOL_VERSION, "2025-06-18", "2025-03-26"]);
|
|
8069
|
+
function initializeResponse(request) {
|
|
8070
|
+
const params = objectValue(request.params);
|
|
8071
|
+
const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : CURRENT_PROTOCOL_VERSION;
|
|
8072
|
+
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : CURRENT_PROTOCOL_VERSION;
|
|
8073
|
+
return mcpResult(request.id, {
|
|
8074
|
+
protocolVersion,
|
|
8075
|
+
capabilities: {
|
|
8076
|
+
tools: { listChanged: false },
|
|
8077
|
+
resources: { subscribe: false, listChanged: false }
|
|
8078
|
+
},
|
|
8079
|
+
serverInfo: {
|
|
8080
|
+
name: "DeepSpace Docs",
|
|
8081
|
+
title: "DeepSpace documentation",
|
|
8082
|
+
version: MCP_SERVER_VERSION
|
|
8083
|
+
},
|
|
8084
|
+
instructions: "Search the published docs before answering, then read exact pages when full context is needed."
|
|
8085
|
+
});
|
|
8086
|
+
}
|
|
8087
|
+
function hasValidOrigin(request) {
|
|
8088
|
+
const origin = request.headers.get("Origin");
|
|
8089
|
+
if (!origin) return true;
|
|
8090
|
+
try {
|
|
8091
|
+
return new URL(origin).origin === new URL(request.url).origin;
|
|
8092
|
+
} catch {
|
|
8093
|
+
return false;
|
|
8094
|
+
}
|
|
8095
|
+
}
|
|
8096
|
+
function parseRequest(raw) {
|
|
8097
|
+
let value;
|
|
8098
|
+
try {
|
|
8099
|
+
value = JSON.parse(raw);
|
|
8100
|
+
} catch {
|
|
8101
|
+
return { ok: false, message: "Invalid JSON" };
|
|
8102
|
+
}
|
|
8103
|
+
const candidate = objectValue(value);
|
|
8104
|
+
if (!candidate || candidate.jsonrpc !== "2.0" || typeof candidate.method !== "string") {
|
|
8105
|
+
return { ok: false, message: "Invalid JSON-RPC request" };
|
|
8106
|
+
}
|
|
8107
|
+
if (candidate.id !== void 0 && candidate.id !== null && typeof candidate.id !== "string" && typeof candidate.id !== "number")
|
|
8108
|
+
return { ok: false, message: "Invalid JSON-RPC id" };
|
|
8109
|
+
return {
|
|
8110
|
+
ok: true,
|
|
8111
|
+
request: {
|
|
8112
|
+
jsonrpc: "2.0",
|
|
8113
|
+
method: candidate.method,
|
|
8114
|
+
...candidate.id !== void 0 ? { id: candidate.id } : {},
|
|
8115
|
+
...candidate.params !== void 0 ? { params: candidate.params } : {}
|
|
8116
|
+
}
|
|
8117
|
+
};
|
|
8118
|
+
}
|
|
8119
|
+
function objectValue(value) {
|
|
8120
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
8121
|
+
}
|
|
8122
|
+
function toolResult(id, value) {
|
|
8123
|
+
return mcpResult(id, {
|
|
8124
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
8125
|
+
structuredContent: value,
|
|
8126
|
+
isError: false
|
|
8127
|
+
});
|
|
8128
|
+
}
|
|
8129
|
+
function toolError(id, message) {
|
|
8130
|
+
return mcpResult(id, {
|
|
8131
|
+
content: [{ type: "text", text: message }],
|
|
8132
|
+
isError: true
|
|
8133
|
+
});
|
|
8134
|
+
}
|
|
8135
|
+
function mcpResult(id, result) {
|
|
8136
|
+
return jsonResponse({ jsonrpc: "2.0", id, result });
|
|
8137
|
+
}
|
|
8138
|
+
function mcpError(id, code, message, status = 200, headers) {
|
|
8139
|
+
return jsonResponse({ jsonrpc: "2.0", id, error: { code, message } }, status, headers);
|
|
8140
|
+
}
|
|
8141
|
+
function jsonResponse(value, status = 200, headers) {
|
|
8142
|
+
return new Response(JSON.stringify(value), {
|
|
8143
|
+
status,
|
|
8144
|
+
headers: {
|
|
8145
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
8146
|
+
...mcpSecurityHeaders(),
|
|
8147
|
+
...headers
|
|
8148
|
+
}
|
|
8149
|
+
});
|
|
8150
|
+
}
|
|
8151
|
+
function mcpSecurityHeaders() {
|
|
8152
|
+
return {
|
|
8153
|
+
"Cache-Control": "no-store",
|
|
8154
|
+
"X-Content-Type-Options": "nosniff",
|
|
8155
|
+
"Referrer-Policy": "no-referrer"
|
|
8156
|
+
};
|
|
8157
|
+
}
|
|
8158
|
+
|
|
8159
|
+
// src/server/utils/docs-mcp/tools.ts
|
|
8160
|
+
var DOCS_TOOLS = [
|
|
8161
|
+
{
|
|
8162
|
+
name: "docs_search",
|
|
8163
|
+
title: "Search documentation",
|
|
8164
|
+
description: "Search the published documentation and return ranked, linked excerpts.",
|
|
8165
|
+
inputSchema: {
|
|
8166
|
+
type: "object",
|
|
8167
|
+
properties: {
|
|
8168
|
+
query: { type: "string", minLength: 2, maxLength: 300 },
|
|
8169
|
+
limit: { type: "integer", minimum: 1, maximum: 10, default: 6 }
|
|
8170
|
+
},
|
|
8171
|
+
required: ["query"],
|
|
8172
|
+
additionalProperties: false
|
|
8173
|
+
},
|
|
8174
|
+
outputSchema: {
|
|
8175
|
+
type: "object",
|
|
8176
|
+
properties: { results: { type: "array" } },
|
|
8177
|
+
required: ["results"]
|
|
8178
|
+
},
|
|
8179
|
+
annotations: {
|
|
8180
|
+
readOnlyHint: true,
|
|
8181
|
+
destructiveHint: false,
|
|
8182
|
+
idempotentHint: true,
|
|
8183
|
+
openWorldHint: false
|
|
8184
|
+
}
|
|
8185
|
+
},
|
|
8186
|
+
{
|
|
8187
|
+
name: "docs_read",
|
|
8188
|
+
title: "Read documentation page",
|
|
8189
|
+
description: "Read one complete published documentation page as Markdown.",
|
|
8190
|
+
inputSchema: {
|
|
8191
|
+
type: "object",
|
|
8192
|
+
properties: {
|
|
8193
|
+
route: { type: "string", minLength: 1, maxLength: 500, pattern: "^/" }
|
|
8194
|
+
},
|
|
8195
|
+
required: ["route"],
|
|
8196
|
+
additionalProperties: false
|
|
8197
|
+
},
|
|
8198
|
+
outputSchema: {
|
|
8199
|
+
type: "object",
|
|
8200
|
+
properties: {
|
|
8201
|
+
route: { type: "string" },
|
|
8202
|
+
title: { type: "string" },
|
|
8203
|
+
url: { type: "string" },
|
|
8204
|
+
markdown: { type: "string" }
|
|
8205
|
+
},
|
|
8206
|
+
required: ["route", "title", "url", "markdown"]
|
|
8207
|
+
},
|
|
8208
|
+
annotations: {
|
|
8209
|
+
readOnlyHint: true,
|
|
8210
|
+
destructiveHint: false,
|
|
8211
|
+
idempotentHint: true,
|
|
8212
|
+
openWorldHint: false
|
|
8213
|
+
}
|
|
8214
|
+
}
|
|
8215
|
+
];
|
|
8216
|
+
async function callDocsTool(rawRequest, request, corpus, env) {
|
|
8217
|
+
const params = objectValue(request.params);
|
|
8218
|
+
const name = typeof params?.name === "string" ? params.name : "";
|
|
8219
|
+
const args = objectValue(params?.arguments) ?? {};
|
|
8220
|
+
const origin = new URL(rawRequest.url).origin;
|
|
8221
|
+
if (name === "docs_search") {
|
|
8222
|
+
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
8223
|
+
const limit = args.limit === void 0 ? 6 : args.limit;
|
|
8224
|
+
if (query.length < 2 || query.length > 300 || !Number.isInteger(limit) || Number(limit) < 1 || Number(limit) > 10) {
|
|
8225
|
+
return mcpError(request.id, -32602, "Invalid docs_search arguments");
|
|
8226
|
+
}
|
|
8227
|
+
const value = {
|
|
8228
|
+
results: searchDocsCorpus(corpus, query, Number(limit)).map((result) => ({
|
|
8229
|
+
...result,
|
|
8230
|
+
url: new URL(result.route, origin).toString()
|
|
8231
|
+
}))
|
|
8232
|
+
};
|
|
8233
|
+
return toolResult(request.id, value);
|
|
8234
|
+
}
|
|
8235
|
+
if (name === "docs_read") {
|
|
8236
|
+
const route = typeof args.route === "string" ? args.route.trim() : "";
|
|
8237
|
+
if (!route.startsWith("/") || route.length > 500) {
|
|
8238
|
+
return mcpError(request.id, -32602, "Invalid docs_read arguments");
|
|
8239
|
+
}
|
|
8240
|
+
try {
|
|
8241
|
+
const page = await readDocsPublishedPage(env, corpus, route, rawRequest.url);
|
|
8242
|
+
if (!page) return toolError(request.id, `No published documentation page exists at ${route}`);
|
|
8243
|
+
return toolResult(request.id, {
|
|
8244
|
+
...page,
|
|
8245
|
+
url: new URL(page.route, origin).toString()
|
|
8246
|
+
});
|
|
8247
|
+
} catch (error) {
|
|
8248
|
+
console.error("[docs-mcp] page unavailable:", error);
|
|
8249
|
+
return toolError(request.id, "The documentation page could not be read");
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
8252
|
+
return mcpError(request.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
|
|
8253
|
+
}
|
|
8254
|
+
|
|
8255
|
+
// src/server/utils/docs-mcp-routes.ts
|
|
8256
|
+
var MAX_REQUEST_CHARS = 64e3;
|
|
8257
|
+
var MAX_REQUEST_BYTES2 = MAX_REQUEST_CHARS * 4;
|
|
8258
|
+
var MAX_SKILL_CHARS = 256e3;
|
|
8259
|
+
function registerDocsMcpRoutes(app) {
|
|
8260
|
+
app.get("/mcp", async (c) => {
|
|
8261
|
+
if (!await publicManifest(c.req.raw, c.env)) return c.notFound();
|
|
8262
|
+
return new Response(null, {
|
|
8263
|
+
status: 405,
|
|
8264
|
+
headers: { Allow: "POST", ...mcpSecurityHeaders() }
|
|
8265
|
+
});
|
|
8266
|
+
});
|
|
8267
|
+
app.post("/mcp", async (c) => {
|
|
8268
|
+
const manifest = await publicManifest(c.req.raw, c.env);
|
|
8269
|
+
if (!manifest) return c.notFound();
|
|
8270
|
+
if (!hasValidOrigin(c.req.raw)) {
|
|
8271
|
+
return mcpError(null, -32e3, "Invalid Origin", 403);
|
|
8272
|
+
}
|
|
8273
|
+
let raw;
|
|
8274
|
+
try {
|
|
8275
|
+
raw = await readBoundedRequestText(c.req.raw, MAX_REQUEST_BYTES2);
|
|
8276
|
+
} catch (error) {
|
|
8277
|
+
if (error instanceof RequestBodyTooLargeError) {
|
|
8278
|
+
return mcpError(null, -32600, `Request exceeds ${MAX_REQUEST_BYTES2} bytes`, 413);
|
|
8279
|
+
}
|
|
8280
|
+
return mcpError(null, -32700, "Unable to read request body", 400);
|
|
8281
|
+
}
|
|
8282
|
+
if (raw.length > MAX_REQUEST_CHARS) {
|
|
8283
|
+
return mcpError(null, -32600, `Request exceeds ${MAX_REQUEST_CHARS} characters`, 413);
|
|
8284
|
+
}
|
|
8285
|
+
const parsed = parseRequest(raw);
|
|
8286
|
+
if (!parsed.ok) return mcpError(null, -32700, parsed.message, 400);
|
|
8287
|
+
const request = parsed.request;
|
|
8288
|
+
if (request.method !== "initialize") {
|
|
8289
|
+
const protocolVersion = c.req.header("MCP-Protocol-Version");
|
|
8290
|
+
if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
|
|
8291
|
+
return mcpError(request.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
const requestId = request.id;
|
|
8295
|
+
if (requestId === void 0) return new Response(null, { status: 202 });
|
|
8296
|
+
const requestWithId = { ...request, id: requestId };
|
|
8297
|
+
if (request.method === "initialize") return initializeResponse(requestWithId);
|
|
8298
|
+
if (request.method === "ping") return mcpResult(requestId, {});
|
|
8299
|
+
if (request.method === "tools/list") {
|
|
8300
|
+
return mcpResult(requestId, { tools: DOCS_TOOLS });
|
|
8301
|
+
}
|
|
8302
|
+
if (request.method === "resources/list") {
|
|
8303
|
+
return mcpResult(requestId, { resources: [skillResource(c.req.raw, manifest)] });
|
|
8304
|
+
}
|
|
8305
|
+
if (request.method === "resources/read") {
|
|
8306
|
+
return readSkillResource(c.req.raw, requestWithId, manifest, c.env);
|
|
8307
|
+
}
|
|
8308
|
+
if (request.method !== "tools/call") {
|
|
8309
|
+
return mcpError(requestId, -32601, `Method not found: ${request.method}`);
|
|
8310
|
+
}
|
|
8311
|
+
const corpusResult = await resolveDocsPublishedCorpus(c.env, manifest.sourceHash, c.req.url);
|
|
8312
|
+
if (!corpusResult.ok) {
|
|
8313
|
+
console.error("[docs-mcp] corpus unavailable:", corpusResult.error);
|
|
8314
|
+
return mcpError(requestId, -32002, "Documentation corpus is unavailable", 503);
|
|
8315
|
+
}
|
|
8316
|
+
const corpus = corpusResult.chunks;
|
|
8317
|
+
const clientKey = await docsPublicClientKey(c.req.header("CF-Connecting-IP") ?? "unknown");
|
|
8318
|
+
const leaseResult = await acquireDocsPublicLease(c.env, clientKey, "docs-mcp");
|
|
8319
|
+
if (!leaseResult.ok) {
|
|
8320
|
+
return mcpError(
|
|
8321
|
+
requestId,
|
|
8322
|
+
leaseResult.status === 429 ? -32029 : -32002,
|
|
8323
|
+
leaseResult.status === 429 ? "Documentation tool rate limit exceeded. Try again shortly." : "Documentation tools are temporarily unavailable",
|
|
8324
|
+
leaseResult.status,
|
|
8325
|
+
leaseResult.status === 429 ? { "Retry-After": "60" } : void 0
|
|
8326
|
+
);
|
|
8327
|
+
}
|
|
8328
|
+
try {
|
|
8329
|
+
return await callDocsTool(c.req.raw, requestWithId, corpus, c.env);
|
|
8330
|
+
} finally {
|
|
8331
|
+
await releaseDocsPublicLease(leaseResult.lease, "docs-mcp");
|
|
8332
|
+
}
|
|
8333
|
+
});
|
|
8334
|
+
for (const path of [
|
|
8335
|
+
"/.well-known/mcp",
|
|
8336
|
+
"/.well-known/mcp.json",
|
|
8337
|
+
"/.well-known/mcp/server-card.json"
|
|
8338
|
+
]) {
|
|
8339
|
+
app.get(path, async (c) => {
|
|
8340
|
+
const manifest = await publicManifest(c.req.raw, c.env);
|
|
8341
|
+
if (!manifest) return c.notFound();
|
|
8342
|
+
return discoveryResponse(c.req.raw, manifest);
|
|
8343
|
+
});
|
|
8344
|
+
}
|
|
8345
|
+
app.get("/.well-known/mcp/server-cards.json", async (c) => {
|
|
8346
|
+
const manifest = await publicManifest(c.req.raw, c.env);
|
|
8347
|
+
if (!manifest) return c.notFound();
|
|
8348
|
+
return jsonResponse({ servers: [serverCard(c.req.raw, manifest)] }, 200, {
|
|
8349
|
+
"Cache-Control": "public, max-age=300"
|
|
8350
|
+
});
|
|
8351
|
+
});
|
|
8352
|
+
}
|
|
8353
|
+
async function readSkillResource(rawRequest, request, manifest, env) {
|
|
8354
|
+
const params = objectValue(request.params);
|
|
8355
|
+
const requestedUri = typeof params?.uri === "string" ? params.uri : "";
|
|
8356
|
+
const resource = skillResource(rawRequest, manifest);
|
|
8357
|
+
if (requestedUri !== resource.uri) {
|
|
8358
|
+
return mcpError(request.id, -32602, "Unknown documentation resource");
|
|
8359
|
+
}
|
|
8360
|
+
const assetUrl = new URL("/_docs/skill.md", rawRequest.url);
|
|
8361
|
+
const response = await env.ASSETS.fetch(new Request(assetUrl, { method: "GET" }));
|
|
8362
|
+
if (!response.ok) return mcpError(request.id, -32002, "Documentation skill is unavailable", 503);
|
|
8363
|
+
const text = await response.text();
|
|
8364
|
+
if (text.length > MAX_SKILL_CHARS) {
|
|
8365
|
+
return mcpError(request.id, -32002, "Documentation skill exceeds the published size limit", 503);
|
|
8366
|
+
}
|
|
8367
|
+
return mcpResult(request.id, {
|
|
8368
|
+
contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
|
|
8369
|
+
});
|
|
8370
|
+
}
|
|
8371
|
+
function skillResource(request, manifest) {
|
|
8372
|
+
return {
|
|
8373
|
+
uri: new URL("/skill.md", request.url).toString(),
|
|
8374
|
+
name: `${manifest.name} documentation skill`,
|
|
8375
|
+
title: `Use ${manifest.name}`,
|
|
8376
|
+
description: `Agent instructions grounded in the published ${manifest.name} documentation.`,
|
|
8377
|
+
mimeType: "text/markdown"
|
|
8378
|
+
};
|
|
8379
|
+
}
|
|
8380
|
+
async function publicManifest(request, env) {
|
|
8381
|
+
if (request.headers.get("x-deepspace-surface") !== "docs") return null;
|
|
8382
|
+
const manifest = await loadDocsPublishedManifest(env, request.url);
|
|
8383
|
+
return manifest?.mcp.access === "public" ? manifest : null;
|
|
8384
|
+
}
|
|
8385
|
+
function discoveryResponse(request, manifest) {
|
|
8386
|
+
return jsonResponse(serverCard(request, manifest), 200, {
|
|
8387
|
+
"Cache-Control": "public, max-age=300"
|
|
8388
|
+
});
|
|
8389
|
+
}
|
|
8390
|
+
function serverCard(request, manifest) {
|
|
8391
|
+
return {
|
|
8392
|
+
name: manifest.name,
|
|
8393
|
+
description: `Search and read the published ${manifest.name} documentation.`,
|
|
8394
|
+
version: MCP_SERVER_VERSION,
|
|
8395
|
+
protocolVersion: CURRENT_PROTOCOL_VERSION,
|
|
8396
|
+
transport: {
|
|
8397
|
+
type: "streamable-http",
|
|
8398
|
+
url: new URL("/mcp", request.url).toString()
|
|
8399
|
+
},
|
|
8400
|
+
authentication: { required: false },
|
|
8401
|
+
capabilities: {
|
|
8402
|
+
tools: DOCS_TOOLS.map(({ name, title, description }) => ({ name, title, description })),
|
|
8403
|
+
resources: [skillResource(request, manifest)]
|
|
8404
|
+
}
|
|
8405
|
+
};
|
|
8406
|
+
}
|
|
8407
|
+
|
|
8408
|
+
// src/server/utils/docs-routes.ts
|
|
8409
|
+
function registerDeepSpaceDocs(app, options) {
|
|
8410
|
+
registerDocsAssistantRoutes(app, options.resolveAuth);
|
|
8411
|
+
registerDocsMcpRoutes(app);
|
|
8412
|
+
registerDocsStaticRoutes(app);
|
|
8413
|
+
}
|
|
8414
|
+
function docsAssistantLimiterManifestEntry(className) {
|
|
8415
|
+
return {
|
|
8416
|
+
binding: "DOCS_ASSISTANT_LIMITER",
|
|
8417
|
+
className,
|
|
8418
|
+
sqlite: true
|
|
8419
|
+
};
|
|
8420
|
+
}
|
|
8421
|
+
|
|
7107
8422
|
// src/server/utils/metering.ts
|
|
7108
8423
|
function meterUsage(env, kind, fields = {}) {
|
|
7109
8424
|
const ds = env.USAGE_EVENTS;
|
|
@@ -7581,7 +8896,7 @@ function shouldThrottleClientError(now = Date.now()) {
|
|
|
7581
8896
|
windowCount += 1;
|
|
7582
8897
|
return windowCount > THROTTLE_MAX;
|
|
7583
8898
|
}
|
|
7584
|
-
function
|
|
8899
|
+
function jsonResponse2(body, status) {
|
|
7585
8900
|
return new Response(JSON.stringify(body), {
|
|
7586
8901
|
status,
|
|
7587
8902
|
headers: { "Content-Type": "application/json" }
|
|
@@ -7597,16 +8912,16 @@ async function handleClientErrorReport(request) {
|
|
|
7597
8912
|
}
|
|
7598
8913
|
const declared = Number(request.headers.get("content-length"));
|
|
7599
8914
|
if (Number.isFinite(declared) && declared > MAX_BODY_BYTES) {
|
|
7600
|
-
return
|
|
8915
|
+
return jsonResponse2({ error: "payload_too_large" }, 413);
|
|
7601
8916
|
}
|
|
7602
8917
|
let body;
|
|
7603
8918
|
try {
|
|
7604
8919
|
body = await request.json();
|
|
7605
8920
|
} catch {
|
|
7606
|
-
return
|
|
8921
|
+
return jsonResponse2({ error: "invalid_json" }, 400);
|
|
7607
8922
|
}
|
|
7608
8923
|
const report = normalizeClientErrorReport(body);
|
|
7609
|
-
if (!report) return
|
|
8924
|
+
if (!report) return jsonResponse2({ error: "invalid_report" }, 400);
|
|
7610
8925
|
console.error(clientErrorToLogLine(report));
|
|
7611
8926
|
return new Response(null, { status: 204 });
|
|
7612
8927
|
}
|
|
@@ -7649,11 +8964,18 @@ export {
|
|
|
7649
8964
|
COST_RATES,
|
|
7650
8965
|
CanvasRoom,
|
|
7651
8966
|
CronRoom,
|
|
8967
|
+
DEEPSPACE_AGENT_PROFILES,
|
|
8968
|
+
DEEPSPACE_AI_DEFAULTS,
|
|
8969
|
+
DEEPSPACE_AI_MODELS,
|
|
8970
|
+
DEEPSPACE_MODEL_CATALOG_PROVENANCE,
|
|
7652
8971
|
DEFAULT_CONTEXT_CONFIG,
|
|
7653
8972
|
DEFAULT_DO_MANIFEST,
|
|
7654
8973
|
DEFAULT_QUERY_LIMIT,
|
|
7655
8974
|
DEPLOYED_APPS_DEFAULTS,
|
|
7656
8975
|
DIRECTORY_SCHEMAS,
|
|
8976
|
+
DeepSpaceAgentModelError,
|
|
8977
|
+
DeepSpaceAgentProfileError,
|
|
8978
|
+
DocsAssistantLimiter,
|
|
7657
8979
|
GLOBAL_DO_TYPES,
|
|
7658
8980
|
GLOBAL_DO_TYPE_NAMES,
|
|
7659
8981
|
GameRoom,
|
|
@@ -7689,6 +9011,7 @@ export {
|
|
|
7689
9011
|
VOTING_SCHEMAS,
|
|
7690
9012
|
WORKSPACE_SCHEMAS,
|
|
7691
9013
|
YjsRoom,
|
|
9014
|
+
acquireDocsPublicLease,
|
|
7692
9015
|
analyzePermissions,
|
|
7693
9016
|
apiWorkerFetch,
|
|
7694
9017
|
appendMessage,
|
|
@@ -7697,6 +9020,8 @@ export {
|
|
|
7697
9020
|
authWorkerFetch,
|
|
7698
9021
|
bindingManifestFromOutputConfig,
|
|
7699
9022
|
buildCronContext,
|
|
9023
|
+
buildDocsAssistantPrompt,
|
|
9024
|
+
buildDocsAssistantTools,
|
|
7700
9025
|
buildTableSelect,
|
|
7701
9026
|
buildUiParts,
|
|
7702
9027
|
callRegistry,
|
|
@@ -7722,8 +9047,12 @@ export {
|
|
|
7722
9047
|
createScopedR2Handler,
|
|
7723
9048
|
dataToColumnValues,
|
|
7724
9049
|
decodeJwtPayload,
|
|
9050
|
+
deepSpaceAgentErrorSummary,
|
|
7725
9051
|
deleteChatCascade,
|
|
7726
9052
|
dispatch,
|
|
9053
|
+
docsAssistantErrorSummary,
|
|
9054
|
+
docsAssistantLimiterManifestEntry,
|
|
9055
|
+
docsPublicClientKey,
|
|
7727
9056
|
encode,
|
|
7728
9057
|
encodeAwarenessMessage,
|
|
7729
9058
|
encodeSyncStep1,
|
|
@@ -7731,6 +9060,7 @@ export {
|
|
|
7731
9060
|
encodeUpdate,
|
|
7732
9061
|
enqueueJob,
|
|
7733
9062
|
getChat,
|
|
9063
|
+
getDeepSpaceAIModel,
|
|
7734
9064
|
getGlobalDOSchemas,
|
|
7735
9065
|
getGlobalDOType,
|
|
7736
9066
|
getMessageType,
|
|
@@ -7740,11 +9070,15 @@ export {
|
|
|
7740
9070
|
handleSyncMessage,
|
|
7741
9071
|
hasContent,
|
|
7742
9072
|
isAutoProvision,
|
|
9073
|
+
isDocsResourcePath,
|
|
7743
9074
|
isOwner,
|
|
7744
9075
|
isTestAccountClaims,
|
|
7745
9076
|
isTestAccountEmail,
|
|
7746
9077
|
isTestAccountTier,
|
|
7747
9078
|
lintSchema,
|
|
9079
|
+
listDeepSpaceAgentModels,
|
|
9080
|
+
loadDocsPublishedCorpus,
|
|
9081
|
+
loadDocsPublishedManifest,
|
|
7748
9082
|
loadMessages,
|
|
7749
9083
|
makeDefaultSummarizer,
|
|
7750
9084
|
matchOrigin,
|
|
@@ -7752,21 +9086,35 @@ export {
|
|
|
7752
9086
|
meterUsage,
|
|
7753
9087
|
meterVectorize,
|
|
7754
9088
|
noopPermissionContext,
|
|
9089
|
+
normalizeDocsAssistantHistory,
|
|
9090
|
+
normalizeDocsRoute,
|
|
9091
|
+
parseDocsCorpus,
|
|
7755
9092
|
parseQuotaLimit,
|
|
7756
9093
|
platformCorsPatterns,
|
|
7757
9094
|
platformWorkerFetch,
|
|
7758
9095
|
prepareMessagesWithCompaction,
|
|
7759
9096
|
priceBindingUsageEvent,
|
|
9097
|
+
readDocsPublishedPage,
|
|
9098
|
+
readDocsRoute,
|
|
7760
9099
|
readVarUint,
|
|
7761
9100
|
readVarUint8Array,
|
|
7762
9101
|
registerClientErrorRoute,
|
|
9102
|
+
registerDeepSpaceDocs,
|
|
9103
|
+
registerDocsAssistantRoutes,
|
|
9104
|
+
registerDocsMcpRoutes,
|
|
9105
|
+
registerDocsStaticRoutes,
|
|
7763
9106
|
registryErrorJson,
|
|
7764
9107
|
registryStub,
|
|
9108
|
+
releaseDocsPublicLease,
|
|
7765
9109
|
resolveAppName,
|
|
7766
9110
|
resolveColumn,
|
|
9111
|
+
resolveDeepSpaceAgentModel,
|
|
9112
|
+
resolveDocsPublishedCorpus,
|
|
7767
9113
|
rowToData,
|
|
7768
9114
|
runMigrations,
|
|
9115
|
+
searchDocsCorpus,
|
|
7769
9116
|
serverBuild,
|
|
9117
|
+
streamDeepSpaceAgent,
|
|
7770
9118
|
timingSafeEqualHex,
|
|
7771
9119
|
timingSafeEqualStrings,
|
|
7772
9120
|
toUint8Array,
|