index.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. /**
  2. * Model-facing, workspace-authorized session-history search and read tools.
  3. *
  4. * @module @deepseek-ai/dsh-tool-session-query
  5. */
  6. import type { Context } from '@deepseek-ai/cordis'
  7. import z from '@deepseek-ai/schemastery'
  8. import { MAX_TIMER_DELAY_MS } from '@deepseek-ai/dsh-timeout'
  9. import { defineTool } from '@deepseek-ai/dsh-tools'
  10. import { toolInput } from './input.ts'
  11. import { operations } from './operations.ts'
  12. import { presentation } from './presentation.ts'
  13. /** Cordis plugin name used by Loader diagnostics. */
  14. export const name = 'tool-session-query'
  15. /** Capability services required by the model-facing consumer. */
  16. export const inject = ['tools', 'systemPrompt', 'sessionQuery', 'sessionProjections']
  17. /** Default maximum number of authorized search hits returned by one call. */
  18. export const DEFAULT_MAX_SEARCH_RESULTS = 100
  19. /** Default cooperative deadline for either full-text search tool. */
  20. export const DEFAULT_SEARCH_TIMEOUT_MS = 30_000
  21. /** Deployment-owned search count and timeout bounds. */
  22. export interface Config {
  23. /** Maximum authorized hits returned by one search call. Defaults to 100. */
  24. maxSearchResults?: number
  25. /** Cooperative full-text search deadline in milliseconds. Defaults to 30000. */
  26. searchTimeoutMs?: number
  27. }
  28. /** Schemastery config for Loader defaults and generated configuration docs. */
  29. export const Config: z<Config> = z.object({
  30. maxSearchResults: z.number().step(1).min(1).default(DEFAULT_MAX_SEARCH_RESULTS),
  31. searchTimeoutMs: z.number().step(1).min(1).max(MAX_TIMER_DELAY_MS).default(DEFAULT_SEARCH_TIMEOUT_MS),
  32. })
  33. interface ResolvedConfig {
  34. readonly maxSearchResults: number
  35. readonly searchTimeoutMs: number
  36. }
  37. const TEXT_OUTPUT = {
  38. schema: { type: 'string' as const },
  39. render: (_args: unknown, value: string) => [{ type: 'text' as const, text: value }],
  40. }
  41. const PROMPT_TEXT =
  42. 'Use session_search to find relevant work from prior sessions, or session_event_search to search earlier '
  43. + 'events in one session. Search results are cursor-free and workspace-scoped. Follow a useful hit with '
  44. + 'session_trace, session_event_trace, or session_event_read when you need lineage, relationships, or exact data.'
  45. /** Register all five tools and their shared model guidance. */
  46. export function apply(ctx: Context, config: Config): void {
  47. const resolved = resolveConfig(config)
  48. ctx.systemPrompt.section({
  49. name: 'tool:session-query',
  50. order: ctx.systemPrompt.getSectionOrder('TOOL_SESSION_QUERY'),
  51. text: PROMPT_TEXT,
  52. })
  53. ctx.tools.register(defineTool({
  54. name: 'session_search',
  55. description: 'Search prior sessions in the caller workspace and return the strongest matching event from each session.',
  56. parameters: toolInput.sessionSearchParameters,
  57. output: TEXT_OUTPUT,
  58. timeoutMs: resolved.searchTimeoutMs,
  59. execute: (args, exec) => operations.executeSessionSearch(ctx, args, exec, resolved.maxSearchResults),
  60. presentCall: presentation.presentSessionSearchCall,
  61. }))
  62. ctx.tools.register(defineTool({
  63. name: 'session_event_search',
  64. description: 'Search prior events in one authorized session; the current session excludes the step performing this call.',
  65. parameters: toolInput.eventSearchParameters,
  66. output: TEXT_OUTPUT,
  67. timeoutMs: resolved.searchTimeoutMs,
  68. execute: (args, exec) => operations.executeEventSearch(ctx, args, exec, resolved.maxSearchResults),
  69. presentCall: presentation.presentEventSearchCall,
  70. }))
  71. ctx.tools.register(defineTool({
  72. name: 'session_trace',
  73. description: 'Read the authorized session lineage around one session, including complete visible ancestor and descendant relationships.',
  74. parameters: toolInput.targetSessionParameter,
  75. output: TEXT_OUTPUT,
  76. isConcurrencySafe: () => true,
  77. execute: (args, exec) => operations.executeSessionTrace(ctx, args, exec),
  78. presentCall: presentation.presentSessionTraceCall,
  79. }))
  80. ctx.tools.register(defineTool({
  81. name: 'session_event_trace',
  82. description: 'Read every direct replacement and relationship to a cited source event for one event in an authorized session.',
  83. parameters: {
  84. ...toolInput.targetSessionParameter,
  85. seq: { type: 'integer', required: true, description: 'Target event sequence number.' },
  86. },
  87. output: TEXT_OUTPUT,
  88. isConcurrencySafe: () => true,
  89. execute: (args, exec) => operations.executeEventTrace(ctx, args, exec),
  90. presentCall: args => presentation.presentEventTargetCall('Trace event', args),
  91. }))
  92. ctx.tools.register(defineTool({
  93. name: 'session_event_read',
  94. description: 'Read one full unabridged event and optional neighboring raw-event summaries from an authorized session.',
  95. parameters: {
  96. ...toolInput.targetSessionParameter,
  97. seq: { type: 'integer', required: true, description: 'Target event sequence number.' },
  98. before: { type: 'integer', description: 'Number of preceding raw events to summarize. Omit for none.' },
  99. after: { type: 'integer', description: 'Number of following raw events to summarize. Omit for none.' },
  100. },
  101. output: TEXT_OUTPUT,
  102. isConcurrencySafe: () => true,
  103. execute: (args, exec) => operations.executeEventRead(ctx, args, exec),
  104. presentCall: args => presentation.presentEventTargetCall('Read event', args),
  105. }))
  106. }
  107. function resolveConfig(config: Config): ResolvedConfig {
  108. const maxSearchResults = config.maxSearchResults ?? DEFAULT_MAX_SEARCH_RESULTS
  109. const searchTimeoutMs = config.searchTimeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS
  110. if (!Number.isSafeInteger(maxSearchResults) || maxSearchResults < 1) {
  111. throw new TypeError('tool-session-query: maxSearchResults must be a positive safe integer')
  112. }
  113. if (!Number.isInteger(searchTimeoutMs) || searchTimeoutMs < 1 || searchTimeoutMs > MAX_TIMER_DELAY_MS) {
  114. throw new TypeError(
  115. `tool-session-query: searchTimeoutMs must be a positive integer no greater than ${MAX_TIMER_DELAY_MS}`,
  116. )
  117. }
  118. return { maxSearchResults, searchTimeoutMs }
  119. }