deepspace 0.10.0 → 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.
Files changed (82) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/{chunk-DQ4G6VL6.js → chunk-T7V4GUIN.js} +283 -180
  3. package/dist/chunk-T7V4GUIN.js.map +1 -0
  4. package/dist/cli.js +1010 -6123
  5. package/dist/cli.js.map +1 -1
  6. package/dist/documentation-client-core.d.ts +18 -0
  7. package/dist/{docs-client-core.js → documentation-client-core.js} +60 -51
  8. package/dist/documentation-client-core.js.map +1 -0
  9. package/dist/documentation-default-renderer.cjs +124 -0
  10. package/dist/documentation-react.d.ts +2 -0
  11. package/dist/{docs-react.js → documentation-react.js} +8 -8
  12. package/dist/documentation-runtime.js +71 -0
  13. package/dist/documentation-server-core.d.ts +11 -0
  14. package/dist/{docs-server-core.js → documentation-server-core.js} +13 -13
  15. package/dist/documentation-server-core.js.map +1 -0
  16. package/dist/documentation.d.ts +366 -0
  17. package/dist/documentation.js +3953 -0
  18. package/dist/documentation.js.map +1 -0
  19. package/dist/feature-installer.js +145 -0
  20. package/dist/feature-installer.js.map +1 -1
  21. package/dist/index.d.ts +6 -8
  22. package/dist/index.js +5 -15
  23. package/dist/index.js.map +1 -1
  24. package/dist/public-BGRX_Cjd.d.ts +234 -0
  25. package/dist/schema.d.ts +186 -0
  26. package/dist/schema.js +610 -0
  27. package/dist/schema.js.map +1 -0
  28. package/dist/server.d.ts +19 -167
  29. package/dist/server.js +76 -1069
  30. package/dist/server.js.map +1 -1
  31. package/dist/testing.d.ts +5 -8
  32. package/dist/testing.js +48 -14
  33. package/dist/testing.js.map +1 -1
  34. package/dist/worker.d.ts +52 -173
  35. package/dist/worker.js +563 -492
  36. package/dist/worker.js.map +1 -1
  37. package/features/admin-page/src/admin-schema.ts +1 -1
  38. package/features/ai-chat/feature.json +2 -2
  39. package/features/ai-chat/src/ChatPanel.tsx +0 -1
  40. package/features/ai-chat/src/ai-chat-schema.ts +1 -2
  41. package/features/canvas/src/canvas-schema.ts +1 -1
  42. package/features/documentation/content/index.mdx +14 -0
  43. package/features/documentation/content/quickstart.mdx +12 -0
  44. package/features/documentation/documentation.json +18 -0
  45. package/features/documentation/feature.json +68 -0
  46. package/features/{docs → documents}/feature.json +27 -27
  47. package/features/{docs/src/DocsEditorPage.tsx → documents/src/DocumentsEditorPage.tsx} +74 -74
  48. package/features/{docs/src/DocsListPage.tsx → documents/src/DocumentsListPage.tsx} +102 -102
  49. package/features/{docs/src/DocsOutlinePanel.tsx → documents/src/DocumentsOutlinePanel.tsx} +9 -9
  50. package/features/{docs/src/DocsPresence.tsx → documents/src/DocumentsPresence.tsx} +18 -18
  51. package/features/{docs → documents}/src/InviteDialog.tsx +21 -21
  52. package/features/{docs → documents}/src/LibrarySidebar.tsx +25 -25
  53. package/features/documents/src/__tests__/documents-presence-access.test.ts +71 -0
  54. package/features/{docs/src/docs-library-types.ts → documents/src/documents-library-types.ts} +3 -3
  55. package/features/{docs/src/docs-schema.ts → documents/src/documents-schema.ts} +6 -6
  56. package/features/{docs/src/docs-ui.css → documents/src/documents-ui.css} +89 -89
  57. package/features/{docs → documents}/src/editor/DocEditorSurface.tsx +11 -11
  58. package/features/{docs/src/editor/DocsTiptapToolbar.tsx → documents/src/editor/DocumentsTiptapToolbar.tsx} +21 -21
  59. package/features/{docs → documents}/src/editor/useDocEditor.ts +4 -4
  60. package/features/{docs/src/use-docs-presence-access.ts → documents/src/use-documents-presence-access.ts} +51 -51
  61. package/features/file-attachments/src/attachments-schema.ts +1 -1
  62. package/features/items/src/items-schema.ts +1 -1
  63. package/features/leaderboard/src/leaderboard-schema.ts +1 -1
  64. package/features/messaging/src/ChatMultiPage.tsx +1 -1
  65. package/features/messaging/src/ChatPage.tsx +1 -1
  66. package/features/messaging/src/messaging-schema.ts +2 -2
  67. package/features/tasks/src/tasks-schema.ts +1 -1
  68. package/features/testing/src/test-schema.ts +2 -2
  69. package/package.json +12 -7
  70. package/dist/chunk-DQ4G6VL6.js.map +0 -1
  71. package/dist/docs-client-core.d.ts +0 -18
  72. package/dist/docs-client-core.js.map +0 -1
  73. package/dist/docs-react.d.ts +0 -2
  74. package/dist/docs-runtime.js +0 -71
  75. package/dist/docs-server-core.d.ts +0 -11
  76. package/dist/docs-server-core.js.map +0 -1
  77. package/dist/docs.d.ts +0 -350
  78. package/dist/docs.js +0 -5139
  79. package/dist/docs.js.map +0 -1
  80. package/dist/public-Cp1X8ZQ3.d.ts +0 -230
  81. package/features/docs/src/__tests__/docs-presence-access.test.ts +0 -71
  82. /package/dist/{docs-react.js.map → documentation-react.js.map} +0 -0
package/dist/worker.js CHANGED
@@ -340,16 +340,6 @@ 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
-
353
343
  // src/server/schemas/registry.ts
