deepspace 0.4.3 → 0.4.5

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.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { LanguageModel, ModelMessage } from 'ai';
1
+ import { LanguageModel, ToolSet, ModelMessage } from 'ai';
2
2
  import * as Y from 'yjs';
3
3
  import * as better_auth from 'better-auth';
4
+ import { betterAuth } from 'better-auth';
4
5
  import * as better_auth_plugins from 'better-auth/plugins';
5
6
  import { Context } from 'hono';
6
7
 
@@ -31,6 +32,30 @@ type ToolResult = {
31
32
  success: false;
32
33
  error: string;
33
34
  };
35
+ /**
36
+ * Page size the assistant defaults to for `records.query` when it omits
37
+ * `limit`. Keeps a model-issued unbounded scan from blowing the tool-result
38
+ * byte cap and gives the model a usable first page. Raise `limit` to page
39
+ * through more (still subject to the cap; see
40
+ * `DEFAULT_CONTEXT_CONFIG.toolResultCap` / `capToolResultSize`, which truncates
41
+ * oversized pages gracefully rather than dropping them).
42
+ *
43
+ * Applied by the AI tool layer via `applyAiToolDefaults`, never by the shared
44
+ * tools-api dispatch, so internal record readers (chat history, cron, app
45
+ * `actions.query`) stay unbounded.
46
+ */
47
+ declare const DEFAULT_QUERY_LIMIT = 50;
48
+ /**
49
+ * Fill in assistant-only parameter defaults for a built-in tool call.
50
+ *
51
+ * Applied by the AI tool layer (`buildTools`) before a model-issued tool call
52
+ * is dispatched, so the default only affects calls the assistant makes.
53
+ * `records.query` doubles as the SDK's general record-read primitive (chat
54
+ * history, cron, app `actions.query`); those callers reach the tools dispatch
55
+ * directly and must return every row, so this default must not live in the
56
+ * dispatch itself. Pure and non-mutating.
57
+ */
58
+ declare function applyAiToolDefaults(toolName: string, params: Record<string, unknown>): Record<string, unknown>;
34
59
  /**
35
60
  * Built-in tool definitions for storage, record, schema, user, and backup operations
36
61
  */
@@ -470,7 +495,7 @@ interface CronContext {
470
495
  };
471
496
  /**
472
497
  * Call a platform integration endpoint (e.g. "openai/chat-completion",
473
- * "freepik/text-to-image-classic"). Billed to the app owner — the same
498
+ * "fal/run-model"). Billed to the app owner using the same
474
499
  * APP_OWNER_JWT the rest of the SDK uses for server-side billed calls.
475
500
  */
