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/dist/server.js CHANGED
@@ -475,7 +475,7 @@ function authWorkerFetch(env, path, init) {
475
475
  function buildCronContext(env, ownerUserId, roomId = "default") {
476
476
  const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
477
477
  const room = env.RECORD_ROOMS.get(roomIdObj);
478
- async function executeTool3(tool2, params) {
478
+ async function executeTool3(tool3, params) {
479
479
  const response = await room.fetch(
480
480
  new Request("https://internal/api/tools/execute", {
481
481
  method: "POST",
@@ -484,12 +484,12 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
484
484
  "X-User-Id": ownerUserId,
485
485
  "X-App-Action": "true"
486
486
  },
487
- body: JSON.stringify({ tool: tool2, params })
487
+ body: JSON.stringify({ tool: tool3, params })
488
488
  })
489
489
  );
490
490
  const result = await response.json();
491
491
  if (!result.success) {
492
- throw new Error(`RecordRoom tool ${tool2} failed: ${result.error || "Unknown error"}`);
492
+ throw new Error(`RecordRoom tool ${tool3} failed: ${result.error || "Unknown error"}`);
493
493
  }
494
494
  return result.data;
495
495
  }
@@ -611,6 +611,270 @@ function createDeepSpaceAI(env, provider, options = {}) {
611
611
  }
612
612
  }
613
613
 
614
+ // src/server/utils/agent.ts
615
+ import {
616
+ stepCountIs,
617
+ streamText
618
+ } from "ai";
619
+
620
+ // src/shared/ai-models.ts
621
+ var MULTI_STEP_AGENT_PROFILES = ["application", "documentation"];
622
+ function createMultiStepModelFactory(provider) {
623
+ return (model) => ({
624
+ ...model,
625
+ ...provider,
626
+ agentSupport: "multi-step",
627
+ agentProfiles: MULTI_STEP_AGENT_PROFILES
628
+ });
629
+ }
630
+ var anthropicModel = createMultiStepModelFactory({
631
+ provider: "anthropic",
632
+ providerLabel: "Anthropic",
633
+ transport: "messages"
634
+ });
635
+ var openAIModel = createMultiStepModelFactory({
636
+ provider: "openai",
637
+ providerLabel: "OpenAI",
638
+ transport: "chat-completions"
639
+ });
640
+ var DEEPSPACE_AI_MODELS = [
641
+ anthropicModel({
642
+ id: "claude-fable-5",
643
+ label: "Claude Fable 5",
644
+ family: "Claude 5",
645
+ recommendation: "frontier"
646
+ }),
647
+ anthropicModel({
648
+ id: "claude-opus-5",
649
+ label: "Claude Opus 5",
650
+ family: "Claude 5",
651
+ recommendation: "frontier"
652
+ }),
653
+ anthropicModel({
654
+ id: "claude-sonnet-5",
655
+ label: "Claude Sonnet 5",
656
+ family: "Claude 5",
657
+ recommendation: "balanced"
658
+ }),
659
+ anthropicModel({
660
+ id: "claude-haiku-4-5",
661
+ label: "Claude Haiku 4.5",
662
+ family: "Claude 4.5",
663
+ recommendation: "fast"
664
+ }),
665
+ openAIModel({
666
+ id: "gpt-5.6-sol",
667
+ label: "GPT-5.6 Sol",
668
+ family: "GPT-5.6",
669
+ recommendation: "frontier"
670
+ }),
671
+ openAIModel({
672
+ id: "gpt-5.6-terra",
673
+ label: "GPT-5.6 Terra",
674
+ family: "GPT-5.6",
675
+ recommendation: "balanced"
676
+ }),
677
+ openAIModel({
678
+ id: "gpt-5.6-luna",
679
+ label: "GPT-5.6 Luna",
680
+ family: "GPT-5.6",
681
+ recommendation: "fast"
682
+ }),
683
+ {
684
+ id: "gpt-oss-120b",
685
+ label: "GPT-OSS 120B",
686
+ provider: "cerebras",
687
+ providerLabel: "Cerebras",
688
+ family: "GPT-OSS",
689
+ agentSupport: "single-step",
690
+ agentProfiles: [],
691
+ transport: "chat-completions",
692
+ recommendation: "limited",
693
+ note: "Available for direct generation; the current proxy adapter has not passed multi-step tool-result continuation."
694
+ }
695
+ ];
696
+ var DEEPSPACE_AI_DEFAULTS = {
697
+ agent: "claude-sonnet-5",
698
+ directGeneration: "claude-sonnet-5",
699
+ summarization: "claude-haiku-4-5"
700
+ };
701
+ var DEEPSPACE_AGENT_PROFILES = {
702
+ application: {
703
+ id: "application",
704
+ defaultModel: DEEPSPACE_AI_DEFAULTS.agent,
705
+ maxSteps: 5,
706
+ allowedTools: "application-defined"
707
+ },
708
+ documentation: {
709
+ id: "documentation",
710
+ defaultModel: DEEPSPACE_AI_DEFAULTS.agent,
711
+ // Permit up to twenty completed read-only tools plus one final synthesis
712
+ // step. The runner disables tools once the tool-call budget is exhausted.
713
+ maxSteps: 21,
714
+ maxToolCalls: 20,
715
+ allowedTools: ["docs_search", "docs_read"]
716
+ }
717
+ };
718
+ function getDeepSpaceAIModel(modelId) {
719
+ if (typeof modelId !== "string") return null;
720
+ return DEEPSPACE_AI_MODELS.find((model) => model.id === modelId) ?? null;
721
+ }
722
+ function resolveDeepSpaceAgentModel(modelId, profileId = "application") {
723
+ const profile = DEEPSPACE_AGENT_PROFILES[profileId];
724
+ const resolvedId = modelId === void 0 ? profile.defaultModel : modelId;
725
+ const model = getDeepSpaceAIModel(resolvedId);
726
+ if (!model || !model.agentProfiles.includes(profileId)) return null;
727
+ return { modelId: model.id, provider: model.provider, model, profile };
728
+ }
729
+
730
+ // src/server/utils/agent.ts
731
+ var DeepSpaceAgentModelError = class extends Error {
732
+ constructor(modelId, profile) {
733
+ super(
734
+ `Model ${typeof modelId === "string" ? JSON.stringify(modelId) : String(modelId)} is not compatible with the ${profile} agent profile`
735
+ );
736
+ this.modelId = modelId;
737
+ this.profile = profile;
738
+ this.name = "DeepSpaceAgentModelError";
739
+ }
740
+ code = "unsupported_agent_model";
741
+ };
742
+ var DeepSpaceAgentProfileError = class extends Error {
743
+ constructor(profile, toolName) {
744
+ super(`The ${profile} agent profile does not allow tool ${JSON.stringify(toolName)}`);
745
+ this.profile = profile;
746
+ this.toolName = toolName;
747
+ this.name = "DeepSpaceAgentProfileError";
748
+ }
749
+ code = "agent_profile_violation";
750
+ };
751
+ function streamDeepSpaceAgent(env, options) {
752
+ const { profile, modelId, authToken, ...streamOptions } = options;
753
+ const selection = resolveDeepSpaceAgentModel(modelId, profile);
754
+ if (!selection) throw new DeepSpaceAgentModelError(modelId, profile);
755
+ if (selection.profile.allowedTools !== "application-defined") {
756
+ const allowed = selection.profile.allowedTools;
757
+ for (const toolName of Object.keys(streamOptions.tools ?? {})) {
758
+ if (!allowed.includes(toolName)) {
759
+ throw new DeepSpaceAgentProfileError(profile, toolName);
760
+ }
761
+ }
762
+ }
763
+ const provider = createDeepSpaceAI(
764
+ env,
765
+ selection.provider,
766
+ authToken ? { authToken } : {}
767
+ );
768
+ const { prepareStep, tools, ...remainingStreamOptions } = streamOptions;
769
+ const boundedTools = limitToolExecutions(tools, selection.profile.maxToolCalls);
770
+ const result = streamText({
771
+ ...remainingStreamOptions,
772
+ ...boundedTools ? { tools: boundedTools } : {},
773
+ model: provider(selection.modelId),
774
+ stopWhen: stepCountIs(selection.profile.maxSteps),
775
+ prepareStep: enforceToolCallLimit(prepareStep, selection.profile.maxToolCalls),
776
+ ...selection.provider === "openai" ? { providerOptions: { openai: { reasoningEffort: "none" } } } : {}
777
+ });
778
+ return { selection, result };
779
+ }
780
+ function enforceToolCallLimit(prepareStep, maxToolCalls) {
781
+ if (maxToolCalls === void 0) return prepareStep;
782
+ return async (context) => {
783
+ const prepared = await prepareStep?.(context);
784
+ const completedToolCalls = context.steps.reduce(
785
+ (count, step) => count + step.toolCalls.length,
786
+ 0
787
+ );
788
+ if (completedToolCalls < maxToolCalls) return prepared;
789
+ return { ...prepared, activeTools: [], toolChoice: "none" };
790
+ };
791
+ }
792
+ function limitToolExecutions(tools, maxToolCalls) {
793
+ if (!tools || maxToolCalls === void 0) return tools;
794
+ let executions = 0;
795
+ return Object.fromEntries(Object.entries(tools).map(([name, definition]) => {
796
+ if (typeof definition.execute !== "function") return [name, definition];
797
+ const execute = definition.execute;
798
+ return [name, {
799
+ ...definition,
800
+ execute: (...args) => {
801
+ if (executions >= maxToolCalls) {
802
+ return Promise.resolve({
803
+ error: `The agent tool-call limit of ${maxToolCalls} has been reached`
804
+ });
805
+ }
806
+ executions++;
807
+ return execute(...args);
808
+ }
809
+ }];
810
+ }));
811
+ }
812
+ function deepSpaceAgentErrorSummary(error, context = {}) {
813
+ const details = [];
814
+ const contextFields = [
815
+ context.profile ? `profile=${context.profile}` : void 0,
816
+ context.provider ? `provider=${safeLogToken(context.provider)}` : void 0,
817
+ context.modelId ? `model=${safeLogToken(context.modelId)}` : void 0
818
+ ].filter((part) => Boolean(part));
819
+ if (contextFields.length > 0) details.push(contextFields.join(" "));
820
+ const pending = [error];
821
+ const visited = /* @__PURE__ */ new Set();
822
+ while (pending.length > 0 && details.length < 8) {
823
+ const current = pending.shift();
824
+ if (current == null || visited.has(current)) continue;
825
+ visited.add(current);
826
+ if (typeof current === "object") {
827
+ const record = current;
828
+ const parts = [
829
+ typeof record.name === "string" ? `name=${safeLogToken(record.name)}` : current instanceof Error ? `name=${safeLogToken(current.name)}` : void 0,
830
+ safeErrorMessage(current, record),
831
+ typeof record.statusCode === "number" ? `status=${record.statusCode}` : void 0,
832
+ typeof record.code === "string" ? `code=${safeLogToken(record.code)}` : void 0,
833
+ typeof record.requestId === "string" ? `requestId=${safeLogToken(record.requestId)}` : void 0,
834
+ ...safeProviderResponseMetadata(record.responseBody)
835
+ ].filter((part) => Boolean(part));
836
+ details.push(parts.join(" ") || Object.prototype.toString.call(current));
837
+ if (record.lastError != null) pending.push(record.lastError);
838
+ if (record.cause != null) pending.push(record.cause);
839
+ if (Array.isArray(record.errors)) pending.push(...record.errors);
840
+ continue;
841
+ }
842
+ details.push(String(current));
843
+ }
844
+ return details.join(" <- ") || "Unknown error";
845
+ }
846
+ function safeErrorMessage(current, record) {
847
+ if (record.responseBody !== void 0) return void 0;
848
+ const message = current instanceof Error ? current.message : record.message;
849
+ if (typeof message !== "string" || !message.trim()) return void 0;
850
+ return `message=${safeLogText(message)}`;
851
+ }
852
+ function safeProviderResponseMetadata(responseBody) {
853
+ if (typeof responseBody !== "string") return [];
854
+ try {
855
+ const parsed = JSON.parse(responseBody);
856
+ const error = parsed.error && typeof parsed.error === "object" ? parsed.error : void 0;
857
+ const code = typeof error?.code === "string" ? error.code : typeof error?.type === "string" ? error.type : void 0;
858
+ const requestId = typeof parsed.request_id === "string" ? parsed.request_id : void 0;
859
+ return [
860
+ code ? `upstreamCode=${safeLogToken(code)}` : void 0,
861
+ requestId ? `upstreamRequestId=${safeLogToken(requestId)}` : void 0
862
+ ].filter((part) => Boolean(part));
863
+ } catch {
864
+ return [];
865
+ }
866
+ }
867
+ function safeLogToken(value) {
868
+ return value.replace(/[^a-zA-Z0-9._:/-]/g, "_").slice(0, 120);
869
+ }
870
+ function safeLogText(value) {
871
+ const withoutControlCharacters = Array.from(value, (character) => {
872
+ const codePoint = character.codePointAt(0) ?? 0;
873
+ return codePoint <= 31 || codePoint === 127 ? " " : character;
874
+ }).join("");
875
+ 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);
876
+ }
877
+
614
878
  // src/server/utils/composio-tools.ts