354
344
  function collectionTableName(name) {
355
345
  return `c_${name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
@@ -550,6 +540,14 @@ var SYSTEM_MANAGED_COLUMNS = /* @__PURE__ */ new Set([
550
540
  "createdAt",
551
541
  "lastSeenAt"
552
542
  ]);
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
+ ];
553
551
  var BASE_USERS_SCHEMA = {
554
552
  name: "users",
555
553
  columns: [...USERS_COLUMNS],
@@ -5107,7 +5105,8 @@ var ALLOWED_BINDING_TYPES = /* @__PURE__ */ new Set([
5107
5105
  "queue",
5108
5106
  "browser_rendering",
5109
5107
  "analytics_engine",
5110
- "hyperdrive"
5108
+ "hyperdrive",
5109
+ "ratelimit"
5111
5110
  ]);
5112
5111
  var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
5113
5112
  "ASSETS",
@@ -5115,6 +5114,7 @@ var RESERVED_BINDING_NAMES = /* @__PURE__ */ new Set([
5115
5114
  "API_WORKER",
5116
5115
  "DEEPSPACE_APP_ID",
5117
5116
  "DEEPSPACE_RESOURCE_ID",
5117
+ "DEEPSPACE_RELEASE_OPERATION_ID",
5118
5118
  "APP_NAME",
5119
5119
  "OWNER_USER_ID",
5120
5120
  "AUTH_JWT_PUBLIC_KEY",
@@ -5205,6 +5205,17 @@ function requiredFieldError(b) {
5205
5205
  return `hyperdrive binding "${b.name}": auto-provisioning not yet supported (provide a real id)`;
5206
5206
  }
5207
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;
5208
5219
  case "ai":
5209
5220
  case "browser_rendering":
5210
5221
  case "analytics_engine":
@@ -5282,6 +5293,15 @@ function bindingManifestFromOutputConfig(outputConfig) {
5282
5293
  const id = pluckString(h, "id");
5283
5294
  if (name && id) out.push({ type: "hyperdrive", name, id });
5284
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
+ }
5285
5305
  return out;
5286
5306
  }
5287
5307
  function isObject(v) {
@@ -6026,8 +6046,8 @@ function validateAppMigrationIds(value) {
6026
6046
 
6027
6047
  // src/shared/ai-models.ts
6028
6048
  var DEEPSPACE_MODEL_CATALOG_PROVENANCE = {
6029
- version: "2026-08-03",
6030
- verifiedAt: "2026-08-03",
6049
+ version: "2026-08-04",
6050
+ verifiedAt: "2026-08-04",
6031
6051
  sources: {
6032
6052
  anthropic: "https://platform.claude.com/docs/en/api/beta/models/list",
6033
6053
  openai: "https://developers.openai.com/api/docs/models/all",
@@ -6118,7 +6138,8 @@ var DEEPSPACE_AGENT_PROFILES = {
6118
6138
  application: {
6119
6139
  id: "application",
6120
6140
  defaultModel: DEEPSPACE_AI_DEFAULTS.agent,
6121
- maxSteps: 5,
6141
+ maxSteps: 21,
6142
+ maxToolCalls: 20,
6122
6143
  allowedTools: "application-defined"
6123
6144
  },
6124
6145
  documentation: {
@@ -6128,7 +6149,7 @@ var DEEPSPACE_AGENT_PROFILES = {
6128
6149
  // step. The runner disables tools once the tool-call budget is exhausted.
6129
6150
  maxSteps: 21,
6130
6151
  maxToolCalls: 20,
6131
- allowedTools: ["docs_search", "docs_read"]
6152
+ allowedTools: ["documentation_search", "documentation_read"]
6132
6153
  }
6133
6154
  };
6134
6155
  function getDeepSpaceAIModel(modelId) {
@@ -6149,45 +6170,6 @@ function resolveDeepSpaceAgentModel(modelId, profileId = "application") {
6149
6170
  return { modelId: model.id, provider: model.provider, model, profile };
6150
6171
  }
6151
6172
 
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
-
6191
6173
  // src/server/utils/cors.ts
6192
6174
  function matchOrigin(origin, patterns) {
6193
6175
  if (!origin) return null;
@@ -6732,11 +6714,7 @@ function streamDeepSpaceAgent(env, options) {
6732
6714
  }
6733
6715
  }
6734
6716
  }
6735
- const provider = createDeepSpaceAI(
6736
- env,
6737
- selection.provider,
6738
- authToken ? { authToken } : {}
6739
- );
6717
+ const provider = createDeepSpaceAI(env, selection.provider, authToken ? { authToken } : {});
6740
6718
  const { prepareStep, tools, ...remainingStreamOptions } = streamOptions;
6741
6719
  const boundedTools = limitToolExecutions(tools, selection.profile.maxToolCalls);
6742
6720
  const result = streamText({
@@ -6764,22 +6742,27 @@ function enforceToolCallLimit(prepareStep, maxToolCalls) {
6764
6742
  function limitToolExecutions(tools, maxToolCalls) {
6765
6743
  if (!tools || maxToolCalls === void 0) return tools;
6766
6744
  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
- });
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
+ }
6777
6762
  }
6778
- executions++;
6779
- return execute(...args);
6780
- }
6781
- }];
6782
- }));
6763
+ ];
6764
+ })
6765
+ );
6783
6766
  }
6784
6767
  function deepSpaceAgentErrorSummary(error, context = {}) {
6785
6768
  const details = [];
@@ -6844,7 +6827,10 @@ function safeLogText(value) {
6844
6827
  const codePoint = character.codePointAt(0) ?? 0;
6845
6828
  return codePoint <= 31 || codePoint === 127 ? " " : character;
6846
6829
  }).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);
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);
6848
6834
  }
6849
6835
 
6850
6836
  // src/server/utils/composio-tools.ts
@@ -7283,11 +7269,10 @@ function formatTranscript(messages) {
7283
7269
  }
7284
7270
  function makeDefaultSummarizer(env, options = {}) {
7285
7271
  return async (messages) => {
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 });
7272
+ const anthropic = createDeepSpaceAI(env, "anthropic", { authToken: options.authToken });
7289
7273
  const { text } = await generateText({
7290
- model: provider(model.id),
7274
+ // Stable alias — provider bug-fix snapshots roll forward without code change.
7275
+ model: anthropic("claude-haiku-4-5"),
7291
7276
  // 2500 gives the model room to finish a sentence after hitting the
7292
7277
  // "under 2000 tokens" soft limit in the prompt.
7293
7278
  maxOutputTokens: 2500,
@@ -7423,9 +7408,189 @@ async function appendMessage(stub, msg) {
7423
7408
  });
7424
7409
  }
7425
7410
 
7426
- // src/server/utils/docs-assistant.ts
7411
+ // src/server/utils/metering.ts
7412
+ function meterUsage(env, kind, fields = {}) {
7413
+ const ds = env.USAGE_EVENTS;
7414
+ if (!ds) return false;
7415
+ try {
7416
+ ds.writeDataPoint({
7417
+ indexes: [env.OWNER_USER_ID ?? "unknown"],
7418
+ blobs: [
7419
+ env.DEEPSPACE_RESOURCE_ID ?? env.APP_NAME ?? "unknown",
7420
+ kind,
7421
+ fields.id ?? "",
7422
+ fields.op ?? ""
7423
+ ],
7424
+ doubles: [fields.units ?? 0, fields.count ?? 0]
7425
+ });
7426
+ return true;
7427
+ } catch {
7428
+ return false;
7429
+ }
7430
+ }
7431
+ function meterAi(env, model, fields = {}) {
7432
+ const calls = fields.calls ?? 1;
7433
+ const inChars = fields.inputChars ?? 0;
7434
+ const outChars = fields.outputChars ?? 0;
7435
+ let ok = true;
7436
+ if (inChars > 0) {
7437
+ ok = meterUsage(env, "ai", { id: model, op: "input", units: inChars, count: calls }) && ok;
7438
+ }
7439
+ if (outChars > 0) {
7440
+ ok = meterUsage(env, "ai", { id: model, op: "output", units: outChars, count: calls }) && ok;
7441
+ }
7442
+ if (inChars === 0 && outChars === 0) {
7443
+ ok = meterUsage(env, "ai", { id: model, op: "call", units: 0, count: calls }) && ok;
7444
+ }
7445
+ return ok;
7446
+ }
7447
+ function meterVectorize(env, indexName, op, fields = {}) {
7448
+ const vectors = fields.vectors ?? 0;
7449
+ const dims = fields.dims ?? 0;
7450
+ const stored = fields.storedCount ?? 0;
7451
+ const units = op === "query" ? (vectors + stored) * dims : vectors * dims;
7452
+ return meterUsage(env, "vectorize", { id: indexName, op, units, count: vectors });
7453
+ }
7454
+ var CHARS_PER_TOKEN = 4;
7455
+ var AI_EMBED_USD_PER_M_TOKENS = 0.012;
7456
+ var VECTORIZE_QUERIED_USD_PER_M_DIMS = 0.01;
7457
+ var VECTORIZE_STORED_USD_PER_100M_DIMS = 0.05;
7458
+ var COST_RATES = {
7459
+ ai: {
7460
+ /**
7461
+ * USD per character of *embedding input* (bge-m3 / qwen3-embedding tier).
7462
+ * Renamed from `perChar` to make explicit that this rate does NOT
7463
+ * apply to LLM-generation output — see `priceBindingUsageEvent`.
7464
+ */
7465
+ embedInputPerChar: AI_EMBED_USD_PER_M_TOKENS / 1e6 / CHARS_PER_TOKEN
7466
+ },
7467
+ vectorize: {
7468
+ /** USD per queried dimension (per query, per stored vector compared). */
7469
+ queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
7470
+ /** USD per stored dimension per month. */
7471
+ storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 1e8
7472
+ }
7473
+ };
7474
+ function priceBindingUsageEvent(kind, op, units) {
7475
+ if (kind === "ai" && op === "input") {
7476
+ return units * COST_RATES.ai.embedInputPerChar;
7477
+ }
7478
+ if (kind === "vectorize" && op === "query") {
7479
+ return units * COST_RATES.vectorize.queriedPerDim;
7480
+ }
7481
+ return 0;
7482
+ }
7483
+
7484
+ // src/server/utils/d1-migrations.ts
7485
+ var META_TABLE = "_dpc_migrations";
7486
+ var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
7487
+ async function runMigrations(db, migrations) {
7488
+ await db.prepare(META_BOOTSTRAP_SQL).run();
7489
+ const row = await db.prepare(`SELECT COUNT(*) AS n FROM ${META_TABLE}`).first();
7490
+ const fromVersion = row?.n ?? 0;
7491
+ if (fromVersion >= migrations.length) {
7492
+ return { fromVersion, toVersion: fromVersion, applied: 0 };
7493
+ }
7494
+ let current = fromVersion;
7495
+ for (let i = fromVersion; i < migrations.length; i++) {
7496
+ const statements = splitStatements(migrations[i]);
7497
+ for (const stmt of statements) {
7498
+ try {
7499
+ await db.prepare(stmt).run();
7500
+ } catch (err) {
7501
+ const msg = err instanceof Error ? err.message : String(err);
7502
+ throw new Error(`Migration ${i} failed (db at version=${current}): ${msg}`);
7503
+ }
7504
+ }
7505
+ await db.prepare(`INSERT INTO ${META_TABLE} (idx, applied_at) VALUES (?, ?)`).bind(i, (/* @__PURE__ */ new Date()).toISOString()).run();
7506
+ current = i + 1;
7507
+ }
7508
+ return { fromVersion, toVersion: current, applied: current - fromVersion };
7509
+ }
7510
+ function splitStatements(sql) {
7511
+ return sql.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
7512
+ }
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
7427
7527
  import { tool as tool2 } from "ai";
7428
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
7429
7594
  var MAX_CHUNKS = 1e4;
7430
7595
  var MAX_CHUNK_CHARS = 2500;
7431
7596
  var MAX_CORPUS_CHARS = 12e6;
@@ -7472,28 +7637,28 @@ var SEARCH_STOP_WORDS = /* @__PURE__ */ new Set([
7472
7637
  "your"
7473
7638
  ]);
7474
7639
  var indexedChunkCache = /* @__PURE__ */ new WeakMap();
7475
- function parseDocsCorpus(value) {
7476
- if (!Array.isArray(value)) throw new Error("Docs assistant index must be an array");
7640
+ function parseDocumentationCorpus(value) {
7641
+ if (!Array.isArray(value)) throw new Error("Documentation assistant index must be an array");
7477
7642
  if (value.length > MAX_CHUNKS) {
7478
- throw new Error(`Docs assistant index exceeds ${MAX_CHUNKS} chunks`);
7643
+ throw new Error(`Documentation assistant index exceeds ${MAX_CHUNKS} chunks`);
7479
7644
  }
7480
7645
  const chunks = [];
7481
7646
  let totalChars2 = 0;
7482
7647
  for (let index = 0; index < value.length; index++) {
7483
7648
  const raw = value[index];
7484
7649
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
7485
- throw new Error(`Docs assistant chunk ${index} must be an object`);
7650
+ throw new Error(`Documentation assistant chunk ${index} must be an object`);
7486
7651
  }
7487
7652
  const chunk = raw;
7488
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) {
7489
- throw new Error(`Docs assistant chunk ${index} has invalid required fields`);
7654
+ throw new Error(`Documentation assistant chunk ${index} has invalid required fields`);
7490
7655
  }
7491
7656
  if (chunk.text.length > MAX_CHUNK_CHARS) {
7492
- throw new Error(`Docs assistant chunk ${chunk.id} exceeds ${MAX_CHUNK_CHARS} characters`);
7657
+ throw new Error(`Documentation assistant chunk ${chunk.id} exceeds ${MAX_CHUNK_CHARS} characters`);
7493
7658
  }
7494
7659
  totalChars2 += chunk.text.length;
7495
7660
  if (totalChars2 > MAX_CORPUS_CHARS) {
7496
- throw new Error(`Docs assistant corpus exceeds ${MAX_CORPUS_CHARS} characters`);
7661
+ throw new Error(`Documentation assistant corpus exceeds ${MAX_CORPUS_CHARS} characters`);
7497
7662
  }
7498
7663
  chunks.push({
7499
7664
  id: chunk.id,
@@ -7505,7 +7670,7 @@ function parseDocsCorpus(value) {
7505
7670
  }
7506
7671
  return chunks;
7507
7672
  }
7508
- function searchDocsCorpus(corpus, query, limit = 6) {
7673
+ function searchDocumentationCorpus(corpus, query, limit = 6) {
7509
7674
  const normalized = normalize(query);
7510
7675
  const terms = [
7511
7676
  ...new Set(
@@ -7539,8 +7704,8 @@ function searchDocsCorpus(corpus, query, limit = 6) {
7539
7704
  };
7540
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)));
7541
7706
  }
7542
- function readDocsRoute(corpus, route) {
7543
- const normalizedRoute = normalizeDocsRoute(route);
7707
+ function readDocumentationRoute(corpus, route) {
7708
+ const normalizedRoute = collapseRoute(route);
7544
7709
  return corpus.filter((chunk) => chunk.route === normalizedRoute).slice(0, 12).map(({ route: chunkRoute, title, heading, text }) => ({
7545
7710
  route: chunkRoute,
7546
7711
  title,
@@ -7548,40 +7713,44 @@ function readDocsRoute(corpus, route) {
7548
7713
  text
7549
7714
  }));
7550
7715
  }
7551
- function buildDocsAssistantTools(corpus) {
7716
+ function buildDocumentationAssistantTools(corpus, basePath = "/docs") {
7552
7717
  return {
7553
- docs_search: tool2({
7718
+ documentation_search: tool2({
7554
7719
  description: "Search the current documentation. Use this before answering factual or how-to questions.",
7555
7720
  inputSchema: z.object({
7556
7721
  query: z.string().trim().min(2).max(300),
7557
7722
  limit: z.number().int().min(1).max(10).optional()
7558
7723
  }),
7559
7724
  execute: async ({ query, limit }) => ({
7560
- results: searchDocsCorpus(corpus, query, limit ?? 6)
7725
+ results: searchDocumentationCorpus(corpus, query, limit ?? 6).map((result) => ({
7726
+ ...result,
7727
+ url: documentationPublicPath(basePath, result.route)
7728
+ }))
7561
7729
  })
7562
7730
  }),
7563
- docs_read: tool2({
7564
- description: "Read bounded source chunks for one exact documentation route returned by docs_search.",
7731
+ documentation_read: tool2({
7732
+ description: "Read bounded source chunks for one exact documentation route returned by documentation_search.",
7565
7733
  inputSchema: z.object({ route: z.string().trim().startsWith("/").max(500) }),
7566
7734
  execute: async ({ route }) => ({
7567
- route: normalizeDocsRoute(route),
7568
- chunks: readDocsRoute(corpus, route)
7735
+ route: collapseRoute(route),
7736
+ url: documentationPublicPath(basePath, route),
7737
+ chunks: readDocumentationRoute(corpus, route)
7569
7738
  })
7570
7739
  })
7571
7740
  };
7572
7741
  }
7573
- function buildDocsAssistantPrompt(appName, currentRoute) {
7742
+ function buildDocumentationAssistantPrompt(appName, currentRoute) {
7574
7743
  return [
7575
7744
  `You are the public documentation assistant for "${appName}" on DeepSpace.`,
7576
- "Answer only from the documentation available through docs_search and docs_read.",
7745
+ "Answer only from the documentation available through documentation_search and documentation_read.",
7577
7746
  "Search before answering unless the user is only greeting you.",
7578
- "Use docs_read when search excerpts are insufficient or ambiguous.",
7747
+ "Use documentation_read when search excerpts are insufficient or ambiguous.",
7579
7748
  "Never claim access to application records, integrations, private data, or mutation tools.",
7580
7749
  "If the documentation does not support an answer, say so plainly and suggest the closest documented page.",
7581
7750
  "Cite factual answers with Markdown links to the exact documentation routes returned by tools.",
7582
7751
  "Do not invent URLs, APIs, configuration keys, versions, or behavior.",
7583
7752
  "Keep answers concise, practical, and explicit about uncertainty.",
7584
- ...currentRoute ? [`The reader is currently viewing ${normalizeDocsRoute(currentRoute)}.`] : []
7753
+ ...currentRoute ? [`The reader is currently viewing ${collapseRoute(currentRoute)}.`] : []
7585
7754
  ].join("\n");
7586
7755
  }
7587
7756
  function normalize(value) {
@@ -7589,7 +7758,7 @@ function normalize(value) {
7589
7758
  }
7590
7759
  function canonicalSearchTerm(term) {
7591
7760
  if (SEARCH_STOP_WORDS.has(term)) return term;
7592
- if (["doc", "docs", "document", "documents", "documentation"].includes(term)) return "docs";
7761
+ if (["doc", "docs", "document", "documents", "documentation"].includes(term)) return "documentation";
7593
7762
  if (["builds", "building", "built", "compile", "compiled", "compiles", "compiling"].includes(term)) {
7594
7763
  return "build";
7595
7764
  }
@@ -7609,11 +7778,6 @@ function indexChunk(chunk) {
7609
7778
  indexedChunkCache.set(chunk, indexed);
7610
7779
  return indexed;
7611
7780
  }
7612
- function normalizeDocsRoute(value) {
7613
- let route = `/${value}`.replace(/\/{2,}/g, "/");
7614
- if (route.length > 1) route = route.replace(/\/$/, "");
7615
- return route;
7616
- }
7617
7781
  function countTerms(tokens, required) {
7618
7782
  const counts = /* @__PURE__ */ new Map();
7619
7783
  for (const token of tokens) {
@@ -7656,130 +7820,67 @@ function excerpt(text, terms) {
7656
7820
  return `${start > 0 ? "\u2026" : ""}${text.slice(start, end).trim()}${end < text.length ? "\u2026" : ""}`;
7657
7821
  }
7658
7822
 
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
7823
+ // src/documentation/worker/published-corpus.ts
7742
7824
  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());
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
+ );
7745
7829
  if (!response.ok) return null;
7746
7830
  const value = await response.json().catch(() => null);
7747
7831
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
7748
7832
  const manifest = value;
7749
7833
  const assistant = parseAssistantPolicy(manifest.assistant);
7750
- if (typeof manifest.sourceHash !== "string" || typeof manifest.name !== "string" || !assistant) return null;
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;
7751
7839
  return {
7752
7840
  sourceHash: manifest.sourceHash,
7753
7841
  name: manifest.name,
7842
+ routes,
7843
+ resources,
7754
7844
  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" }
7845
+ mcp
7758
7846
  };
7759
7847
  }
7760
- async function loadDocsPublishedCorpus(env, sourceHash, requestUrl = "https://assets.local/") {
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") {
7761
7859
  if (cachedCorpus?.sourceHash === sourceHash) return cachedCorpus.chunks;
7762
7860
  const response = await env.ASSETS.fetch(
7763
- new URL("/_docs/assistant-index.json", requestUrl).toString()
7861
+ new Request(new URL(`${assetBasePath}/assistant-index.json`, requestUrl))
7764
7862
  );
7765
7863
  if (!response.ok) throw new Error(`assistant-index.json returned ${response.status}`);
7766
- const chunks = parseDocsCorpus(await response.json());
7864
+ const chunks = parseDocumentationCorpus(await response.json());
7767
7865
  cachedCorpus = { sourceHash, chunks };
7768
7866
  return chunks;
7769
7867
  }
7770
- async function resolveDocsPublishedCorpus(env, sourceHash, requestUrl = "https://assets.local/") {
7868
+ async function resolveDocumentationPublishedCorpus(env, sourceHash, requestUrl = "https://assets.local/", assetBasePath = "/_documentation") {
7771
7869
  try {
7772
- return { ok: true, chunks: await loadDocsPublishedCorpus(env, sourceHash, requestUrl) };
7870
+ return {
7871
+ ok: true,
7872
+ chunks: await loadDocumentationPublishedCorpus(env, sourceHash, requestUrl, assetBasePath)
7873
+ };
7773
7874
  } catch (error) {
7774
7875
  return { ok: false, error };
7775
7876
  }
7776
7877
  }
7777
- async function readDocsPublishedPage(env, corpus, requestedRoute, requestUrl = "https://assets.local/") {
7778
- const route = normalizeDocsRoute(requestedRoute.split("#", 1)[0] ?? "/");
7878
+ async function readDocumentationPublishedPage(env, corpus, requestedRoute, requestUrl = "https://assets.local/", assetBasePath = "/_documentation") {
7879
+ const route = collapseRoute(requestedRoute.split("#", 1)[0] ?? "/");
7779
7880
  const firstChunk = corpus.find((chunk) => chunk.route === route);
7780
7881
  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());
7882
+ const pathname = route === "/" ? `${assetBasePath}/index.md` : `${assetBasePath}${route}.md`;
7883
+ const response = await env.ASSETS.fetch(new Request(new URL(pathname, requestUrl)));
7783
7884
  if (!response.ok) return null;
7784
7885
  const markdown = await response.text();
7785
7886
  if (markdown.length > 5e5) throw new Error("Documentation page exceeds 500000 characters");
@@ -7801,40 +7902,38 @@ function parseMcpPolicy(value) {
7801
7902
  return { access: policy.access };
7802
7903
  }
7803
7904
 
7804
- // src/server/utils/docs-public-limiter.ts
7805
- async function docsPublicClientKey(value) {
7905
+ // src/documentation/worker/public-limiter.ts
7906
+ async function documentationPublicClientKey(value) {
7806
7907
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
7807
7908
  return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
7808
7909
  }
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;
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
7820
7933
  });
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
7934
  }
7836
7935
 
7837
- // src/server/utils/request-body.ts
7936
+ // src/documentation/worker/request-body.ts
7838
7937
  var RequestBodyTooLargeError = class extends Error {
7839
7938
  constructor(maxBytes) {
7840
7939
  super(`Request body exceeds ${maxBytes} bytes`);
@@ -7868,15 +7967,14 @@ async function readBoundedRequestText(request, maxBytes) {
7868
7967
  return text + decoder.decode();
7869
7968
  }
7870
7969
 
7871
- // src/server/utils/docs-assistant-routes.ts
7970
+ // src/documentation/worker/assistant-routes.ts
7872
7971
  var MAX_QUESTION_CHARS = 4e3;
7873
7972
  var MAX_HISTORY_MESSAGES = 12;
7874
7973
  var MAX_HISTORY_CHARS = 2e4;
7875
7974
  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);
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);
7880
7978
  if (!manifest || manifest.assistant.access === "disabled") return c.notFound();
7881
7979
  let authToken;
7882
7980
  let publicClientKey;
@@ -7890,7 +7988,19 @@ function registerDocsAssistantRoutes(app, resolveAuth) {
7890
7988
  if (!c.env.APP_OWNER_JWT) {
7891
7989
  return c.json({ error: "Public documentation assistant billing is not configured" }, 503);
7892
7990
  }
7893
- publicClientKey = await docsPublicClientKey(c.req.header("CF-Connecting-IP") ?? "unknown");
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
+ );
7894
8004
  }
7895
8005
  let body = null;
7896
8006
  try {
@@ -7913,22 +8023,27 @@ function registerDocsAssistantRoutes(app, resolveAuth) {
7913
8023
  const route = typeof body?.route === "string" && body.route.startsWith("/") ? body.route.slice(0, 500) : void 0;
7914
8024
  let history;
7915
8025
  try {
7916
- history = normalizeDocsAssistantHistory(body?.messages);
8026
+ history = normalizeDocumentationAssistantHistory(body?.messages);
7917
8027
  } catch (error) {
7918
8028
  return c.json({ error: error instanceof Error ? error.message : "messages are invalid" }, 400);
7919
8029
  }
7920
- const corpusResult = await resolveDocsPublishedCorpus(c.env, manifest.sourceHash, c.req.url);
8030
+ const corpusResult = await resolveDocumentationPublishedCorpus(
8031
+ c.env,
8032
+ manifest.sourceHash,
8033
+ c.req.url,
8034
+ assetBasePath
8035
+ );
7921
8036
  if (!corpusResult.ok) {
7922
- console.error("[docs-assistant] corpus unavailable:", corpusResult.error);
8037
+ console.error("[documentation-assistant] corpus unavailable:", corpusResult.error);
7923
8038
  return c.json({ error: "Documentation corpus is unavailable" }, 503);
7924
8039
  }
7925
8040
  const corpus = corpusResult.chunks;
7926
- const selectedModel = resolveDeepSpaceAgentModel(
7927
- manifest.assistant.model,
7928
- "documentation"
7929
- );
8041
+ const selectedModel = resolveDeepSpaceAgentModel(manifest.assistant.model, "documentation");
7930
8042
  if (!selectedModel) {
7931
- console.error("[docs-assistant] unsupported configured model:", manifest.assistant.model);
8043
+ console.error(
8044
+ "[documentation-assistant] unsupported configured model:",
8045
+ manifest.assistant.model
8046
+ );
7932
8047
  return c.json({ error: "Documentation assistant model is not supported" }, 503);
7933
8048
  }
7934
8049
  const diagnosticContext = {
@@ -7936,66 +8051,40 @@ function registerDocsAssistantRoutes(app, resolveAuth) {
7936
8051
  provider: selectedModel.provider,
7937
8052
  modelId: selectedModel.modelId
7938
8053
  };
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
8054
  let result;
7960
8055
  try {
7961
8056
  result = streamDeepSpaceAgent(c.env, {
7962
8057
  profile: "documentation",
7963
8058
  modelId: selectedModel.modelId,
7964
8059
  ...authToken ? { authToken } : {},
7965
- system: buildDocsAssistantPrompt(manifest.name || c.env.APP_NAME, route),
8060
+ system: buildDocumentationAssistantPrompt(manifest.name || c.env.APP_NAME, route),
7966
8061
  messages: [...history, { role: "user", content: question }],
7967
- tools: buildDocsAssistantTools(corpus),
8062
+ tools: buildDocumentationAssistantTools(corpus, publicBasePath),
7968
8063
  abortSignal: c.req.raw.signal,
7969
- onAbort: () => releaseLease(),
7970
- onFinish: releaseLease,
7971
8064
  onError: ({ error }) => {
7972
8065
  console.error(
7973
- `[docs-assistant] stream error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
8066
+ `[documentation-assistant] stream error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
7974
8067
  );
7975
- void releaseLease();
7976
8068
  }
7977
8069
  }).result;
7978
8070
  } catch (error) {
7979
8071
  console.error(
7980
- `[docs-assistant] agent unavailable: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
8072
+ `[documentation-assistant] agent unavailable: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
7981
8073
  );
7982
- await releaseLease();
7983
8074
  return c.json({ error: "Documentation assistant is not configured" }, 503);
7984
8075
  }
7985
8076
  return result.toUIMessageStreamResponse({
7986
8077
  sendReasoning: false,
7987
8078
  onError: (error) => {
7988
8079
  console.error(
7989
- `[docs-assistant] response error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
8080
+ `[documentation-assistant] response error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
7990
8081
  );
7991
- void releaseLease();
7992
8082
  return "The documentation assistant could not complete this response.";
7993
8083
  }
7994
8084
  });
7995
8085
  });
7996
8086
  }
7997
- var docsAssistantErrorSummary = deepSpaceAgentErrorSummary;
7998
- function normalizeDocsAssistantHistory(value) {
8087
+ function normalizeDocumentationAssistantHistory(value) {
7999
8088
  if (value === void 0) return [];
8000
8089
  if (!Array.isArray(value)) throw new TypeError("messages must be an array");
8001
8090
  if (value.length > MAX_HISTORY_MESSAGES) {
@@ -8028,21 +8117,62 @@ function normalizeDocsAssistantHistory(value) {
8028
8117
  }
8029
8118
  return messages;
8030
8119
  }
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;
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
+ }
8036
8137
  if (status === 404) {
8037
8138
  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));
8139
+ url.pathname = `${assetBasePath}/404.html`;
8140
+ response = await fetchDocumentationAsset(c.env, c.req.raw, url, assetBasePath);
8040
8141
  status = 404;
8041
8142
  }
8042
- return secureDocsResponse(response, status, pathname);
8043
- });
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`;
8044
8174
  }