476
501
  integrations: {
@@ -569,6 +594,49 @@ interface DeepSpaceAIOptions {
569
594
  */
570
595
  declare function createDeepSpaceAI(env: DeepSpaceAIEnv, provider: Provider, options?: DeepSpaceAIOptions): DeepSpaceModelFactory;
571
596
 
597
+ /**
598
+ * Composio tools for the Vercel AI SDK.
599
+ *
600
+ * Turns the platform's Composio integration into ready-to-use Vercel AI SDK
601
+ * tools, so an agent (or the DeepSpace AI chat) can take actions on a user's
602
+ * connected apps. Each returned tool's `execute()` calls our own
603
+ * `composio/execute-tool` endpoint through the api-worker proxy, so our auth
604
+ * and per-user billing apply. This is the managed-proxy equivalent of
605
+ * Composio's `@composio/vercel` provider, which talks straight to Composio.
606
+ *
607
+ * `authToken` must be the END USER's JWT: Composio resolves the user's
608
+ * connected accounts by the JWT subject, and that user is billed.
609
+ *
610
+ * Usage (inside a request handler, e.g. /api/ai/chat):
611
+ *
612
+ * import { composioTools, createDeepSpaceAI } from 'deepspace/worker'
613
+ * const jwt = c.req.header('Authorization')!.slice(7)
614
+ * const tools = await composioTools(c.env, { toolkit: 'gmail', authToken: jwt })
615
+ * const ai = createDeepSpaceAI(c.env, 'anthropic', { authToken: jwt })
616
+ * const result = streamText({ model: ai('claude-sonnet-4-6'), prompt, tools })
617
+ *
618
+ * Merge with your own tools: `tools: { ...buildTools(executor), ...composio }`.
619
+ */
620
+
621
+ interface ComposioToolsOptions {
622
+ /** End user's JWT. Required: the tools run as this user and bill them. */
623
+ authToken: string;
624
+ /** Restrict to one toolkit (e.g. 'gmail', 'github'). */
625
+ toolkit?: string;
626
+ /** Explicit tool slugs to expose (e.g. ['GMAIL_SEND_EMAIL']). */
627
+ tools?: string[];
628
+ /** Natural-language search to pick relevant tools. */
629
+ search?: string;
630
+ /** Cap how many tools are exposed (default 20). Keep small: the LLM sees them all. */
631
+ limit?: number;
632
+ }
633
+ /**
634
+ * Fetch Composio tools for the given scope and return them as a Vercel AI SDK
635
+ * `ToolSet`. One `list-tools` call provides each tool's JSON Schema; each tool
636
+ * executes via `composio/execute-tool` as the authenticated user.
637
+ */
638
+ declare function composioTools(env: ApiWorkerEnv, options: ComposioToolsOptions): Promise<ToolSet>;
639
+
572
640
  /**
573
641
  * captureScreenshot — call platform-worker /internal/screenshot.
574
642
  *
@@ -657,8 +725,17 @@ declare function truncateOldToolResults(messages: ChatTurn[], keepRecent: number
657
725
  */
658
726
  declare function applySlidingWindow(messages: ChatTurn[], charCap: number, minKept: number): ChatTurn[];
659
727
  /**
660
- * Replace oversized tool results with an error telling the agent to narrow
661
- * its query. Keeps a small preview so the agent can see what it got.
728
+ * Keep an individual tool result under `byteCap`.
729
+ *
730
+ * If the payload carries a list of items (e.g. a `records.query` result), it is
731
+ * degraded gracefully: as many leading items as fit under the cap are returned,
732
+ * the `success: true` shape is preserved, and `{ truncated, returned, total }`
733
+ * flags are merged in next to the array so callers can still use the partial
734
+ * data and paginate for the rest.
735
+ *
736
+ * Only when there is no array to trim (or even an empty list still overflows
737
+ * because of oversized sibling fields) does it fall back to replacing the
738
+ * result with an error + small preview telling the agent to narrow its query.
662
739
  */
663
740
  declare function capToolResultSize(result: unknown, byteCap: number): unknown;
664
741
  /**
@@ -2971,11 +3048,6 @@ declare function timingSafeEqualHex(a: string, b: string): Promise<boolean>;
2971
3048
 
2972
3049
  declare function decodeJwtPayload(token: string | null | undefined): TokenDebugInfo | undefined;
2973
3050
 
2974
- /**
2975
- * Better Auth configuration factory for DeepSpace
2976
- *
2977
- * Provides pre-configured Better Auth instances for Cloudflare Workers + D1.
2978
- */
2979
3051
  interface DeepSpaceAuthConfig {
2980
3052
  /** D1 database binding */
2981
3053
  database: D1Database;
@@ -2993,29 +3065,28 @@ interface DeepSpaceAuthConfig {
2993
3065
  clientId: string;
2994
3066
  clientSecret: string;
2995
3067
  };
3068
+ /** Microsoft (Entra ID) OAuth credentials (optional) */
3069
+ microsoft?: {
3070
+ clientId: string;
3071
+ clientSecret: string;
3072
+ };
3073
+ /**
3074
+ * Apple (Sign in with Apple) credentials (optional). Pass the raw
3075
+ * Sign-in-with-Apple key material; the ES256 client-secret JWT is minted
3076
+ * on demand, so there is no six-month token to rotate by hand.
3077
+ */
3078
+ apple?: {
3079
+ clientId: string;
3080
+ teamId: string;
3081
+ keyId: string;
3082
+ privateKey: string;
3083
+ };
2996
3084
  /** Enable email/password authentication */
2997
3085
  emailAndPassword?: boolean;
2998
3086
  /** Trusted origins for CORS */
2999
3087
  trustedOrigins?: string[];
3000
3088
  }
3001
- /**
3002
- * Create a Better Auth instance configured for DeepSpace.
3003
- *
3004
- * This is called per-request in the auth worker since D1 bindings
3005
- * are request-scoped in Cloudflare Workers.
3006
- */
3007
3089
  declare function createDeepSpaceAuth(config: DeepSpaceAuthConfig): better_auth.Auth<{
3008
- database: D1Database;
3009
- baseURL: string;
3010
- secret: string;
3011
- emailAndPassword: {
3012
- enabled: boolean;
3013
- };
3014
- socialProviders: Record<string, {
3015
- clientId: string;
3016
- clientSecret: string;
3017
- }>;
3018
- trustedOrigins: string[];
3019
3090
  plugins: [{
3020
3091
  id: "organization";
3021
3092
  endpoints: better_auth_plugins.OrganizationEndpoints<better_auth_plugins.OrganizationOptions & {
@@ -3815,6 +3886,30 @@ declare function createDeepSpaceAuth(config: DeepSpaceAuthConfig): better_auth.A
3815
3886
  INVALID_TWO_FACTOR_COOKIE: better_auth.RawError<"INVALID_TWO_FACTOR_COOKIE">;
3816
3887
  };
3817
3888
  }];
3889
+ advanced?: {
3890
+ cookies: {
3891
+ state: {
3892
+ attributes: {
3893
+ sameSite: "none";
3894
+ secure: true;
3895
+ };
3896
+ };
3897
+ oauth_state: {
3898
+ attributes: {
3899
+ sameSite: "none";
3900
+ secure: true;
3901
+ };
3902
+ };
3903
+ };
3904
+ } | undefined;
3905
+ database: D1Database;
3906
+ baseURL: string;
3907
+ secret: string;
3908
+ emailAndPassword: {
3909
+ enabled: boolean;
3910
+ };
3911
+ socialProviders: Parameters<typeof betterAuth>[0]["socialProviders"];
3912
+ trustedOrigins: string[];
3818
3913
  }>;
3819
3914
  type DeepSpaceAuth = ReturnType<typeof createDeepSpaceAuth>;
3820
3915
 
@@ -3971,4 +4066,4 @@ declare class RefundError extends Error {
3971
4066
  constructor(message: string, status: number);
3972
4067
  }
3973
4068
 
3974
- export { AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, BaseRoom, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CONVERSATION_SCHEMAS, COST_RATES, CancelSubscriptionError, type CancelSubscriptionOpts, type CancelSubscriptionResult, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ConvMemberData, type ConvMessageData, type ConvReactionData, type ConvReadCursorData, type ConvVoteData, type ConversationStateData, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DebugApiContext, type DeepSpaceAIEnv, type DeepSpaceAIOptions, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DirectoryCommunityData, type DirectoryConversationData, type DirectoryMembershipData, type DirectoryPostData, type DoMigrationDirective, type DoMigrationPlan, type ExistingDOBinding, GLOBAL_DO_TYPES, GLOBAL_DO_TYPE_NAMES, type GameInput, GameRoom, type GameRoomConfig, type GetActionData, type GlobalDOType, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, MESSAGES_SCHEMA, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlanInfo, type PlatformWorkerEnv, type Player, type PrefixResult, type PresencePeer, PresenceRoom, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, type RecordContext, RecordRoom, type RecordRoomConfig, RefundError, type RefundOpts, type RefundResult, type ResolvedColumn, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, SYSTEM_COLLECTION_SCHEMAS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotOptions, type ScreenshotResult, type SharedConnection, type StoredRecord, SubscriptionAuthError, type SubscriptionContext, type SubscriptionRead, SubscriptionRequiredError, type SubscriptionStatus, type Summarizer, type TokenDebugInfo, type ToolResult, type ToolSchema, type ToolsApiContext, USERS_COLUMNS, type User, type UserAttachment, type UserContext, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, type YjsContext, YjsRoom, analyzePermissions, apiWorkerFetch, appendMessage, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, broadcastChange, broadcastYjsUpdate, buildCronContext, buildTableSelect, buildUiParts, canCreate, canDelete, canRead, canUpdate, cancelSubscription, capToolResultSize, captureScreenshot, checkFieldPermissions, coerceValue, collectionTableName, columnId, computeDoMigration, computeHmacHex, createChat, createDeepSpaceAI, createDeepSpaceAuth, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deleteChatCascade, deleteRecord, enqueueJob, executeQuery, getAllUsers, getChat, getGlobalDOSchemas, getGlobalDOType, getOrCreateYjsDoc, getRecord, getRolePermissions, getSubscription, getUser, getYjsDocKey, handleApiRequest, handleDelete, handlePut, handleSetRole, handleSubscribe, handleToolsRequest, handleUnsubscribe, handleUserList, handleUserUpdate, handleYjsBinaryMessage, handleYjsJoin, handleYjsLeave, isAutoProvision, isOwner, lintSchema, loadMessages, makeDefaultSummarizer, meterAi, meterUsage, meterVectorize, noopPermissionContext, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, putRecord, readRecord, recordMatchesSubscription, refundInvoice, registerUser, requireSubscription, resolveAppName, resolveColumn, rowToData, runMigrations, saveYjsDoc, timingSafeEqualHex, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema };
4069
+ export { AI_CHATS_SCHEMA, AI_MESSAGES_SCHEMA, ALLOWED_BINDING_TYPES, APP_NAME_RULES, AUTO_PROVISIONABLE_TYPES, AUTO_PROVISION_SENTINEL, type ActionContext, type ActionHandler, type ActionResult, type ActionTools, type ApiWorkerEnv, type AppNameResolution, type AppNameValidation, type AuthWorkerEnv, BASE_USERS_SCHEMA, BUILT_IN_TOOLS, BaseRoom, CHANNELS_SCHEMA, CHANNEL_INVITATIONS_SCHEMA, CHANNEL_MEMBERS_SCHEMA, CONVERSATION_SCHEMAS, COST_RATES, CancelSubscriptionError, type CancelSubscriptionOpts, type CancelSubscriptionResult, CanvasRoom, type CanvasShape, type ChatContextConfig, type ChatMessageRow, type ChatRow, type ChatTurn, type CollectionPermissionSummary, type CollectionSchema, type ColumnDefinition, type ColumnInterpretation, type ComposioToolsOptions, type ConvMemberData, type ConvMessageData, type ConvReactionData, type ConvReadCursorData, type ConvVoteData, type ConversationStateData, type CronContext, type CronExecution, CronRoom, type CronRoomConfig, type CronTask, type CustomBinding, type CustomBindingManifest, DEFAULT_CONTEXT_CONFIG, DEFAULT_DO_MANIFEST, DEFAULT_QUERY_LIMIT, DIRECTORY_SCHEMAS, type DOBindings, type DOManifest, type DOManifestEntry, type DebugApiContext, type DeepSpaceAIEnv, type DeepSpaceAIOptions, type DeepSpaceAuth, type DeepSpaceAuthConfig, type DirectoryCommunityData, type DirectoryConversationData, type DirectoryMembershipData, type DirectoryPostData, type DoMigrationDirective, type DoMigrationPlan, type ExistingDOBinding, GLOBAL_DO_TYPES, GLOBAL_DO_TYPE_NAMES, type GameInput, GameRoom, type GameRoomConfig, type GetActionData, type GlobalDOType, type Job, type JobContext, JobRoom, type JobRoomConfig, type JobStatus, type JwtClaims, type JwtVerifierConfig, MESSAGES_SCHEMA, type MutateActionData, type PermissionAnalysis, type PermissionContext, type PermissionLevel, type PermissionSource, type PlanInfo, type PlatformWorkerEnv, type Player, type PrefixResult, type PresencePeer, PresenceRoom, type QueryActionData, REACTIONS_SCHEMA, READ_RECEIPTS_SCHEMA, RESERVED_BINDING_NAMES, RESERVED_COLLECTION_NAMES, type RecordContext, RecordRoom, type RecordRoomConfig, RefundError, type RefundOpts, type RefundResult, type ResolvedColumn, type ResolvedPermission, type RolePermissions, type RunMigrationsResult, SYSTEM_COLLECTION_SCHEMAS, SYSTEM_MANAGED_COLUMNS, SchemaRegistry, type ScopeContext, type ScopedR2Auth, type ScopedR2Config, type ScopedR2Handler, type ScreenshotEnv, type ScreenshotOptions, type ScreenshotResult, type SharedConnection, type StoredRecord, SubscriptionAuthError, type SubscriptionContext, type SubscriptionRead, SubscriptionRequiredError, type SubscriptionStatus, type Summarizer, type TokenDebugInfo, type ToolResult, type ToolSchema, type ToolsApiContext, USERS_COLUMNS, type User, type UserAttachment, type UserContext, VOTING_SCHEMAS, type ValidationError, type VerifiedAuth, type VerifyOutcome, type VerifyResult, type Viewport, WORKSPACE_SCHEMAS, type YjsContext, YjsRoom, analyzePermissions, apiWorkerFetch, appendMessage, applyAiToolDefaults, applySlidingWindow, authWorkerFetch, bindingManifestFromOutputConfig, broadcastChange, broadcastYjsUpdate, buildCronContext, buildTableSelect, buildUiParts, canCreate, canDelete, canRead, canUpdate, cancelSubscription, capToolResultSize, captureScreenshot, checkFieldPermissions, coerceValue, collectionTableName, columnId, composioTools, computeDoMigration, computeHmacHex, createChat, createDeepSpaceAI, createDeepSpaceAuth, createScopedR2Handler, dataToColumnValues, decodeJwtPayload, deleteChatCascade, deleteRecord, enqueueJob, executeQuery, getAllUsers, getChat, getGlobalDOSchemas, getGlobalDOType, getOrCreateYjsDoc, getRecord, getRolePermissions, getSubscription, getUser, getYjsDocKey, handleApiRequest, handleDelete, handlePut, handleSetRole, handleSubscribe, handleToolsRequest, handleUnsubscribe, handleUserList, handleUserUpdate, handleYjsBinaryMessage, handleYjsJoin, handleYjsLeave, isAutoProvision, isOwner, lintSchema, loadMessages, makeDefaultSummarizer, meterAi, meterUsage, meterVectorize, noopPermissionContext, platformWorkerFetch, prepareMessagesWithCompaction, priceBindingUsageEvent, putRecord, readRecord, recordMatchesSubscription, refundInvoice, registerUser, requireSubscription, resolveAppName, resolveColumn, rowToData, runMigrations, saveYjsDoc, timingSafeEqualHex, totalChars, truncateOldToolResults, turnsToCoreMessages, unwrapToolOutput, updateChat, validateAppName, validateBindingManifest, validateDoManifest, verifyJwt, workspaceAccountsSchema, workspaceContentSharesSchema, workspaceEmailHandlesSchema, workspaceFormResponsesSchema, workspacePeopleSchema, workspaceProjectsSchema, workspaceTagsSchema, workspaceTasksSchema, workspaceTeamMembersSchema, workspaceTeamsSchema, workspaceTransactionsSchema };
package/dist/server.js CHANGED
@@ -1,15 +1,22 @@
1
1
  // src/server/utils/tools.ts
2
+ var DEFAULT_QUERY_LIMIT = 50;
3
+ function applyAiToolDefaults(toolName, params) {
4
+ if (toolName === "records.query" && params.limit === void 0) {
5
+ return { ...params, limit: DEFAULT_QUERY_LIMIT };
6
+ }
7
+ return params;
8
+ }
2
9
  var BUILT_IN_TOOLS = [
3
10
  // Record tools (structured collections with RBAC)
4
11
  {
5
12
  name: "records.query",
6
- description: "Query records from a collection with optional filtering, ordering, and limits",
13
+ description: `Query records from a collection with optional filtering, ordering, and pagination. Returns at most \`limit\` records (default ${DEFAULT_QUERY_LIMIT}). Large result sets are capped at ~30KB: an oversized page is truncated to the leading records that fit and flagged with \`{ truncated, returned, total }\`, so narrow with a \`where\` filter (or page via \`orderBy\` + \`limit\`) to see the rest.`,
7
14
  params: {
8
15
  collection: { type: "string", description: "Collection name to query", required: true },
9
16
  where: { type: "object", description: "Filter object with field=value equality conditions", required: false },
10
17
  orderBy: { type: "string", description: 'Field to order by (or "createdAt"/"updatedAt")', required: false },
11
18
  orderDir: { type: "string", description: 'Order direction: "asc" or "desc" (default: "desc")', required: false },
12
- limit: { type: "number", description: "Maximum number of records to return", required: false }
19
+ limit: { type: "number", description: `Maximum number of records to return (default: ${DEFAULT_QUERY_LIMIT}). Oversized pages are still capped at ~30KB and truncated with returned/total flags.`, required: false }
13
20
  }
14
21
  },
15
22
  {
@@ -446,7 +453,7 @@ function authWorkerFetch(env, path, init) {
446
453
  function buildCronContext(env, ownerUserId, roomId = "default") {
447
454
  const roomIdObj = env.RECORD_ROOMS.idFromName(roomId);
448
455
  const room = env.RECORD_ROOMS.get(roomIdObj);
449
- async function executeTool3(tool, params) {
456
+ async function executeTool3(tool2, params) {
450
457
  const response = await room.fetch(new Request("https://internal/api/tools/execute", {
451
458
  method: "POST",
452
459
  headers: {
@@ -454,11 +461,11 @@ function buildCronContext(env, ownerUserId, roomId = "default") {
454
461
  "X-User-Id": ownerUserId,
455
462
  "X-App-Action": "true"
456
463
  },
457
- body: JSON.stringify({ tool, params })
464
+ body: JSON.stringify({ tool: tool2, params })
458
465
  }));
459
466
  const result = await response.json();
460
467
  if (!result.success) {
461
- throw new Error(`RecordRoom tool ${tool} failed: ${result.error || "Unknown error"}`);
468
+ throw new Error(`RecordRoom tool ${tool2} failed: ${result.error || "Unknown error"}`);
462
469
  }
463
470
  return result.data;
464
471
  }
@@ -579,6 +586,87 @@ function createDeepSpaceAI(env, provider, options = {}) {
579
586
  }
580
587
  }
581
588
 
589
+ // src/server/utils/composio-tools.ts
590
+ import { tool, jsonSchema } from "ai";
591
+ var EMPTY_SCHEMA = { type: "object", properties: {} };
592
+ async function requestComposio(env, endpoint, params, authToken) {
593
+ const res = await apiWorkerFetch(env, `/api/integrations/composio/${endpoint}`, {
594
+ method: "POST",
595
+ headers: {
596
+ "Content-Type": "application/json",
597
+ Authorization: `Bearer ${authToken}`
598
+ },
599
+ body: JSON.stringify(params)
600
+ });
601
+ const text = await res.text();
602
+ let parsed = {};
603
+ try {
604
+ parsed = text ? JSON.parse(text) : {};
605
+ } catch {
606
+ }
607
+ const detail = parsed.message || parsed.error || text || `HTTP ${res.status}`;
608
+ return { ok: res.ok, success: parsed.success === true, data: parsed.data, detail };
609
+ }
610
+ function toolKey(slug) {
611
+ const key = slug.replace(/[^a-zA-Z0-9_-]/g, "_");
612
+ return key.length <= 64 ? key : key.slice(0, 64);
613
+ }
614
+ async function composioTools(env, options) {
615
+ const { authToken } = options;
616
+ if (!authToken) {
617
+ throw new Error(
618
+ "composioTools: options.authToken is required. Pass the end user's JWT so the tools run as that user (Composio resolves their connected accounts by it) and the calls are billed to them."
619
+ );
620
+ }
621
+ const list = await requestComposio(
622
+ env,
623
+ "list-tools",
624
+ {
625
+ toolkit: options.toolkit,
626
+ toolSlugs: options.tools?.length ? options.tools.join(",") : void 0,
627
+ search: options.search,
628
+ limit: options.limit ?? 20
629
+ },
630
+ authToken
631
+ );
632
+ if (!list.ok || !list.success) {
633
+ throw new Error(`composioTools: failed to list Composio tools: ${list.detail}`);
634
+ }
635
+ const items = Array.isArray(list.data?.items) ? list.data.items : [];
636
+ const tools = {};
637
+ for (const item of items) {
638
+ if (!item?.slug) continue;
639
+ const slug = item.slug;
640
+ const schema = item.input_parameters ?? EMPTY_SCHEMA;
641
+ tools[toolKey(slug)] = tool({
642
+ description: item.description ?? item.name ?? slug,
643
+ inputSchema: jsonSchema(schema),
644
+ execute: async (args) => {
645
+ const r = await requestComposio(
646
+ env,
647
+ "execute-tool",
648
+ { slug, arguments: args ?? {} },
649
+ authToken
650
+ );
651
+ if (!r.ok || !r.success) {
652
+ return { error: true, message: `Composio tool ${slug} failed: ${r.detail}` };
653
+ }
654
+ const data = r.data;
655
+ if (data && typeof data === "object" && data.requiresConnection) {
656
+ const toolkit = data.toolkit;
657
+ return {
658
+ requiresConnection: true,
659
+ toolkit,
660
+ message: `The user has not connected ${toolkit ?? "this app"}. Ask them to connect it before using this tool.`
661
+ };
662
+ }
663
+ return data;
664
+ }
665
+ });
666
+ }
667
+ return tools;
668
+ }
669
+
582
670
  // src/server/utils/screenshot.ts
583
671
  async function captureScreenshot(env, opts) {
584
672
  const headers = new Headers({
@@ -666,6 +754,45 @@ function applySlidingWindow(messages, charCap, minKept) {
666
754
  }
667
755
  return out;
668
756
  }
757
+ var TRIMMABLE_ARRAY_PATHS = [
758
+ ["records"],
759
+ ["data", "records"],
760
+ ["items"],
761
+ ["data", "items"],
762
+ ["data"]
763
+ ];
764
+ function getPath(root, path) {
765
+ let cur = root;
766
+ for (const key of path) {
767
+ if (!cur || typeof cur !== "object") return void 0;
768
+ cur = cur[key];
769
+ }
770
+ return cur;
771
+ }
772
+ function setPath(root, path, value, flags) {
773
+ const obj = root && typeof root === "object" ? root : {};
774
+ const [head, ...tail] = path;
775
+ if (tail.length === 0) {
776
+ return { ...obj, [head]: value, ...flags };
777
+ }
778
+ return { ...obj, [head]: setPath(obj[head], tail, value, flags) };
779
+ }
780
+ function locateTrimmableArray(result) {
781
+ for (const path of TRIMMABLE_ARRAY_PATHS) {
782
+ const arr = getPath(result, path);
783
+ if (Array.isArray(arr)) return { path, items: arr };
784
+ }
785
+ for (const [key, value] of Object.entries(result)) {
786
+ if (Array.isArray(value)) return { path: [key], items: value };
787
+ }
788
+ const data = result.data;
789
+ if (data && typeof data === "object") {
790
+ for (const [key, value] of Object.entries(data)) {
791
+ if (Array.isArray(value)) return { path: ["data", key], items: value };
792
+ }
793
+ }
794
+ return void 0;
795
+ }
669
796
  function capToolResultSize(result, byteCap) {
670
797
  let serialized;
671
798
  try {
@@ -675,6 +802,31 @@ function capToolResultSize(result, byteCap) {
675
802
  }
676
803
  if (typeof serialized !== "string") return result;
677
804
  if (serialized.length <= byteCap) return result;
805
+ if (result && typeof result === "object") {
806
+ const located = locateTrimmableArray(result);
807
+ if (located) {
808
+ const { path, items } = located;
809
+ const total = items.length;
810
+ const build = (k) => setPath(result, path, items.slice(0, k), { truncated: true, returned: k, total });
811
+ const fits = (k) => {
812
+ try {
813
+ return JSON.stringify(build(k)).length <= byteCap;
814
+ } catch {
815
+ return false;
816
+ }
817
+ };
818
+ if (fits(0)) {
819
+ let lo = 0;
820
+ let hi = total;
821
+ while (lo < hi) {
822
+ const mid = Math.ceil((lo + hi) / 2);
823
+ if (fits(mid)) lo = mid;
824
+ else hi = mid - 1;
825
+ }
826
+ if (lo > 0) return build(lo);
827
+ }
828
+ }
829
+ }
678
830
  return {
679
831
  success: false,
680
832
  truncated: true,
@@ -883,7 +1035,7 @@ function makeDefaultSummarizer(env, options = {}) {
883
1035
  }
884
1036
 
885
1037
  // src/server/utils/chat-history.ts
886
- async function executeTool(stub, userId, tool, params) {
1038
+ async function executeTool(stub, userId, tool2, params) {
887
1039
  const res = await stub.fetch(new Request("https://internal/api/tools/execute", {
888
1040
  method: "POST",
889
1041
  headers: {
@@ -891,11 +1043,11 @@ async function executeTool(stub, userId, tool, params) {
891
1043
  "X-User-Id": userId,
892
1044
  "X-App-Action": "true"
893
1045
  },
894
- body: JSON.stringify({ tool, params })
1046
+ body: JSON.stringify({ tool: tool2, params })
895
1047
  }));
896
1048
  const body = await res.json();
897
1049
  if (!body.success) {
898
- throw new Error(`chat-history ${tool} failed: ${body.error ?? "unknown error"}`);
1050
+ throw new Error(`chat-history ${tool2} failed: ${body.error ?? "unknown error"}`);
899
1051
  }
900
1052
  return body.data;
901
1053
  }
@@ -2790,11 +2942,11 @@ async function handleToolsRequest(ctx, request, path) {
2790
2942
  }
2791
2943
  if (path.startsWith("tools/describe/")) {
2792
2944
  const toolName = path.replace("tools/describe/", "");
2793
- const tool = BUILT_IN_TOOLS.find((t) => t.name === toolName);
2794
- if (!tool) {
2945
+ const tool2 = BUILT_IN_TOOLS.find((t) => t.name === toolName);
2946
+ if (!tool2) {
2795
2947
  return Response.json({ error: `Tool not found: ${toolName}` }, { status: 404, headers: CORS_HEADERS2 });
2796
2948
  }
2797
- return Response.json({ tool }, { headers: CORS_HEADERS2 });
2949
+ return Response.json({ tool: tool2 }, { headers: CORS_HEADERS2 });
2798
2950
  }
2799
2951
  if (path.startsWith("tools/execute") && request.method === "POST") {
2800
2952
  return handleToolExecute(ctx, request);
@@ -2811,8 +2963,8 @@ async function handleToolExecute(ctx, request) {
2811
2963
  { status: 400, headers: CORS_HEADERS2 }
2812
2964
  );
2813
2965
  }
2814
- const { tool, params = {} } = body;
2815
- if (!tool) {
2966
+ const { tool: tool2, params = {} } = body;
2967
+ if (!tool2) {
2816
2968
  return Response.json(
2817
2969
  { success: false, error: 'Missing "tool" in request body' },
2818
2970
  { status: 400, headers: CORS_HEADERS2 }
@@ -2829,8 +2981,8 @@ async function handleToolExecute(ctx, request) {
2829
2981
  userRole = user.role;
2830
2982
  }
2831
2983
  }
2832
- if (tool.startsWith("yjs.")) {
2833
- const result2 = await executeYjsTool(ctx, tool, params, userId, userRole);
2984
+ if (tool2.startsWith("yjs.")) {
2985
+ const result2 = await executeYjsTool(ctx, tool2, params, userId, userRole);
2834
2986
  const status2 = result2.success ? 200 : result2.error?.includes("not found") ? 404 : 400;
2835
2987
  return Response.json(result2, { status: status2, headers: CORS_HEADERS2 });
2836
2988
  }
@@ -2841,7 +2993,7 @@ async function handleToolExecute(ctx, request) {
2841
2993
  getPermissionContext: ctx.getPermissionContext,
2842
2994
  send: ctx.send
2843
2995
  };
2844
- const result = await executeTool2(recordCtx, tool, params, userId, userRole, isAppAction);
2996
+ const result = await executeTool2(recordCtx, tool2, params, userId, userRole, isAppAction);
2845
2997
  const status = result.success ? 200 : result.error?.includes("not found") ? 404 : 400;
2846
2998
  return Response.json(result, { status, headers: CORS_HEADERS2 });
2847
2999
  }
@@ -6668,6 +6820,7 @@ async function timingSafeEqualHex(a, b) {
6668
6820
  // src/server/auth/betterAuth.ts
6669
6821
  import { betterAuth } from "better-auth";
6670
6822
  import { organization, twoFactor } from "better-auth/plugins";
6823
+ import { SignJWT, importPKCS8 } from "jose";
6671
6824
  function createDeepSpaceAuth(config) {
6672
6825
  const socialProviders = {};
6673
6826
  if (config.google) {
@@ -6676,6 +6829,20 @@ function createDeepSpaceAuth(config) {
6676
6829
  if (config.github) {
6677
6830
  socialProviders.github = config.github;
6678
6831
  }
6832
+ if (config.microsoft) {
6833
+ socialProviders.microsoft = config.microsoft;
6834
+ }
6835
+ if (config.apple) {
6836
+ const apple = config.apple;
6837
+ socialProviders.apple = async () => {
6838
+ try {
6839
+ return { clientId: apple.clientId, clientSecret: await generateAppleClientSecret(apple) };
6840
+ } catch (err) {
6841
+ console.error("[deepspace] failed to mint Apple client secret; Apple sign-in disabled", err);
6842
+ return { clientId: apple.clientId, clientSecret: "" };
6843
+ }
6844
+ };
6845
+ }
6679
6846
  return betterAuth({
6680
6847
  database: config.database,
6681
6848
  baseURL: config.baseURL,
@@ -6688,11 +6855,42 @@ function createDeepSpaceAuth(config) {
6688
6855
  "https://deep.space",
6689
6856
  "https://*.deep.space",
6690
6857
  "https://*.app.space",
6691
- "http://localhost:*"
6858
+ "http://localhost:*",
6859
+ // Apple posts its OAuth callback from this origin (form_post).
6860
+ "https://appleid.apple.com"
6692
6861
  ],
6862
+ // Apple returns its callback as a cross-site form_post, which a
6863
+ // SameSite=Lax cookie won't ride along with — so the OAuth handshake
6864
+ // cookie must be SameSite=None. Scoped to just that cookie; the session
6865
+ // cookie stays Lax. Gated on Apple being configured: deployments without
6866
+ // Apple keep Lax entirely. Note that when Apple IS configured, this also
6867
+ // relaxes the handshake cookie for Google/GitHub/Microsoft on the same
6868
+ // instance — which is safe, since the state is encrypted and verified
6869
+ // independently of SameSite (plus a separate per-flow CSRF cookie).
6870
+ ...config.apple ? {
6871
+ advanced: {
6872
+ cookies: {
6873
+ state: { attributes: { sameSite: "none", secure: true } },
6874
+ oauth_state: { attributes: { sameSite: "none", secure: true } }
6875
+ }
6876
+ }
6877
+ } : {},
6693
6878
  plugins: [organization(), twoFactor()]
6694
6879
  });
6695
6880
  }
6881
+ var cachedAppleSecret = null;
6882
+ async function generateAppleClientSecret(apple) {
6883
+ const now = Math.floor(Date.now() / 1e3);
6884
+ if (cachedAppleSecret && cachedAppleSecret.keyId === apple.keyId && cachedAppleSecret.expiresAt - now > 3600) {
6885
+ return cachedAppleSecret.token;
6886
+ }
6887
+ const pem = apple.privateKey.replace(/\\n/g, "\n");
6888
+ const key = await importPKCS8(pem, "ES256");
6889
+ const expiresAt = now + 180 * 24 * 60 * 60;
6890
+ const token = await new SignJWT({}).setProtectedHeader({ alg: "ES256", kid: apple.keyId }).setIssuer(apple.teamId).setSubject(apple.clientId).setAudience("https://appleid.apple.com").setIssuedAt(now).setExpirationTime(expiresAt).sign(key);
6891
+ cachedAppleSecret = { keyId: apple.keyId, token, expiresAt };
6892
+ return token;
6893
+ }
6696
6894
 
6697
6895
  // src/shared/subscription.ts
6698
6896
  var ENTITLED_STATUSES = /* @__PURE__ */ new Set([
@@ -6858,6 +7056,7 @@ export {
6858
7056
  CronRoom,
6859
7057
  DEFAULT_CONTEXT_CONFIG,
6860
7058
  DEFAULT_DO_MANIFEST,
7059
+ DEFAULT_QUERY_LIMIT,
6861
7060
  DIRECTORY_SCHEMAS,
6862
7061
  GLOBAL_DO_TYPES,
6863
7062
  GLOBAL_DO_TYPE_NAMES,
@@ -6883,6 +7082,7 @@ export {
6883
7082
  analyzePermissions,
6884
7083
  apiWorkerFetch,
6885
7084
  appendMessage,
7085
+ applyAiToolDefaults,
6886
7086
  applySlidingWindow,
6887
7087
  authWorkerFetch,
6888
7088
  bindingManifestFromOutputConfig,
@@ -6902,6 +7102,7 @@ export {
6902
7102
  coerceValue,
6903
7103
  collectionTableName,
6904
7104
  columnId,
7105
+ composioTools,
6905
7106
  computeDoMigration,
6906
7107
  computeHmacHex,
6907
7108
  createChat,