615
879
  import { tool, jsonSchema } from "ai";
616
880
  var EMPTY_SCHEMA = { type: "object", properties: {} };
@@ -1047,10 +1311,11 @@ function formatTranscript(messages) {
1047
1311
  }
1048
1312
  function makeDefaultSummarizer(env, options = {}) {
1049
1313
  return async (messages) => {
1050
- const anthropic = createDeepSpaceAI(env, "anthropic", { authToken: options.authToken });
1314
+ const model = getDeepSpaceAIModel(DEEPSPACE_AI_DEFAULTS.summarization);
1315
+ if (!model) throw new Error("DeepSpace summarization model is missing from the catalog");
1316
+ const provider = createDeepSpaceAI(env, model.provider, { authToken: options.authToken });
1051
1317
  const { text } = await generateText({
1052
- // Stable alias — provider bug-fix snapshots roll forward without code change.
1053
- model: anthropic("claude-haiku-4-5"),
1318
+ model: provider(model.id),
1054
1319
  // 2500 gives the model room to finish a sentence after hitting the
1055
1320
  // "under 2000 tokens" soft limit in the prompt.
1056
1321
  maxOutputTokens: 2500,
@@ -1062,7 +1327,7 @@ function makeDefaultSummarizer(env, options = {}) {
1062
1327
  }
1063
1328
 
1064
1329
  // src/server/utils/chat-history.ts
1065
- async function executeTool(stub, userId, tool2, params) {
1330
+ async function executeTool(stub, userId, tool3, params) {
1066
1331
  const res = await stub.fetch(new Request("https://internal/api/tools/execute", {
1067
1332
  method: "POST",
1068
1333
  headers: {
@@ -1070,11 +1335,11 @@ async function executeTool(stub, userId, tool2, params) {
1070
1335
  "X-User-Id": userId,
1071
1336
  "X-App-Action": "true"
1072
1337
  },
1073
- body: JSON.stringify({ tool: tool2, params })
1338
+ body: JSON.stringify({ tool: tool3, params })
1074
1339
  }));
1075
1340
  const body = await res.json();
1076
1341
  if (!body.success) {
1077
- throw new Error(`chat-history ${tool2} failed: ${body.error ?? "unknown error"}`);
1342
+ throw new Error(`chat-history ${tool3} failed: ${body.error ?? "unknown error"}`);
1078
1343
  }
1079
1344
  return body.data;
1080
1345
  }
@@ -1186,6 +1451,1002 @@ async function appendMessage(stub, msg) {
1186
1451
  });
1187
1452
  }
1188
1453
 
1454
+ // src/server/utils/docs-assistant.ts
1455
+ import { tool as tool2 } from "ai";
1456
+ import { z } from "zod/v4";
1457
+ var MAX_CHUNKS = 1e4;
1458
+ var MAX_CHUNK_CHARS = 2500;
1459
+ var MAX_CORPUS_CHARS = 12e6;
1460
+ var SEARCH_STOP_WORDS = /* @__PURE__ */ new Set([
1461
+ "a",
1462
+ "about",
1463
+ "an",
1464
+ "and",
1465
+ "are",
1466
+ "can",
1467
+ "could",
1468
+ "do",
1469
+ "does",
1470
+ "for",
1471
+ "from",
1472
+ "has",
1473
+ "have",
1474
+ "how",
1475
+ "i",
1476
+ "in",
1477
+ "is",
1478
+ "it",
1479
+ "me",
1480
+ "my",
1481
+ "of",
1482
+ "on",
1483
+ "or",
1484
+ "our",
1485
+ "should",
1486
+ "the",
1487
+ "this",
1488
+ "to",
1489
+ "was",
1490
+ "we",
1491
+ "were",
1492
+ "what",
1493
+ "when",
1494
+ "where",
1495
+ "which",
1496
+ "will",
1497
+ "with",
1498
+ "would",
1499
+ "you",
1500
+ "your"
1501
+ ]);
1502
+ var indexedChunkCache = /* @__PURE__ */ new WeakMap();
1503
+ function parseDocsCorpus(value) {
1504
+ if (!Array.isArray(value)) throw new Error("Docs assistant index must be an array");
1505
+ if (value.length > MAX_CHUNKS) {
1506
+ throw new Error(`Docs assistant index exceeds ${MAX_CHUNKS} chunks`);
1507
+ }
1508
+ const chunks = [];
1509
+ let totalChars2 = 0;
1510
+ for (let index = 0; index < value.length; index++) {
1511
+ const raw = value[index];
1512
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
1513
+ throw new Error(`Docs assistant chunk ${index} must be an object`);
1514
+ }
1515
+ const chunk = raw;
1516
+ 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) {
1517
+ throw new Error(`Docs assistant chunk ${index} has invalid required fields`);
1518
+ }
1519
+ if (chunk.text.length > MAX_CHUNK_CHARS) {
1520
+ throw new Error(`Docs assistant chunk ${chunk.id} exceeds ${MAX_CHUNK_CHARS} characters`);
1521
+ }
1522
+ totalChars2 += chunk.text.length;
1523
+ if (totalChars2 > MAX_CORPUS_CHARS) {
1524
+ throw new Error(`Docs assistant corpus exceeds ${MAX_CORPUS_CHARS} characters`);
1525
+ }
1526
+ chunks.push({
1527
+ id: chunk.id,
1528
+ route: chunk.route,
1529
+ title: chunk.title,
1530
+ ...typeof chunk.heading === "string" && chunk.heading ? { heading: chunk.heading } : {},
1531
+ text: chunk.text
1532
+ });
1533
+ }
1534
+ return chunks;
1535
+ }
1536
+ function searchDocsCorpus(corpus, query, limit = 6) {
1537
+ const normalized = normalize(query);
1538
+ const terms = [
1539
+ ...new Set(
1540
+ normalized.split(" ").filter((term) => term.length > 1 && !SEARCH_STOP_WORDS.has(term))
1541
+ )
1542
+ ];
1543
+ if (!terms.length) return [];
1544
+ const requiredTerms = new Set(terms);
1545
+ return corpus.map((chunk) => {
1546
+ const { text, textTerms, title, titleTerms } = indexChunk(chunk);
1547
+ const textCounts = countTerms(textTerms, requiredTerms);
1548
+ let score = 0;
1549
+ if (title === normalized) score += 120;
1550
+ else if (title.includes(normalized)) score += 55;
1551
+ if (text.includes(normalized)) score += 30;
1552
+ for (const term of terms) {
1553
+ if (titleTerms.has(term)) score += 14;
1554
+ score += Math.min(6, textCounts.get(term) ?? 0) * 3;
1555
+ }
1556
+ const matchedTerms = terms.filter(
1557
+ (term) => titleTerms.has(term) || textCounts.has(term)
1558
+ ).length;
1559
+ score += matchedTerms * matchedTerms * 4;
1560
+ score += proximityBonus([...titleTerms, ...textTerms], terms);
1561
+ return {
1562
+ route: chunk.route,
1563
+ title: chunk.title,
1564
+ ...chunk.heading ? { heading: chunk.heading } : {},
1565
+ excerpt: excerpt(chunk.text, terms),
1566
+ score
1567
+ };
1568
+ }).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)));
1569
+ }
1570
+ function readDocsRoute(corpus, route) {
1571
+ const normalizedRoute = normalizeDocsRoute(route);
1572
+ return corpus.filter((chunk) => chunk.route === normalizedRoute).slice(0, 12).map(({ route: chunkRoute, title, heading, text }) => ({
1573
+ route: chunkRoute,
1574
+ title,
1575
+ ...heading ? { heading } : {},
1576
+ text
1577
+ }));
1578
+ }
1579
+ function buildDocsAssistantTools(corpus) {
1580
+ return {
1581
+ docs_search: tool2({
1582
+ description: "Search the current documentation. Use this before answering factual or how-to questions.",
1583
+ inputSchema: z.object({
1584
+ query: z.string().trim().min(2).max(300),
1585
+ limit: z.number().int().min(1).max(10).optional()
1586
+ }),
1587
+ execute: async ({ query, limit }) => ({
1588
+ results: searchDocsCorpus(corpus, query, limit ?? 6)
1589
+ })
1590
+ }),
1591
+ docs_read: tool2({
1592
+ description: "Read bounded source chunks for one exact documentation route returned by docs_search.",
1593
+ inputSchema: z.object({ route: z.string().trim().startsWith("/").max(500) }),
1594
+ execute: async ({ route }) => ({
1595
+ route: normalizeDocsRoute(route),
1596
+ chunks: readDocsRoute(corpus, route)
1597
+ })
1598
+ })
1599
+ };
1600
+ }
1601
+ function buildDocsAssistantPrompt(appName, currentRoute) {
1602
+ return [
1603
+ `You are the public documentation assistant for "${appName}" on DeepSpace.`,
1604
+ "Answer only from the documentation available through docs_search and docs_read.",
1605
+ "Search before answering unless the user is only greeting you.",
1606
+ "Use docs_read when search excerpts are insufficient or ambiguous.",
1607
+ "Never claim access to application records, integrations, private data, or mutation tools.",
1608
+ "If the documentation does not support an answer, say so plainly and suggest the closest documented page.",
1609
+ "Cite factual answers with Markdown links to the exact documentation routes returned by tools.",
1610
+ "Do not invent URLs, APIs, configuration keys, versions, or behavior.",
1611
+ "Keep answers concise, practical, and explicit about uncertainty.",
1612
+ ...currentRoute ? [`The reader is currently viewing ${normalizeDocsRoute(currentRoute)}.`] : []
1613
+ ].join("\n");
1614
+ }
1615
+ function normalize(value) {
1616
+ return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim().split(" ").filter(Boolean).map(canonicalSearchTerm).join(" ");
1617
+ }
1618
+ function canonicalSearchTerm(term) {
1619
+ if (SEARCH_STOP_WORDS.has(term)) return term;
1620
+ if (["doc", "docs", "document", "documents", "documentation"].includes(term)) return "docs";
1621
+ if (["builds", "building", "built", "compile", "compiled", "compiles", "compiling"].includes(term)) {
1622
+ return "build";
1623
+ }
1624
+ return term.length > 3 && term.endsWith("s") && !term.endsWith("ss") ? term.slice(0, -1) : term;
1625
+ }
1626
+ function indexChunk(chunk) {
1627
+ const cached = indexedChunkCache.get(chunk);
1628
+ if (cached) return cached;
1629
+ const title = normalize(`${chunk.title} ${chunk.heading ?? ""}`);
1630
+ const text = normalize(chunk.text);
1631
+ const indexed = {
1632
+ text,
1633
+ textTerms: text.split(" "),
1634
+ title,
1635
+ titleTerms: new Set(title.split(" "))
1636
+ };
1637
+ indexedChunkCache.set(chunk, indexed);
1638
+ return indexed;
1639
+ }
1640
+ function normalizeDocsRoute(value) {
1641
+ let route = `/${value}`.replace(/\/{2,}/g, "/");
1642
+ if (route.length > 1) route = route.replace(/\/$/, "");
1643
+ return route;
1644
+ }
1645
+ function countTerms(tokens, required) {
1646
+ const counts = /* @__PURE__ */ new Map();
1647
+ for (const token of tokens) {
1648
+ if (required.has(token)) counts.set(token, (counts.get(token) ?? 0) + 1);
1649
+ }
1650
+ return counts;
1651
+ }
1652
+ function proximityBonus(tokens, terms) {
1653
+ const required = new Set(terms);
1654
+ if (required.size < 2) return 0;
1655
+ const counts = /* @__PURE__ */ new Map();
1656
+ let matched = 0;
1657
+ let start = 0;
1658
+ let smallestWindow = Number.POSITIVE_INFINITY;
1659
+ for (let end = 0; end < tokens.length; end++) {
1660
+ const token = tokens[end] ?? "";
1661
+ if (required.has(token)) {
1662
+ const count = counts.get(token) ?? 0;
1663
+ counts.set(token, count + 1);
1664
+ if (count === 0) matched++;
1665
+ }
1666
+ while (matched === required.size && start <= end) {
1667
+ smallestWindow = Math.min(smallestWindow, end - start + 1);
1668
+ const first = tokens[start] ?? "";
1669
+ if (required.has(first)) {
1670
+ const count = counts.get(first) ?? 0;
1671
+ counts.set(first, count - 1);
1672
+ if (count === 1) matched--;
1673
+ }
1674
+ start++;
1675
+ }
1676
+ }
1677
+ return Number.isFinite(smallestWindow) ? Math.max(0, 42 - smallestWindow * 2) : 0;
1678
+ }
1679
+ function excerpt(text, terms) {
1680
+ const lower = text.toLowerCase();
1681
+ const first = terms.map((term) => lower.indexOf(term)).filter((index) => index >= 0).sort((left, right) => left - right)[0] ?? 0;
1682
+ const start = Math.max(0, first - 160);
1683
+ const end = Math.min(text.length, start + 520);
1684
+ return `${start > 0 ? "\u2026" : ""}${text.slice(start, end).trim()}${end < text.length ? "\u2026" : ""}`;
1685
+ }
1686
+
1687
+ // src/server/utils/docs-assistant-limiter.ts
1688
+ var WINDOW_MS = 6e4;
1689
+ var CLIENT_REQUESTS_PER_WINDOW = 12;
1690
+ var APP_REQUESTS_PER_WINDOW = 120;
1691
+ var MAX_CONCURRENT_REQUESTS = 4;
1692
+ var LEASE_TTL_MS = 2 * 6e4;
1693
+ var DocsAssistantLimiter = class {
1694
+ sql;
1695
+ constructor(state) {
1696
+ this.sql = state.storage.sql;
1697
+ this.sql.exec(`
1698
+ CREATE TABLE IF NOT EXISTS request_windows (
1699
+ scope TEXT NOT NULL,
1700
+ client_key TEXT NOT NULL,
1701
+ window INTEGER NOT NULL,
1702
+ count INTEGER NOT NULL,
1703
+ PRIMARY KEY (scope, client_key, window)
1704
+ );
1705
+ CREATE TABLE IF NOT EXISTS leases (
1706
+ id TEXT PRIMARY KEY,
1707
+ expires_at INTEGER NOT NULL
1708
+ );
1709
+ `);
1710
+ }
1711
+ async fetch(request) {
1712
+ const url = new URL(request.url);
1713
+ if (request.method !== "POST") return Response.json({ error: "method_not_allowed" }, { status: 405 });
1714
+ if (url.pathname === "/acquire") return this.acquire(request);
1715
+ if (url.pathname === "/release") return this.release(request);
1716
+ return Response.json({ error: "not_found" }, { status: 404 });
1717
+ }
1718
+ async acquire(request) {
1719
+ const body = await request.json().catch(() => null);
1720
+ if (!body || typeof body.clientKey !== "string" || body.clientKey.length !== 64) {
1721
+ return Response.json({ error: "invalid_client_key" }, { status: 400 });
1722
+ }
1723
+ const now = Date.now();
1724
+ const window = Math.floor(now / WINDOW_MS);
1725
+ this.sql.exec("DELETE FROM request_windows WHERE window < ?", window - 1);
1726
+ this.sql.exec("DELETE FROM leases WHERE expires_at <= ?", now);
1727
+ if (!this.consumeWindow("client", body.clientKey, window, CLIENT_REQUESTS_PER_WINDOW)) {
1728
+ return Response.json({ ok: false, reason: "client_rate" }, { status: 429 });
1729
+ }
1730
+ if (!this.consumeWindow("app", "all", window, APP_REQUESTS_PER_WINDOW)) {
1731
+ return Response.json({ ok: false, reason: "app_rate" }, { status: 429 });
1732
+ }
1733
+ const concurrent = this.sql.exec("SELECT COUNT(*) AS count FROM leases").one().count;
1734
+ if (concurrent >= MAX_CONCURRENT_REQUESTS) {
1735
+ return Response.json({ ok: false, reason: "app_concurrency" }, { status: 429 });
1736
+ }
1737
+ const leaseId = crypto.randomUUID();
1738
+ this.sql.exec("INSERT INTO leases (id, expires_at) VALUES (?, ?)", leaseId, now + LEASE_TTL_MS);
1739
+ return Response.json({ ok: true, leaseId });
1740
+ }
1741
+ async release(request) {
1742
+ const body = await request.json().catch(() => null);
1743
+ if (!body || typeof body.leaseId !== "string") {
1744
+ return Response.json({ error: "invalid_lease_id" }, { status: 400 });
1745
+ }
1746
+ this.sql.exec("DELETE FROM leases WHERE id = ?", body.leaseId);
1747
+ return Response.json({ ok: true });
1748
+ }
1749
+ consumeWindow(scope, clientKey, window, limit) {
1750
+ const existing = this.sql.exec(
1751
+ "SELECT count FROM request_windows WHERE scope = ? AND client_key = ? AND window = ?",
1752
+ scope,
1753
+ clientKey,
1754
+ window
1755
+ ).toArray()[0]?.count ?? 0;
1756
+ if (existing >= limit) return false;
1757
+ this.sql.exec(
1758
+ `INSERT INTO request_windows (scope, client_key, window, count)
1759
+ VALUES (?, ?, ?, 1)
1760
+ ON CONFLICT(scope, client_key, window) DO UPDATE SET count = count + 1`,
1761
+ scope,
1762
+ clientKey,
1763
+ window
1764
+ );
1765
+ return true;
1766
+ }
1767
+ };
1768
+
1769
+ // src/server/utils/docs-published-corpus.ts
1770
+ var cachedCorpus;
1771
+ async function loadDocsPublishedManifest(env, requestUrl = "https://assets.local/") {
1772
+ const response = await env.ASSETS.fetch(new URL("/_docs/manifest.json", requestUrl).toString());
1773
+ if (!response.ok) return null;
1774
+ const value = await response.json().catch(() => null);
1775
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1776
+ const manifest = value;
1777
+ const assistant = parseAssistantPolicy(manifest.assistant);
1778
+ if (typeof manifest.sourceHash !== "string" || typeof manifest.name !== "string" || !assistant) return null;
1779
+ return {
1780
+ sourceHash: manifest.sourceHash,
1781
+ name: manifest.name,
1782
+ assistant,
1783
+ // Older docs artifacts did not carry an MCP policy. Fail closed until the
1784
+ // docs bundle is rebuilt by an MCP-aware SDK release.
1785
+ mcp: parseMcpPolicy(manifest.mcp) ?? { access: "disabled" }
1786
+ };
1787
+ }
1788
+ async function loadDocsPublishedCorpus(env, sourceHash, requestUrl = "https://assets.local/") {
1789
+ if (cachedCorpus?.sourceHash === sourceHash) return cachedCorpus.chunks;
1790
+ const response = await env.ASSETS.fetch(
1791
+ new URL("/_docs/assistant-index.json", requestUrl).toString()
1792
+ );
1793
+ if (!response.ok) throw new Error(`assistant-index.json returned ${response.status}`);
1794
+ const chunks = parseDocsCorpus(await response.json());
1795
+ cachedCorpus = { sourceHash, chunks };
1796
+ return chunks;
1797
+ }
1798
+ async function resolveDocsPublishedCorpus(env, sourceHash, requestUrl = "https://assets.local/") {
1799
+ try {
1800
+ return { ok: true, chunks: await loadDocsPublishedCorpus(env, sourceHash, requestUrl) };
1801
+ } catch (error) {
1802
+ return { ok: false, error };
1803
+ }
1804
+ }
1805
+ async function readDocsPublishedPage(env, corpus, requestedRoute, requestUrl = "https://assets.local/") {
1806
+ const route = normalizeDocsRoute(requestedRoute.split("#", 1)[0] ?? "/");
1807
+ const firstChunk = corpus.find((chunk) => chunk.route === route);
1808
+ if (!firstChunk) return null;
1809
+ const pathname = route === "/" ? "/_docs/index.md" : `/_docs${route}.md`;
1810
+ const response = await env.ASSETS.fetch(new URL(pathname, requestUrl).toString());
1811
+ if (!response.ok) return null;
1812
+ const markdown = await response.text();
1813
+ if (markdown.length > 5e5) throw new Error("Documentation page exceeds 500000 characters");
1814
+ return { route, title: firstChunk.title, markdown };
1815
+ }
1816
+ function parseAssistantPolicy(value) {
1817
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1818
+ const policy = value;
1819
+ if (!["disabled", "public", "authenticated"].includes(String(policy.access))) return null;
1820
+ return {
1821
+ access: policy.access,
1822
+ ...typeof policy.model === "string" ? { model: policy.model } : {}
1823
+ };
1824
+ }
1825
+ function parseMcpPolicy(value) {
1826
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
1827
+ const policy = value;
1828
+ if (!["disabled", "public"].includes(String(policy.access))) return null;
1829
+ return { access: policy.access };
1830
+ }
1831
+
1832
+ // src/server/utils/docs-public-limiter.ts
1833
+ async function docsPublicClientKey(value) {
1834
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
1835
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1836
+ }
1837
+ async function acquireDocsPublicLease(env, clientKey, logPrefix) {
1838
+ if (!env.DOCS_ASSISTANT_LIMITER) return { ok: false, status: 503 };
1839
+ const id = env.DOCS_ASSISTANT_LIMITER.idFromName(env.DEEPSPACE_APP_ID);
1840
+ const stub = env.DOCS_ASSISTANT_LIMITER.get(id);
1841
+ const response = await stub.fetch("https://docs-limiter/acquire", {
1842
+ method: "POST",
1843
+ headers: { "Content-Type": "application/json" },
1844
+ body: JSON.stringify({ clientKey })
1845
+ }).catch((error) => {
1846
+ console.error(`[${logPrefix}] limiter unavailable:`, error);
1847
+ return null;
1848
+ });
1849
+ if (!response?.ok) {
1850
+ return { ok: false, status: response?.status === 429 ? 429 : 503 };
1851
+ }
1852
+ const body = await response.json().catch(() => null);
1853
+ if (!body || typeof body.leaseId !== "string") return { ok: false, status: 503 };
1854
+ return { ok: true, lease: { leaseId: body.leaseId, stub } };
1855
+ }
1856
+ async function releaseDocsPublicLease(lease, logPrefix) {
1857
+ if (!lease) return;
1858
+ await lease.stub.fetch("https://docs-limiter/release", {
1859
+ method: "POST",
1860
+ headers: { "Content-Type": "application/json" },
1861
+ body: JSON.stringify({ leaseId: lease.leaseId })
1862
+ }).catch((error) => console.error(`[${logPrefix}] limiter release failed:`, error));
1863
+ }
1864
+
1865
+ // src/server/utils/request-body.ts
1866
+ var RequestBodyTooLargeError = class extends Error {
1867
+ constructor(maxBytes) {
1868
+ super(`Request body exceeds ${maxBytes} bytes`);
1869
+ this.maxBytes = maxBytes;
1870
+ this.name = "RequestBodyTooLargeError";
1871
+ }
1872
+ };
1873
+ async function readBoundedRequestText(request, maxBytes) {
1874
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
1875
+ throw new TypeError("maxBytes must be a positive safe integer");
1876
+ }
1877
+ const declared = request.headers.get("content-length");
1878
+ if (declared && /^\d+$/.test(declared.trim()) && Number(declared) > maxBytes) {
1879
+ throw new RequestBodyTooLargeError(maxBytes);
1880
+ }
1881
+ if (!request.body) return "";
1882
+ const reader = request.body.getReader();
1883
+ const decoder = new TextDecoder();
1884
+ let bytes = 0;
1885
+ let text = "";
1886
+ while (true) {
1887
+ const chunk = await reader.read();
1888
+ if (chunk.done) break;
1889
+ bytes += chunk.value.byteLength;
1890
+ if (bytes > maxBytes) {
1891
+ await reader.cancel().catch(() => void 0);
1892
+ throw new RequestBodyTooLargeError(maxBytes);
1893
+ }
1894
+ text += decoder.decode(chunk.value, { stream: true });
1895
+ }
1896
+ return text + decoder.decode();
1897
+ }
1898
+
1899
+ // src/server/utils/docs-assistant-routes.ts
1900
+ var MAX_QUESTION_CHARS = 4e3;
1901
+ var MAX_HISTORY_MESSAGES = 12;
1902
+ var MAX_HISTORY_CHARS = 2e4;
1903
+ var MAX_REQUEST_BYTES = 128 * 1024;
1904
+ function registerDocsAssistantRoutes(app, resolveAuth) {
1905
+ app.post("/api/ai/docs", async (c) => {
1906
+ if (c.req.header("x-deepspace-surface") !== "docs") return c.notFound();
1907
+ const manifest = await loadDocsPublishedManifest(c.env, c.req.url);
1908
+ if (!manifest || manifest.assistant.access === "disabled") return c.notFound();
1909
+ let authToken;
1910
+ let publicClientKey;
1911
+ if (manifest.assistant.access === "authenticated") {
1912
+ const auth = await resolveAuth(c.req.raw, c.env);
1913
+ if (!auth) return c.json({ error: "Sign in required" }, 401);
1914
+ const header = c.req.header("Authorization") ?? "";
1915
+ if (!header.startsWith("Bearer ")) return c.json({ error: "Sign in required" }, 401);
1916
+ authToken = header.slice(7);
1917
+ } else {
1918
+ if (!c.env.APP_OWNER_JWT) {
1919
+ return c.json({ error: "Public documentation assistant billing is not configured" }, 503);
1920
+ }
1921
+ publicClientKey = await docsPublicClientKey(c.req.header("CF-Connecting-IP") ?? "unknown");
1922
+ }
1923
+ let body = null;
1924
+ try {
1925
+ const raw = await readBoundedRequestText(c.req.raw, MAX_REQUEST_BYTES);
1926
+ const parsed = JSON.parse(raw);
1927
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1928
+ body = parsed;
1929
+ }
1930
+ } catch (error) {
1931
+ if (error instanceof RequestBodyTooLargeError) {
1932
+ return c.json({ error: `request exceeds ${MAX_REQUEST_BYTES} bytes` }, 413);
1933
+ }
1934
+ return c.json({ error: "request body must be valid JSON" }, 400);
1935
+ }
1936
+ const question = typeof body?.question === "string" ? body.question.trim() : "";
1937
+ if (!question) return c.json({ error: "question is required" }, 400);
1938
+ if (question.length > MAX_QUESTION_CHARS) {
1939
+ return c.json({ error: `question exceeds ${MAX_QUESTION_CHARS} characters` }, 413);
1940
+ }
1941
+ const route = typeof body?.route === "string" && body.route.startsWith("/") ? body.route.slice(0, 500) : void 0;
1942
+ let history;
1943
+ try {
1944
+ history = normalizeDocsAssistantHistory(body?.messages);
1945
+ } catch (error) {
1946
+ return c.json({ error: error instanceof Error ? error.message : "messages are invalid" }, 400);
1947
+ }
1948
+ const corpusResult = await resolveDocsPublishedCorpus(c.env, manifest.sourceHash, c.req.url);
1949
+ if (!corpusResult.ok) {
1950
+ console.error("[docs-assistant] corpus unavailable:", corpusResult.error);
1951
+ return c.json({ error: "Documentation corpus is unavailable" }, 503);
1952
+ }
1953
+ const corpus = corpusResult.chunks;
1954
+ const selectedModel = resolveDeepSpaceAgentModel(
1955
+ manifest.assistant.model,
1956
+ "documentation"
1957
+ );
1958
+ if (!selectedModel) {
1959
+ console.error("[docs-assistant] unsupported configured model:", manifest.assistant.model);
1960
+ return c.json({ error: "Documentation assistant model is not supported" }, 503);
1961
+ }
1962
+ const diagnosticContext = {
1963
+ profile: "documentation",
1964
+ provider: selectedModel.provider,
1965
+ modelId: selectedModel.modelId
1966
+ };
1967
+ if (publicClientKey && !c.env.DOCS_ASSISTANT_LIMITER) {
1968
+ return c.json({ error: "Documentation assistant rate limiting is not configured" }, 503);
1969
+ }
1970
+ const leaseResult = publicClientKey ? await acquireDocsPublicLease(c.env, publicClientKey, "docs-assistant") : void 0;
1971
+ if (leaseResult && !leaseResult.ok) {
1972
+ return c.json(
1973
+ {
1974
+ error: leaseResult.status === 429 ? "Documentation assistant rate limit exceeded. Try again shortly." : "Documentation assistant is temporarily unavailable"
1975
+ },
1976
+ leaseResult.status,
1977
+ leaseResult.status === 429 ? { "Retry-After": "60" } : void 0
1978
+ );
1979
+ }
1980
+ const lease = leaseResult?.ok ? leaseResult.lease : void 0;
1981
+ let released = false;
1982
+ const releaseLease = async () => {
1983
+ if (!lease || released) return;
1984
+ released = true;
1985
+ await releaseDocsPublicLease(lease, "docs-assistant");
1986
+ };
1987
+ let result;
1988
+ try {
1989
+ result = streamDeepSpaceAgent(c.env, {
1990
+ profile: "documentation",
1991
+ modelId: selectedModel.modelId,
1992
+ ...authToken ? { authToken } : {},
1993
+ system: buildDocsAssistantPrompt(manifest.name || c.env.APP_NAME, route),
1994
+ messages: [...history, { role: "user", content: question }],
1995
+ tools: buildDocsAssistantTools(corpus),
1996
+ abortSignal: c.req.raw.signal,
1997
+ onAbort: () => releaseLease(),
1998
+ onFinish: releaseLease,
1999
+ onError: ({ error }) => {
2000
+ console.error(
2001
+ `[docs-assistant] stream error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
2002
+ );
2003
+ void releaseLease();
2004
+ }
2005
+ }).result;
2006
+ } catch (error) {
2007
+ console.error(
2008
+ `[docs-assistant] agent unavailable: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
2009
+ );
2010
+ await releaseLease();
2011
+ return c.json({ error: "Documentation assistant is not configured" }, 503);
2012
+ }
2013
+ return result.toUIMessageStreamResponse({
2014
+ sendReasoning: false,
2015
+ onError: (error) => {
2016
+ console.error(
2017
+ `[docs-assistant] response error: ${deepSpaceAgentErrorSummary(error, diagnosticContext)}`
2018
+ );
2019
+ void releaseLease();
2020
+ return "The documentation assistant could not complete this response.";
2021
+ }
2022
+ });
2023
+ });
2024
+ }
2025
+ var docsAssistantErrorSummary = deepSpaceAgentErrorSummary;
2026
+ function normalizeDocsAssistantHistory(value) {
2027
+ if (value === void 0) return [];
2028
+ if (!Array.isArray(value)) throw new TypeError("messages must be an array");
2029
+ if (value.length > MAX_HISTORY_MESSAGES) {
2030
+ throw new TypeError(`messages exceeds ${MAX_HISTORY_MESSAGES} turns`);
2031
+ }
2032
+ const messages = [];
2033
+ let totalChars2 = 0;
2034
+ for (const [index, item] of value.entries()) {
2035
+ if (!item || typeof item !== "object" || Array.isArray(item)) {
2036
+ throw new TypeError(`messages[${index}] must be an object`);
2037
+ }
2038
+ const candidate = item;
2039
+ const expectedRole = index % 2 === 0 ? "user" : "assistant";
2040
+ if (candidate.role !== expectedRole) {
2041
+ throw new TypeError(`messages[${index}].role must be ${expectedRole}`);
2042
+ }
2043
+ const content = typeof candidate.content === "string" ? candidate.content.trim() : "";
2044
+ if (!content) throw new TypeError(`messages[${index}].content is required`);
2045
+ if (content.length > MAX_QUESTION_CHARS) {
2046
+ throw new TypeError(`messages[${index}].content exceeds ${MAX_QUESTION_CHARS} characters`);
2047
+ }
2048
+ totalChars2 += content.length;
2049
+ if (totalChars2 > MAX_HISTORY_CHARS) {
2050
+ throw new TypeError(`messages exceeds ${MAX_HISTORY_CHARS} characters`);
2051
+ }
2052
+ messages.push({ role: expectedRole, content });
2053
+ }
2054
+ if (messages.length % 2 !== 0) {
2055
+ throw new TypeError("messages must end with a completed assistant response");
2056
+ }
2057
+ return messages;
2058
+ }
2059
+ function registerDocsStaticRoutes(app) {
2060
+ app.get("/_docs/*", async (c) => {
2061
+ const pathname = new URL(c.req.url).pathname;
2062
+ let response = await c.env.ASSETS.fetch(c.req.raw);
2063
+ let status = response.status;
2064
+ if (status === 404) {
2065
+ const url = new URL(c.req.url);
2066
+ url.pathname = "/_docs/404.html";
2067
+ response = await c.env.ASSETS.fetch(new Request(url.toString(), c.req.raw));
2068
+ status = 404;
2069
+ }
2070
+ return secureDocsResponse(response, status, pathname);
2071
+ });
2072
+ }
2073
+ function secureDocsResponse(response, status, pathname) {
2074
+ const headers = new Headers(response.headers);
2075
+ if (status !== 404 && pathname.endsWith(".md")) {
2076
+ headers.set("Content-Type", "text/markdown; charset=utf-8");
2077
+ }
2078
+ if (headers.get("Content-Type")?.toLowerCase().includes("text/html")) {
2079
+ const cacheControl = headers.get("Cache-Control") ?? "public, max-age=0, must-revalidate";
2080
+ if (!/(?:^|,)\s*no-transform(?:\s*,|$)/i.test(cacheControl)) {
2081
+ headers.set("Cache-Control", `${cacheControl}, no-transform`);
2082
+ }
2083
+ }
2084
+ headers.set("X-Content-Type-Options", "nosniff");
2085
+ headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
2086
+ headers.set(
2087
+ "Content-Security-Policy",
2088
+ "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'"
2089
+ );
2090
+ return new Response(response.body, { status, headers });
2091
+ }
2092
+
2093
+ // src/server/utils/docs-mcp/protocol.ts
2094
+ var MCP_SERVER_VERSION = "1.0.0";
2095
+ var CURRENT_PROTOCOL_VERSION = "2025-11-25";
2096
+ var SUPPORTED_PROTOCOL_VERSIONS = /* @__PURE__ */ new Set([CURRENT_PROTOCOL_VERSION, "2025-06-18", "2025-03-26"]);
2097
+ function initializeResponse(request) {
2098
+ const params = objectValue(request.params);
2099
+ const requested = typeof params?.protocolVersion === "string" ? params.protocolVersion : CURRENT_PROTOCOL_VERSION;
2100
+ const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.has(requested) ? requested : CURRENT_PROTOCOL_VERSION;
2101
+ return mcpResult(request.id, {
2102
+ protocolVersion,
2103
+ capabilities: {
2104
+ tools: { listChanged: false },
2105
+ resources: { subscribe: false, listChanged: false }
2106
+ },
2107
+ serverInfo: {
2108
+ name: "DeepSpace Docs",
2109
+ title: "DeepSpace documentation",
2110
+ version: MCP_SERVER_VERSION
2111
+ },
2112
+ instructions: "Search the published docs before answering, then read exact pages when full context is needed."
2113
+ });
2114
+ }
2115
+ function hasValidOrigin(request) {
2116
+ const origin = request.headers.get("Origin");
2117
+ if (!origin) return true;
2118
+ try {
2119
+ return new URL(origin).origin === new URL(request.url).origin;
2120
+ } catch {
2121
+ return false;
2122
+ }
2123
+ }
2124
+ function parseRequest(raw) {
2125
+ let value;
2126
+ try {
2127
+ value = JSON.parse(raw);
2128
+ } catch {
2129
+ return { ok: false, message: "Invalid JSON" };
2130
+ }
2131
+ const candidate = objectValue(value);
2132
+ if (!candidate || candidate.jsonrpc !== "2.0" || typeof candidate.method !== "string") {
2133
+ return { ok: false, message: "Invalid JSON-RPC request" };
2134
+ }
2135
+ if (candidate.id !== void 0 && candidate.id !== null && typeof candidate.id !== "string" && typeof candidate.id !== "number")
2136
+ return { ok: false, message: "Invalid JSON-RPC id" };
2137
+ return {
2138
+ ok: true,
2139
+ request: {
2140
+ jsonrpc: "2.0",
2141
+ method: candidate.method,
2142
+ ...candidate.id !== void 0 ? { id: candidate.id } : {},
2143
+ ...candidate.params !== void 0 ? { params: candidate.params } : {}
2144
+ }
2145
+ };
2146
+ }
2147
+ function objectValue(value) {
2148
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
2149
+ }
2150
+ function toolResult(id, value) {
2151
+ return mcpResult(id, {
2152
+ content: [{ type: "text", text: JSON.stringify(value) }],
2153
+ structuredContent: value,
2154
+ isError: false
2155
+ });
2156
+ }
2157
+ function toolError(id, message) {
2158
+ return mcpResult(id, {
2159
+ content: [{ type: "text", text: message }],
2160
+ isError: true
2161
+ });
2162
+ }
2163
+ function mcpResult(id, result) {
2164
+ return jsonResponse({ jsonrpc: "2.0", id, result });
2165
+ }
2166
+ function mcpError(id, code, message, status = 200, headers) {
2167
+ return jsonResponse({ jsonrpc: "2.0", id, error: { code, message } }, status, headers);
2168
+ }
2169
+ function jsonResponse(value, status = 200, headers) {
2170
+ return new Response(JSON.stringify(value), {
2171
+ status,
2172
+ headers: {
2173
+ "Content-Type": "application/json; charset=utf-8",
2174
+ ...mcpSecurityHeaders(),
2175
+ ...headers
2176
+ }
2177
+ });
2178
+ }
2179
+ function mcpSecurityHeaders() {
2180
+ return {
2181
+ "Cache-Control": "no-store",
2182
+ "X-Content-Type-Options": "nosniff",
2183
+ "Referrer-Policy": "no-referrer"
2184
+ };
2185
+ }
2186
+
2187
+ // src/server/utils/docs-mcp/tools.ts
2188
+ var DOCS_TOOLS = [
2189
+ {
2190
+ name: "docs_search",
2191
+ title: "Search documentation",
2192
+ description: "Search the published documentation and return ranked, linked excerpts.",
2193
+ inputSchema: {
2194
+ type: "object",
2195
+ properties: {
2196
+ query: { type: "string", minLength: 2, maxLength: 300 },
2197
+ limit: { type: "integer", minimum: 1, maximum: 10, default: 6 }
2198
+ },
2199
+ required: ["query"],
2200
+ additionalProperties: false
2201
+ },
2202
+ outputSchema: {
2203
+ type: "object",
2204
+ properties: { results: { type: "array" } },
2205
+ required: ["results"]
2206
+ },
2207
+ annotations: {
2208
+ readOnlyHint: true,
2209
+ destructiveHint: false,
2210
+ idempotentHint: true,
2211
+ openWorldHint: false
2212
+ }
2213
+ },
2214
+ {
2215
+ name: "docs_read",
2216
+ title: "Read documentation page",
2217
+ description: "Read one complete published documentation page as Markdown.",
2218
+ inputSchema: {
2219
+ type: "object",
2220
+ properties: {
2221
+ route: { type: "string", minLength: 1, maxLength: 500, pattern: "^/" }
2222
+ },
2223
+ required: ["route"],
2224
+ additionalProperties: false
2225
+ },
2226
+ outputSchema: {
2227
+ type: "object",
2228
+ properties: {
2229
+ route: { type: "string" },
2230
+ title: { type: "string" },
2231
+ url: { type: "string" },
2232
+ markdown: { type: "string" }
2233
+ },
2234
+ required: ["route", "title", "url", "markdown"]
2235
+ },
2236
+ annotations: {
2237
+ readOnlyHint: true,
2238
+ destructiveHint: false,
2239
+ idempotentHint: true,
2240
+ openWorldHint: false
2241
+ }
2242
+ }
2243
+ ];
2244
+ async function callDocsTool(rawRequest, request, corpus, env) {
2245
+ const params = objectValue(request.params);
2246
+ const name = typeof params?.name === "string" ? params.name : "";
2247
+ const args = objectValue(params?.arguments) ?? {};
2248
+ const origin = new URL(rawRequest.url).origin;
2249
+ if (name === "docs_search") {
2250
+ const query = typeof args.query === "string" ? args.query.trim() : "";
2251
+ const limit = args.limit === void 0 ? 6 : args.limit;
2252
+ if (query.length < 2 || query.length > 300 || !Number.isInteger(limit) || Number(limit) < 1 || Number(limit) > 10) {
2253
+ return mcpError(request.id, -32602, "Invalid docs_search arguments");
2254
+ }
2255
+ const value = {
2256
+ results: searchDocsCorpus(corpus, query, Number(limit)).map((result) => ({
2257
+ ...result,
2258
+ url: new URL(result.route, origin).toString()
2259
+ }))
2260
+ };
2261
+ return toolResult(request.id, value);
2262
+ }
2263
+ if (name === "docs_read") {
2264
+ const route = typeof args.route === "string" ? args.route.trim() : "";
2265
+ if (!route.startsWith("/") || route.length > 500) {
2266
+ return mcpError(request.id, -32602, "Invalid docs_read arguments");
2267
+ }
2268
+ try {
2269
+ const page = await readDocsPublishedPage(env, corpus, route, rawRequest.url);
2270
+ if (!page) return toolError(request.id, `No published documentation page exists at ${route}`);
2271
+ return toolResult(request.id, {
2272
+ ...page,
2273
+ url: new URL(page.route, origin).toString()
2274
+ });
2275
+ } catch (error) {
2276
+ console.error("[docs-mcp] page unavailable:", error);
2277
+ return toolError(request.id, "The documentation page could not be read");
2278
+ }
2279
+ }
2280
+ return mcpError(request.id, -32602, `Unknown documentation tool: ${name || "<missing>"}`);
2281
+ }
2282
+
2283
+ // src/server/utils/docs-mcp-routes.ts
2284
+ var MAX_REQUEST_CHARS = 64e3;
2285
+ var MAX_REQUEST_BYTES2 = MAX_REQUEST_CHARS * 4;
2286
+ var MAX_SKILL_CHARS = 256e3;
2287
+ function registerDocsMcpRoutes(app) {
2288
+ app.get("/mcp", async (c) => {
2289
+ if (!await publicManifest(c.req.raw, c.env)) return c.notFound();
2290
+ return new Response(null, {
2291
+ status: 405,
2292
+ headers: { Allow: "POST", ...mcpSecurityHeaders() }
2293
+ });
2294
+ });
2295
+ app.post("/mcp", async (c) => {
2296
+ const manifest = await publicManifest(c.req.raw, c.env);
2297
+ if (!manifest) return c.notFound();
2298
+ if (!hasValidOrigin(c.req.raw)) {
2299
+ return mcpError(null, -32e3, "Invalid Origin", 403);
2300
+ }
2301
+ let raw;
2302
+ try {
2303
+ raw = await readBoundedRequestText(c.req.raw, MAX_REQUEST_BYTES2);
2304
+ } catch (error) {
2305
+ if (error instanceof RequestBodyTooLargeError) {
2306
+ return mcpError(null, -32600, `Request exceeds ${MAX_REQUEST_BYTES2} bytes`, 413);
2307
+ }
2308
+ return mcpError(null, -32700, "Unable to read request body", 400);
2309
+ }
2310
+ if (raw.length > MAX_REQUEST_CHARS) {
2311
+ return mcpError(null, -32600, `Request exceeds ${MAX_REQUEST_CHARS} characters`, 413);
2312
+ }
2313
+ const parsed = parseRequest(raw);
2314
+ if (!parsed.ok) return mcpError(null, -32700, parsed.message, 400);
2315
+ const request = parsed.request;
2316
+ if (request.method !== "initialize") {
2317
+ const protocolVersion = c.req.header("MCP-Protocol-Version");
2318
+ if (protocolVersion && !SUPPORTED_PROTOCOL_VERSIONS.has(protocolVersion)) {
2319
+ return mcpError(request.id ?? null, -32600, "Unsupported MCP-Protocol-Version", 400);
2320
+ }
2321
+ }
2322
+ const requestId = request.id;
2323
+ if (requestId === void 0) return new Response(null, { status: 202 });
2324
+ const requestWithId = { ...request, id: requestId };
2325
+ if (request.method === "initialize") return initializeResponse(requestWithId);
2326
+ if (request.method === "ping") return mcpResult(requestId, {});
2327
+ if (request.method === "tools/list") {
2328
+ return mcpResult(requestId, { tools: DOCS_TOOLS });
2329
+ }
2330
+ if (request.method === "resources/list") {
2331
+ return mcpResult(requestId, { resources: [skillResource(c.req.raw, manifest)] });
2332
+ }
2333
+ if (request.method === "resources/read") {
2334
+ return readSkillResource(c.req.raw, requestWithId, manifest, c.env);
2335
+ }
2336
+ if (request.method !== "tools/call") {
2337
+ return mcpError(requestId, -32601, `Method not found: ${request.method}`);
2338
+ }
2339
+ const corpusResult = await resolveDocsPublishedCorpus(c.env, manifest.sourceHash, c.req.url);
2340
+ if (!corpusResult.ok) {
2341
+ console.error("[docs-mcp] corpus unavailable:", corpusResult.error);
2342
+ return mcpError(requestId, -32002, "Documentation corpus is unavailable", 503);
2343
+ }
2344
+ const corpus = corpusResult.chunks;
2345
+ const clientKey = await docsPublicClientKey(c.req.header("CF-Connecting-IP") ?? "unknown");
2346
+ const leaseResult = await acquireDocsPublicLease(c.env, clientKey, "docs-mcp");
2347
+ if (!leaseResult.ok) {
2348
+ return mcpError(
2349
+ requestId,
2350
+ leaseResult.status === 429 ? -32029 : -32002,
2351
+ leaseResult.status === 429 ? "Documentation tool rate limit exceeded. Try again shortly." : "Documentation tools are temporarily unavailable",
2352
+ leaseResult.status,
2353
+ leaseResult.status === 429 ? { "Retry-After": "60" } : void 0
2354
+ );
2355
+ }
2356
+ try {
2357
+ return await callDocsTool(c.req.raw, requestWithId, corpus, c.env);
2358
+ } finally {
2359
+ await releaseDocsPublicLease(leaseResult.lease, "docs-mcp");
2360
+ }
2361
+ });
2362
+ for (const path of [
2363
+ "/.well-known/mcp",
2364
+ "/.well-known/mcp.json",
2365
+ "/.well-known/mcp/server-card.json"
2366
+ ]) {
2367
+ app.get(path, async (c) => {
2368
+ const manifest = await publicManifest(c.req.raw, c.env);
2369
+ if (!manifest) return c.notFound();
2370
+ return discoveryResponse(c.req.raw, manifest);
2371
+ });
2372
+ }
2373
+ app.get("/.well-known/mcp/server-cards.json", async (c) => {
2374
+ const manifest = await publicManifest(c.req.raw, c.env);
2375
+ if (!manifest) return c.notFound();
2376
+ return jsonResponse({ servers: [serverCard(c.req.raw, manifest)] }, 200, {
2377
+ "Cache-Control": "public, max-age=300"
2378
+ });
2379
+ });
2380
+ }
2381
+ async function readSkillResource(rawRequest, request, manifest, env) {
2382
+ const params = objectValue(request.params);
2383
+ const requestedUri = typeof params?.uri === "string" ? params.uri : "";
2384
+ const resource = skillResource(rawRequest, manifest);
2385
+ if (requestedUri !== resource.uri) {
2386
+ return mcpError(request.id, -32602, "Unknown documentation resource");
2387
+ }
2388
+ const assetUrl = new URL("/_docs/skill.md", rawRequest.url);
2389
+ const response = await env.ASSETS.fetch(new Request(assetUrl, { method: "GET" }));
2390
+ if (!response.ok) return mcpError(request.id, -32002, "Documentation skill is unavailable", 503);
2391
+ const text = await response.text();
2392
+ if (text.length > MAX_SKILL_CHARS) {
2393
+ return mcpError(request.id, -32002, "Documentation skill exceeds the published size limit", 503);
2394
+ }
2395
+ return mcpResult(request.id, {
2396
+ contents: [{ uri: resource.uri, mimeType: resource.mimeType, text }]
2397
+ });
2398
+ }
2399
+ function skillResource(request, manifest) {
2400
+ return {
2401
+ uri: new URL("/skill.md", request.url).toString(),
2402
+ name: `${manifest.name} documentation skill`,
2403
+ title: `Use ${manifest.name}`,
2404
+ description: `Agent instructions grounded in the published ${manifest.name} documentation.`,
2405
+ mimeType: "text/markdown"
2406
+ };
2407
+ }
2408
+ async function publicManifest(request, env) {
2409
+ if (request.headers.get("x-deepspace-surface") !== "docs") return null;
2410
+ const manifest = await loadDocsPublishedManifest(env, request.url);
2411
+ return manifest?.mcp.access === "public" ? manifest : null;
2412
+ }
2413
+ function discoveryResponse(request, manifest) {
2414
+ return jsonResponse(serverCard(request, manifest), 200, {
2415
+ "Cache-Control": "public, max-age=300"
2416
+ });
2417
+ }
2418
+ function serverCard(request, manifest) {
2419
+ return {
2420
+ name: manifest.name,
2421
+ description: `Search and read the published ${manifest.name} documentation.`,
2422
+ version: MCP_SERVER_VERSION,
2423
+ protocolVersion: CURRENT_PROTOCOL_VERSION,
2424
+ transport: {
2425
+ type: "streamable-http",
2426
+ url: new URL("/mcp", request.url).toString()
2427
+ },
2428
+ authentication: { required: false },
2429
+ capabilities: {
2430
+ tools: DOCS_TOOLS.map(({ name, title, description }) => ({ name, title, description })),
2431
+ resources: [skillResource(request, manifest)]
2432
+ }
2433
+ };
2434
+ }
2435
+
2436
+ // src/server/utils/docs-routes.ts
2437
+ function registerDeepSpaceDocs(app, options) {
2438
+ registerDocsAssistantRoutes(app, options.resolveAuth);
2439
+ registerDocsMcpRoutes(app);
2440
+ registerDocsStaticRoutes(app);
2441
+ }
2442
+ function docsAssistantLimiterManifestEntry(className) {
2443
+ return {
2444
+ binding: "DOCS_ASSISTANT_LIMITER",
2445
+ className,
2446
+ sqlite: true
2447
+ };
2448
+ }
2449
+
1189
2450
  // src/server/utils/metering.ts
1190
2451
  function meterUsage(env, kind, fields = {}) {
1191
2452
  const ds = env.USAGE_EVENTS;
@@ -1629,6 +2890,16 @@ var MSG_YJS_SYNC = 22;
1629
2890
  var MSG_YJS_AWARENESS = 23;
1630
2891
  var ROLE_ANONYMOUS = "viewer";
1631
2892
 
2893
+ // src/shared/users-schema.ts
2894
+ var USERS_COLUMNS = [
2895
+ { name: "email", storage: "text", interpretation: "plain" },
2896
+ { name: "name", storage: "text", interpretation: "plain" },
2897
+ { name: "imageUrl", storage: "text", interpretation: "plain" },
2898
+ { name: "role", storage: "text", interpretation: "plain" },
2899
+ { name: "createdAt", storage: "text", interpretation: { kind: "datetime" } },
2900
+ { name: "lastSeenAt", storage: "text", interpretation: { kind: "datetime" } }
2901
+ ];
2902
+
1632
2903
  // src/server/schemas/registry.ts
1633
2904
  function collectionTableName(name) {
1634
2905
  return `c_${name.replace(/[^a-zA-Z0-9_]/g, "_")}`;
@@ -1829,14 +3100,6 @@ var SYSTEM_MANAGED_COLUMNS = /* @__PURE__ */ new Set([
1829
3100
  "createdAt",
1830
3101
  "lastSeenAt"
1831
3102
  ]);
1832
- var USERS_COLUMNS = [
1833
- { name: "email", storage: "text", interpretation: "plain" },
1834
- { name: "name", storage: "text", interpretation: "plain" },
1835
- { name: "imageUrl", storage: "text", interpretation: "plain" },
1836
- { name: "role", storage: "text", interpretation: "plain" },
1837
- { name: "createdAt", storage: "text", interpretation: { kind: "datetime" } },
1838
- { name: "lastSeenAt", storage: "text", interpretation: { kind: "datetime" } }
1839
- ];
1840
3103
  var BASE_USERS_SCHEMA = {
1841
3104
  name: "users",
1842
3105
  columns: [...USERS_COLUMNS],
@@ -3063,11 +4326,11 @@ async function handleToolsRequest(ctx, request, path) {
3063
4326
  }
3064
4327
  if (path.startsWith("tools/describe/")) {
3065
4328
  const toolName = path.replace("tools/describe/", "");
3066
- const tool2 = BUILT_IN_TOOLS.find((t) => t.name === toolName);
3067
- if (!tool2) {
4329
+ const tool3 = BUILT_IN_TOOLS.find((t) => t.name === toolName);
4330
+ if (!tool3) {
3068
4331
  return Response.json({ error: `Tool not found: ${toolName}` }, { status: 404, headers: CORS_HEADERS2 });
3069
4332
  }
3070
- return Response.json({ tool: tool2 }, { headers: CORS_HEADERS2 });
4333
+ return Response.json({ tool: tool3 }, { headers: CORS_HEADERS2 });
3071
4334
  }
3072
4335
  if (path.startsWith("tools/execute") && request.method === "POST") {
3073
4336
  return handleToolExecute(ctx, request);
@@ -3084,8 +4347,8 @@ async function handleToolExecute(ctx, request) {
3084
4347
  { status: 400, headers: CORS_HEADERS2 }
3085
4348
  );
3086
4349
  }
3087
- const { tool: tool2, params = {} } = body;
3088
- if (!tool2) {
4350
+ const { tool: tool3, params = {} } = body;
4351
+ if (!tool3) {
3089
4352
  return Response.json(
3090
4353
  { success: false, error: 'Missing "tool" in request body' },
3091
4354
  { status: 400, headers: CORS_HEADERS2 }
@@ -3102,8 +4365,8 @@ async function handleToolExecute(ctx, request) {
3102
4365
  userRole = user.role;
3103
4366
  }
3104
4367
  }
3105
- if (tool2.startsWith("yjs.")) {
3106
- const result2 = executeYjsTool(ctx, tool2, params, userId, userRole);
4368
+ if (tool3.startsWith("yjs.")) {
4369
+ const result2 = executeYjsTool(ctx, tool3, params, userId, userRole);
3107
4370
  const status2 = result2.success ? 200 : result2.error?.includes("not found") ? 404 : 400;
3108
4371
  return Response.json(result2, { status: status2, headers: CORS_HEADERS2 });
3109
4372
  }
@@ -3114,7 +4377,7 @@ async function handleToolExecute(ctx, request) {
3114
4377
  getPermissionContext: ctx.getPermissionContext,
3115
4378
  send: ctx.send
3116
4379
  };
3117
- const result = await executeTool2(recordCtx, tool2, params, userId, userRole, isAppAction);
4380
+ const result = await executeTool2(recordCtx, tool3, params, userId, userRole, isAppAction);
3118
4381
  const status = result.success ? 200 : result.error?.includes("not found") ? 404 : 400;
3119
4382
  return Response.json(result, { status, headers: CORS_HEADERS2 });
3120
4383
  }
@@ -7365,6 +8628,9 @@ export {
7365
8628
  DEFAULT_QUERY_LIMIT,
7366
8629
  DEPLOYED_APPS_DEFAULTS,
7367
8630
  DIRECTORY_SCHEMAS,
8631
+ DeepSpaceAgentModelError,
8632
+ DeepSpaceAgentProfileError,
8633
+ DocsAssistantLimiter,
7368
8634
  GLOBAL_DO_TYPES,
7369
8635
  GLOBAL_DO_TYPE_NAMES,
7370
8636
  GameRoom,
@@ -7391,6 +8657,7 @@ export {
7391
8657
  VOTING_SCHEMAS,
7392
8658
  WORKSPACE_SCHEMAS,
7393
8659
  YjsRoom,
8660
+ acquireDocsPublicLease,
7394
8661
  analyzePermissions,
7395
8662
  apiWorkerFetch,
7396
8663
  appendMessage,
@@ -7401,6 +8668,8 @@ export {
7401
8668
  broadcastChange,
7402
8669
  broadcastYjsUpdate,
7403
8670
  buildCronContext,
8671
+ buildDocsAssistantPrompt,
8672
+ buildDocsAssistantTools,
7404
8673
  buildTableSelect,
7405
8674
  buildUiParts,
7406
8675
  callRegistry,
@@ -7424,8 +8693,12 @@ export {
7424
8693
  createScopedR2Handler,
7425
8694
  dataToColumnValues,
7426
8695
  decodeJwtPayload,
8696
+ deepSpaceAgentErrorSummary,
7427
8697
  deleteChatCascade,
7428
8698
  deleteRecord,
8699
+ docsAssistantErrorSummary,
8700
+ docsAssistantLimiterManifestEntry,
8701
+ docsPublicClientKey,
7429
8702
  enqueueJob,
7430
8703
  executeQuery,
7431
8704
  getAllUsers,
@@ -7456,6 +8729,8 @@ export {
7456
8729
  isTestAccountEmail,
7457
8730
  isTestAccountTier,
7458
8731
  lintSchema,
8732
+ loadDocsPublishedCorpus,
8733
+ loadDocsPublishedManifest,
7459
8734
  loadMessages,
7460
8735
  makeDefaultSummarizer,
7461
8736
  matchOrigin,
@@ -7463,24 +8738,37 @@ export {
7463
8738
  meterUsage,
7464
8739
  meterVectorize,
7465
8740
  noopPermissionContext,
8741
+ normalizeDocsAssistantHistory,
8742
+ normalizeDocsRoute,
8743
+ parseDocsCorpus,
7466
8744
  parseQuotaLimit,
7467
8745
  platformCorsPatterns,
7468
8746
  platformWorkerFetch,
7469
8747
  prepareMessagesWithCompaction,
7470
8748
  priceBindingUsageEvent,
7471
8749
  putRecord,
8750
+ readDocsPublishedPage,
8751
+ readDocsRoute,
7472
8752
  readRecord,
7473
8753
  recordMatchesSubscription,
7474
8754
  refundInvoice,
8755
+ registerDeepSpaceDocs,
8756
+ registerDocsAssistantRoutes,
8757
+ registerDocsMcpRoutes,
8758
+ registerDocsStaticRoutes,
7475
8759
  registerUser,
7476
8760
  registryErrorJson,
7477
8761
  registryStub,
8762
+ releaseDocsPublicLease,
7478
8763
  requireSubscription,
7479
8764
  resolveAppName,
7480
8765
  resolveColumn,
8766
+ resolveDocsPublishedCorpus,
7481
8767
  rowToData,
7482
8768
  runMigrations,
7483
8769
  saveYjsDoc,
8770
+ searchDocsCorpus,
8771
+ streamDeepSpaceAgent,
7484
8772
  timingSafeEqualHex,
7485
8773
  timingSafeEqualStrings,
7486
8774
  totalChars,