8045
- function secureDocsResponse(response, status, pathname) {
8175
+ function secureDocumentationResponse(response, status, pathname) {
8046
8176
  const headers = new Headers(response.headers);
8047
8177
  if (status !== 404 && pathname.endsWith(".md")) {
8048
8178
  headers.set("Content-Type", "text/markdown; charset=utf-8");
@@ -8062,7 +8192,14 @@ function secureDocsResponse(response, status, pathname) {
8062
8192
  return new Response(response.body, { status, headers });
8063
8193
  }
8064
8194
 
8065
- // src/server/utils/docs-mcp/protocol.ts
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
8066
8203
  var MCP_SERVER_VERSION = "1.0.0";
8067
8204
  var CURRENT_PROTOCOL_VERSION = "2025-11-25";
8068
8205
  var SUPPORTED_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PROTOCOL_VERSION, "2025-06-18", "2025-03-26"]);
@@ -8077,11 +8214,11 @@ function initializeResponse(request) {
8077
8214
  resources: { subscribe: false, listChanged: false }
8078
8215
  },
8079
8216
  serverInfo: {
8080
- name: "DeepSpace Docs",
8217
+ name: "DeepSpace Documentation",
8081
8218
  title: "DeepSpace documentation",
8082
8219
  version: MCP_SERVER_VERSION
8083
8220
  },
8084
- instructions: "Search the published docs before answering, then read exact pages when full context is needed."
8221
+ instructions: "Search the published documentation before answering, then read exact pages when full context is needed."
8085
8222
  });
8086
8223
  }
8087
8224
  function hasValidOrigin(request) {
@@ -8156,10 +8293,10 @@ function mcpSecurityHeaders() {
8156
8293
  };
8157
8294
  }
8158
8295
 
8159
- // src/server/utils/docs-mcp/tools.ts
8160
- var DOCS_TOOLS = [
8296
+ // src/documentation/worker/mcp/tools.ts
8297
+ var DOCUMENTATION_TOOLS = [
8161
8298
  {
8162
- name: "docs_search",
8299
+ name: "documentation_search",
8163
8300
  title: "Search documentation",
8164
8301
  description: "Search the published documentation and return ranked, linked excerpts.",
8165
8302
  inputSchema: {
@@ -8184,7 +8321,7 @@ var DOCS_TOOLS = [
8184
8321
  }
8185
8322
  },
8186
8323
  {
8187
- name: "docs_read",
8324
+ name: "documentation_read",
8188
8325
  title: "Read documentation page",
8189
8326
  description: "Read one complete published documentation page as Markdown.",
8190
8327
  inputSchema: {
@@ -8213,59 +8350,67 @@ var DOCS_TOOLS = [
8213
8350
  }
8214
8351
  }
8215
8352
  ];
8216
- async function callDocsTool(rawRequest, request, corpus, env) {
8353
+ async function callDocumentationTool(rawRequest, request, corpus, env, basePath = "/docs", assetBasePath = "/_documentation") {
8217
8354
  const params = objectValue(request.params);
8218
8355
  const name = typeof params?.name === "string" ? params.name : "";
8219
8356
  const args = objectValue(params?.arguments) ?? {};
8220
8357
  const origin = new URL(rawRequest.url).origin;
8221
- if (name === "docs_search") {
8358
+ if (name === "documentation_search") {
8222
8359
  const query = typeof args.query === "string" ? args.query.trim() : "";
8223
8360
  const limit = args.limit === void 0 ? 6 : args.limit;
8224
8361
  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");
8362
+ return mcpError(request.id, -32602, "Invalid documentation_search arguments");
8226
8363
  }
8227
8364
  const value = {
8228
- results: searchDocsCorpus(corpus, query, Number(limit)).map((result) => ({
8365
+ results: searchDocumentationCorpus(corpus, query, Number(limit)).map((result) => ({
8229
8366
  ...result,
8230
- url: new URL(result.route, origin).toString()
8367
+ url: new URL(documentationPublicPath(basePath, result.route), origin).toString()
8231
8368
  }))
8232
8369
  };
8233
8370
  return toolResult(request.id, value);
8234
8371
  }
8235
- if (name === "docs_read") {
8372
+ if (name === "documentation_read") {
8236
8373
  const route = typeof args.route === "string" ? args.route.trim() : "";
8237
8374
  if (!route.startsWith("/") || route.length > 500) {
8238
- return mcpError(request.id, -32602, "Invalid docs_read arguments");
8375
+ return mcpError(request.id, -32602, "Invalid documentation_read arguments");
8239
8376
  }
8240
8377
  try {
8241
- const page = await readDocsPublishedPage(env, corpus, route, rawRequest.url);
8378
+ const page = await readDocumentationPublishedPage(
8379
+ env,
8380
+ corpus,
8381
+ route,
8382
+ rawRequest.url,
8383
+ assetBasePath
8384
+ );
8242
8385
  if (!page) return toolError(request.id, `No published documentation page exists at ${route}`);
8243
8386
  return toolResult(request.id, {
8244
8387
  ...page,
8245
- url: new URL(page.route, origin).toString()
8388
+ url: new URL(documentationPublicPath(basePath, page.route), origin).toString()
8246
8389
  });
8247
8390
  } catch (error) {
8248
- console.error("[docs-mcp] page unavailable:", error);
8391
+ console.error("[documentation-mcp] page unavailable:", error);
8249
8392
  return toolError(request.id, "The documentation page could not be read");
8250
8393
  }
8251
8394
  }
8252
8395
  return mcpError(request.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
8253
8396
  }
8254
8397
 
8255
- // src/server/utils/docs-mcp-routes.ts
8398
+ // src/documentation/worker/mcp-routes.ts
8256
8399
  var MAX_REQUEST_CHARS = 64e3;
8257
8400
  var MAX_REQUEST_BYTES2 = MAX_REQUEST_CHARS * 4;
8258
8401
  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();
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();
8262
8407
  return new Response(null, {
8263
8408
  status: 405,
8264
8409
  headers: { Allow: "POST", ...mcpSecurityHeaders() }
8265
8410
  });
8266
8411
  });
8267
- app.post("/mcp", async (c) => {
8268
- const manifest = await publicManifest(c.req.raw, c.env);
8412
+ app.post(mcpPath, async (c) => {
8413
+ const manifest = await publicManifest(c.req.raw, c.env, assetBasePath);
8269
8414
  if (!manifest) return c.notFound();
8270
8415
  if (!hasValidOrigin(c.req.raw)) {
8271
8416
  return mcpError(null, -32e3, "Invalid Origin", 403);
@@ -8297,67 +8442,89 @@ function registerDocsMcpRoutes(app) {
8297
8442
  if (request.method === "initialize") return initializeResponse(requestWithId);
8298
8443
  if (request.method === "ping") return mcpResult(requestId, {});
8299
8444
  if (request.method === "tools/list") {
8300
- return mcpResult(requestId, { tools: DOCS_TOOLS });
8445
+ return mcpResult(requestId, { tools: DOCUMENTATION_TOOLS });
8301
8446
  }
8302
8447
  if (request.method === "resources/list") {
8303
- return mcpResult(requestId, { resources: [skillResource(c.req.raw, manifest)] });
8448
+ return mcpResult(requestId, {
8449
+ resources: [skillResource(c.req.raw, manifest, publicBasePath)]
8450
+ });
8304
8451
  }
8305
8452
  if (request.method === "resources/read") {
8306
- return readSkillResource(c.req.raw, requestWithId, manifest, c.env);
8453
+ return readSkillResource(
8454
+ c.req.raw,
8455
+ requestWithId,
8456
+ manifest,
8457
+ c.env,
8458
+ publicBasePath,
8459
+ assetBasePath
8460
+ );
8307
8461
  }
8308
8462
  if (request.method !== "tools/call") {
8309
8463
  return mcpError(requestId, -32601, `Method not found: ${request.method}`);
8310
8464
  }
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) {
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) {
8320
8470
  return mcpError(
8321
8471
  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
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
8326
8476
  );
8327
8477
  }
8328
- try {
8329
- return await callDocsTool(c.req.raw, requestWithId, corpus, c.env);
8330
- } finally {
8331
- await releaseDocsPublicLease(leaseResult.lease, "docs-mcp");
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);
8332
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
+ );
8333
8497
  });
8334
8498
  for (const path of [
8335
8499
  "/.well-known/mcp",
8336
8500
  "/.well-known/mcp.json",
8337
8501
  "/.well-known/mcp/server-card.json"
8338
8502
  ]) {
8339
- app.get(path, async (c) => {
8340
- const manifest = await publicManifest(c.req.raw, c.env);
8503
+ app.get(documentationPublicPath(publicBasePath, path), async (c) => {
8504
+ const manifest = await publicManifest(c.req.raw, c.env, assetBasePath);
8341
8505
  if (!manifest) return c.notFound();
8342
- return discoveryResponse(c.req.raw, manifest);
8506
+ return discoveryResponse(c.req.raw, manifest, publicBasePath);
8343
8507
  });
8344
8508
  }
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
- });
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
+ );
8352
8519
  }
8353
- async function readSkillResource(rawRequest, request, manifest, env) {
8520
+ async function readSkillResource(rawRequest, request, manifest, env, publicBasePath, assetBasePath) {
8354
8521
  const params = objectValue(request.params);
8355
8522
  const requestedUri = typeof params?.uri === "string" ? params.uri : "";
8356
- const resource = skillResource(rawRequest, manifest);
8523
+ const resource = skillResource(rawRequest, manifest, publicBasePath);
8357
8524
  if (requestedUri !== resource.uri) {
8358
8525
  return mcpError(request.id, -32602, "Unknown documentation resource");
8359
8526
  }
8360
- const assetUrl = new URL("/_docs/skill.md", rawRequest.url);
8527
+ const assetUrl = new URL(`${assetBasePath}/skill.md`, rawRequest.url);
8361
8528
  const response = await env.ASSETS.fetch(new Request(assetUrl, { method: "GET" }));
8362
8529
  if (!response.ok) return mcpError(request.id, -32002, "Documentation skill is unavailable", 503);
8363
8530
  const text = await response.text();
@@ -8368,158 +8535,83 @@ async function readSkillResource(rawRequest, request, manifest, env) {
8368
8535
  contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
8369
8536
  });
8370
8537
  }
8371
- function skillResource(request, manifest) {
8538
+ function skillResource(request, manifest, publicBasePath) {
8539
+ const subject = documentationSubject(manifest.name);
8372
8540
  return {
8373
- uri: new URL("/skill.md", request.url).toString(),
8374
- name: `${manifest.name} documentation skill`,
8541
+ uri: new URL(`${publicBasePath}/skill.md`, request.url).toString(),
8542
+ name: `${subject} skill`,
8375
8543
  title: `Use ${manifest.name}`,
8376
- description: `Agent instructions grounded in the published ${manifest.name} documentation.`,
8544
+ description: `Agent instructions grounded in the published ${subject}.`,
8377
8545
  mimeType: "text/markdown"
8378
8546
  };
8379
8547
  }
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);
8548
+ async function publicManifest(request, env, assetBasePath) {
8549
+ const manifest = await loadDocumentationPublishedManifest(env, request.url, assetBasePath);
8383
8550
  return manifest?.mcp.access === "public" ? manifest : null;
8384
8551
  }
8385
- function discoveryResponse(request, manifest) {
8386
- return jsonResponse(serverCard(request, manifest), 200, {
8552
+ function discoveryResponse(request, manifest, publicBasePath) {
8553
+ return jsonResponse(serverCard(request, manifest, publicBasePath), 200, {
8387
8554
  "Cache-Control": "public, max-age=300"
8388
8555
  });
8389
8556
  }
8390
- function serverCard(request, manifest) {
8557
+ function serverCard(request, manifest, publicBasePath) {
8391
8558
  return {
8392
8559
  name: manifest.name,
8393
- description: `Search and read the published ${manifest.name} documentation.`,
8560
+ description: `Search and read the published ${documentationSubject(manifest.name)}.`,
8394
8561
  version: MCP_SERVER_VERSION,
8395
8562
  protocolVersion: CURRENT_PROTOCOL_VERSION,
8396
8563
  transport: {
8397
8564
  type: "streamable-http",
8398
- url: new URL("/mcp", request.url).toString()
8565
+ url: new URL(`${publicBasePath}/mcp`, request.url).toString()
8399
8566
  },
8400
8567
  authentication: { required: false },
8401
8568
  capabilities: {
8402
- tools: DOCS_TOOLS.map(({ name, title, description }) => ({ name, title, description })),
8403
- resources: [skillResource(request, manifest)]
8569
+ tools: DOCUMENTATION_TOOLS.map(({ name, title, description }) => ({
8570
+ name,
8571
+ title,
8572
+ description
8573
+ })),
8574
+ resources: [skillResource(request, manifest, publicBasePath)]
8404
8575
  }
8405
8576
  };
8406
8577
  }
8407
8578
 
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
-
8422
- // src/server/utils/metering.ts
8423
- function meterUsage(env, kind, fields = {}) {
8424
- const ds = env.USAGE_EVENTS;
8425
- if (!ds) return false;
8426
- try {
8427
- ds.writeDataPoint({
8428
- indexes: [env.OWNER_USER_ID ?? "unknown"],
8429
- blobs: [
8430
- env.DEEPSPACE_RESOURCE_ID ?? env.APP_NAME ?? "unknown",
8431
- kind,
8432
- fields.id ?? "",
8433
- fields.op ?? ""
8434
- ],
8435
- doubles: [fields.units ?? 0, fields.count ?? 0]
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);
8436
8598
  });
8437
- return true;
8438
- } catch {
8439
- return false;
8440
8599
  }
8600
+ registerDocumentationAssistantRoutes(app, options.resolveAuth);
8601
+ registerDocumentationMcpRoutes(app);
8602
+ registerDocumentationStaticRoutes(app);
8441
8603
  }
8442
- function meterAi(env, model, fields = {}) {
8443
- const calls = fields.calls ?? 1;
8444
- const inChars = fields.inputChars ?? 0;
8445
- const outChars = fields.outputChars ?? 0;
8446
- let ok = true;
8447
- if (inChars > 0) {
8448
- ok = meterUsage(env, "ai", { id: model, op: "input", units: inChars, count: calls }) && ok;
8449
- }
8450
- if (outChars > 0) {
8451
- ok = meterUsage(env, "ai", { id: model, op: "output", units: outChars, count: calls }) && ok;
8452
- }
8453
- if (inChars === 0 && outChars === 0) {
8454
- ok = meterUsage(env, "ai", { id: model, op: "call", units: 0, count: calls }) && ok;
8455
- }
8456
- return ok;
8457
- }
8458
- function meterVectorize(env, indexName, op, fields = {}) {
8459
- const vectors = fields.vectors ?? 0;
8460
- const dims = fields.dims ?? 0;
8461
- const stored = fields.storedCount ?? 0;
8462
- const units = op === "query" ? (vectors + stored) * dims : vectors * dims;
8463
- return meterUsage(env, "vectorize", { id: indexName, op, units, count: vectors });
8464
- }
8465
- var CHARS_PER_TOKEN = 4;
8466
- var AI_EMBED_USD_PER_M_TOKENS = 0.012;
8467
- var VECTORIZE_QUERIED_USD_PER_M_DIMS = 0.01;
8468
- var VECTORIZE_STORED_USD_PER_100M_DIMS = 0.05;
8469
- var COST_RATES = {
8470
- ai: {
8471
- /**
8472
- * USD per character of *embedding input* (bge-m3 / qwen3-embedding tier).
8473
- * Renamed from `perChar` to make explicit that this rate does NOT
8474
- * apply to LLM-generation output — see `priceBindingUsageEvent`.
8475
- */
8476
- embedInputPerChar: AI_EMBED_USD_PER_M_TOKENS / 1e6 / CHARS_PER_TOKEN
8477
- },
8478
- vectorize: {
8479
- /** USD per queried dimension (per query, per stored vector compared). */
8480
- queriedPerDim: VECTORIZE_QUERIED_USD_PER_M_DIMS / 1e6,
8481
- /** USD per stored dimension per month. */
8482
- storedPerDimPerMonth: VECTORIZE_STORED_USD_PER_100M_DIMS / 1e8
8483
- }
8484
- };
8485
- function priceBindingUsageEvent(kind, op, units) {
8486
- if (kind === "ai" && op === "input") {
8487
- return units * COST_RATES.ai.embedInputPerChar;
8488
- }
8489
- if (kind === "vectorize" && op === "query") {
8490
- return units * COST_RATES.vectorize.queriedPerDim;
8491
- }
8492
- return 0;
8493
- }
8494
-
8495
- // src/server/utils/d1-migrations.ts
8496
- var META_TABLE = "_dpc_migrations";
8497
- var META_BOOTSTRAP_SQL = `CREATE TABLE IF NOT EXISTS ${META_TABLE} (idx INTEGER PRIMARY KEY, applied_at TEXT NOT NULL)`;
8498
- async function runMigrations(db, migrations) {
8499
- await db.prepare(META_BOOTSTRAP_SQL).run();
8500
- const row = await db.prepare(`SELECT COUNT(*) AS n FROM ${META_TABLE}`).first();
8501
- const fromVersion = row?.n ?? 0;
8502
- if (fromVersion >= migrations.length) {
8503
- return { fromVersion, toVersion: fromVersion, applied: 0 };
8504
- }
8505
- let current = fromVersion;
8506
- for (let i = fromVersion; i < migrations.length; i++) {
8507
- const statements = splitStatements(migrations[i]);
8508
- for (const stmt of statements) {
8509
- try {
8510
- await db.prepare(stmt).run();
8511
- } catch (err) {
8512
- const msg = err instanceof Error ? err.message : String(err);
8513
- throw new Error(`Migration ${i} failed (db at version=${current}): ${msg}`);
8514
- }
8515
- }
8516
- await db.prepare(`INSERT INTO ${META_TABLE} (idx, applied_at) VALUES (?, ?)`).bind(i, (/* @__PURE__ */ new Date()).toISOString()).run();
8517
- current = i + 1;
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;
8518
8613
  }
8519
- return { fromVersion, toVersion: current, applied: current - fromVersion };
8520
- }
8521
- function splitStatements(sql) {
8522
- return sql.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
8614
+ return /^\/_documentation(?:-root)?(?:\/|$)/u.test(decoded.replace(/\/{2,}/gu, "/"));
8523
8615
  }
8524
8616
 
8525
8617
  // src/server/auth/jwtVerifier.ts
@@ -8975,7 +9067,6 @@ export {
8975
9067
  DIRECTORY_SCHEMAS,
8976
9068
  DeepSpaceAgentModelError,
8977
9069
  DeepSpaceAgentProfileError,
8978
- DocsAssistantLimiter,
8979
9070
  GLOBAL_DO_TYPES,
8980
9071
  GLOBAL_DO_TYPE_NAMES,
8981
9072
  GameRoom,
@@ -9011,7 +9102,6 @@ export {
9011
9102
  VOTING_SCHEMAS,
9012
9103
  WORKSPACE_SCHEMAS,
9013
9104
  YjsRoom,
9014
- acquireDocsPublicLease,
9015
9105
  analyzePermissions,
9016
9106
  apiWorkerFetch,
9017
9107
  appendMessage,
@@ -9020,8 +9110,6 @@ export {
9020
9110
  authWorkerFetch,
9021
9111
  bindingManifestFromOutputConfig,
9022
9112
  buildCronContext,
9023
- buildDocsAssistantPrompt,
9024
- buildDocsAssistantTools,
9025
9113
  buildTableSelect,
9026
9114
  buildUiParts,
9027
9115
  callRegistry,
@@ -9050,9 +9138,6 @@ export {
9050
9138
  deepSpaceAgentErrorSummary,
9051
9139
  deleteChatCascade,
9052
9140
  dispatch,
9053
- docsAssistantErrorSummary,
9054
- docsAssistantLimiterManifestEntry,
9055
- docsPublicClientKey,
9056
9141
  encode,
9057
9142
  encodeAwarenessMessage,
9058
9143
  encodeSyncStep1,
@@ -9070,15 +9155,12 @@ export {
9070
9155
  handleSyncMessage,
9071
9156
  hasContent,
9072
9157
  isAutoProvision,
9073
- isDocsResourcePath,
9074
9158
  isOwner,
9075
9159
  isTestAccountClaims,
9076
9160
  isTestAccountEmail,
9077
9161
  isTestAccountTier,
9078
9162
  lintSchema,
9079
9163
  listDeepSpaceAgentModels,
9080
- loadDocsPublishedCorpus,
9081
- loadDocsPublishedManifest,
9082
9164
  loadMessages,
9083
9165
  makeDefaultSummarizer,
9084
9166
  matchOrigin,
@@ -9086,33 +9168,22 @@ export {
9086
9168
  meterUsage,
9087
9169
  meterVectorize,
9088
9170
  noopPermissionContext,
9089
- normalizeDocsAssistantHistory,
9090
- normalizeDocsRoute,
9091
- parseDocsCorpus,
9092
9171
  parseQuotaLimit,
9093
9172
  platformCorsPatterns,
9094
9173
  platformWorkerFetch,
9095
9174
  prepareMessagesWithCompaction,
9096
9175
  priceBindingUsageEvent,
9097
- readDocsPublishedPage,
9098
- readDocsRoute,
9099
9176
  readVarUint,
9100
9177
  readVarUint8Array,
9101
9178
  registerClientErrorRoute,
9102
- registerDeepSpaceDocs,
9103
- registerDocsAssistantRoutes,
9104
- registerDocsMcpRoutes,
9105
- registerDocsStaticRoutes,
9179
+ registerDeepSpaceDocumentation,
9106
9180
  registryErrorJson,
9107
9181
  registryStub,
9108
- releaseDocsPublicLease,
9109
9182
  resolveAppName,
9110
9183
  resolveColumn,
9111
9184
  resolveDeepSpaceAgentModel,
9112
- resolveDocsPublishedCorpus,
9113
9185
  rowToData,
9114
9186
  runMigrations,
9115
- searchDocsCorpus,
9116
9187
  serverBuild,
9117
9188
  streamDeepSpaceAgent,
9118
9189
  timingSafeEqualHex,