@roj-ai/sdk 0.2.18 → 0.2.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/bootstrap.d.ts.map +1 -1
  2. package/dist/bootstrap.js +6 -9
  3. package/dist/bootstrap.js.map +1 -1
  4. package/dist/config.d.ts +2 -0
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +3 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/core/image/types.d.ts +6 -0
  9. package/dist/core/image/types.d.ts.map +1 -1
  10. package/dist/core/sessions/session-manager.d.ts +2 -0
  11. package/dist/core/sessions/session-manager.d.ts.map +1 -1
  12. package/dist/core/sessions/session-manager.js +3 -0
  13. package/dist/core/sessions/session-manager.js.map +1 -1
  14. package/dist/core/system.d.ts +1 -0
  15. package/dist/core/system.d.ts.map +1 -1
  16. package/dist/core/system.js +2 -1
  17. package/dist/core/system.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/platform/index.d.ts +7 -0
  22. package/dist/platform/index.d.ts.map +1 -1
  23. package/dist/platform/index.js.map +1 -1
  24. package/dist/plugins/limits-guard/plugin.d.ts.map +1 -1
  25. package/dist/plugins/limits-guard/plugin.js +3 -9
  26. package/dist/plugins/limits-guard/plugin.js.map +1 -1
  27. package/dist/plugins/uploads/plugin.d.ts +2 -0
  28. package/dist/plugins/uploads/plugin.d.ts.map +1 -1
  29. package/dist/plugins/uploads/plugin.js +7 -5
  30. package/dist/plugins/uploads/plugin.js.map +1 -1
  31. package/dist/testing/test-harness.d.ts +1 -0
  32. package/dist/testing/test-harness.d.ts.map +1 -1
  33. package/dist/testing/test-harness.js +1 -0
  34. package/dist/testing/test-harness.js.map +1 -1
  35. package/package.json +2 -2
  36. package/src/bootstrap.ts +7 -9
  37. package/src/config.ts +5 -0
  38. package/src/core/image/types.ts +6 -0
  39. package/src/core/sessions/session-manager.ts +4 -0
  40. package/src/core/system.ts +3 -0
  41. package/src/index.ts +3 -0
  42. package/src/platform/index.ts +7 -0
  43. package/src/plugins/limits-guard/plugin.ts +4 -13
  44. package/src/plugins/uploads/plugin.ts +9 -4
  45. package/src/testing/test-harness.ts +2 -0
@@ -9,6 +9,11 @@ export interface ImageResizeOptions {
9
9
  signal?: AbortSignal
10
10
  }
11
11
 
12
+ /**
13
+ * Paths are on the host's `FileSystem`: the caller reads the result through it and unlinks
14
+ * `tempFile` there once the image is encoded. When an image cannot be shrunk, return the original
15
+ * path: the LLM path treats a throw as an unreadable image.
16
+ */
12
17
  export interface ImageResizer {
13
18
  resize(filePath: string, mimeType: string, options?: ImageResizeOptions): Promise<ImageResizeResult>
14
19
  }
@@ -16,6 +21,7 @@ export interface ImageResizer {
16
21
  export interface ImageResizeResult {
17
22
  path: string
18
23
  mimeType: string
24
+ /** A file the resizer created; the caller owns it from here and deletes it. */
19
25
  tempFile?: string
20
26
  }
21
27
 
@@ -83,6 +83,7 @@ export interface SessionManagerOptions {
83
83
  onUserOutput?: UserOutputCallback
84
84
  preprocessorRegistry?: PreprocessorRegistry
85
85
  uploadArchiveLimits?: ArchiveLimitOverrides
86
+ uploadMaxFileSize?: number
86
87
  resourceArchiveLimits?: ArchiveLimitOverrides
87
88
  llmLogger?: LLMLogger
88
89
  portPool?: PortPool
@@ -142,6 +143,7 @@ export class SessionManager {
142
143
  private readonly onUserOutput?: UserOutputCallback
143
144
  private readonly preprocessorRegistry?: PreprocessorRegistry
144
145
  private readonly uploadArchiveLimits?: ArchiveLimitOverrides
146
+ private readonly uploadMaxFileSize?: number
145
147
  private readonly resourceArchiveLimits?: ArchiveLimitOverrides
146
148
  private readonly llmLogger?: LLMLogger
147
149
  private readonly portPool?: PortPool
@@ -171,6 +173,7 @@ export class SessionManager {
171
173
  this.onUserOutput = options.onUserOutput
172
174
  this.preprocessorRegistry = options.preprocessorRegistry
173
175
  this.uploadArchiveLimits = options.uploadArchiveLimits
176
+ this.uploadMaxFileSize = options.uploadMaxFileSize
174
177
  this.resourceArchiveLimits = options.resourceArchiveLimits
175
178
  this.llmLogger = options.llmLogger
176
179
  this.portPool = options.portPool
@@ -1079,6 +1082,7 @@ export class SessionManager {
1079
1082
  dataFileStore: this.dataFileStore,
1080
1083
  preprocessorRegistry: this.preprocessorRegistry,
1081
1084
  archiveLimits: this.uploadArchiveLimits,
1085
+ maxFileSize: this.uploadMaxFileSize,
1082
1086
  })
1083
1087
 
1084
1088
  if (this.resourceArchiveLimits) {
@@ -121,6 +121,7 @@ export interface CreateSystemOptions<TPlugins extends readonly PluginDefinition<
121
121
  onUserOutput?: UserOutputCallback
122
122
  preprocessorRegistry?: PreprocessorRegistry
123
123
  uploadArchiveLimits?: ArchiveLimitOverrides
124
+ uploadMaxFileSize?: number
124
125
  resourceArchiveLimits?: ArchiveLimitOverrides
125
126
  llmLogger?: LLMLogger
126
127
  portPool?: PortPool
@@ -155,6 +156,7 @@ export function createSystem<const TPlugins extends readonly PluginDefinition<st
155
156
  onUserOutput,
156
157
  preprocessorRegistry,
157
158
  uploadArchiveLimits,
159
+ uploadMaxFileSize,
158
160
  resourceArchiveLimits,
159
161
  llmLogger,
160
162
  portPool,
@@ -181,6 +183,7 @@ export function createSystem<const TPlugins extends readonly PluginDefinition<st
181
183
  onUserOutput,
182
184
  preprocessorRegistry,
183
185
  uploadArchiveLimits,
186
+ uploadMaxFileSize,
184
187
  resourceArchiveLimits,
185
188
  llmLogger,
186
189
  portPool,
package/src/index.ts CHANGED
@@ -196,6 +196,9 @@ export type {
196
196
  ExecFileResult,
197
197
  FileHandle,
198
198
  FileSystem,
199
+ ImageResizeOptions,
200
+ ImageResizer,
201
+ ImageResizeResult,
199
202
  Platform,
200
203
  ProcessRunner,
201
204
  SpawnOptions,
@@ -6,6 +6,7 @@
6
6
  * runtime packages (e.g. `@roj-ai/sdk/bun-platform`) and wired in at bootstrap time.
7
7
  */
8
8
 
9
+ import type { ImageResizer } from '../core/image/types.js'
9
10
  import type { FileSystem } from './fs.js'
10
11
  import type { FsRevision } from './fs-revision.js'
11
12
  import type { GitClient } from './git.js'
@@ -39,6 +40,7 @@ export type { LiveScheduler, Scheduler, WakeHandler } from './scheduler.js'
39
40
  export type { SessionLogPage, SessionLogStore } from './session-log.js'
40
41
  export type { ShellConfinement, ShellGrant, ShellLimits, ShellRunner, ShellRunOptions, ShellRunResult } from './shell.js'
41
42
 
43
+ export type { ImageResizeOptions, ImageResizer, ImageResizeResult } from '../core/image/types.js'
42
44
  export type { ServiceExecutorPort } from '../plugins/services/service.js'
43
45
  export type { PortPool } from '../plugins/services/port-pool.js'
44
46
  export type { ServiceConfig, ServiceStatus, ServiceStopSource } from '../plugins/services/schema.js'
@@ -77,6 +79,11 @@ export interface Platform {
77
79
  * writing `sessions/<id>/calls/<callId>.json` and listing that directory.
78
80
  */
79
81
  llmCallLog?: LLMCallStore
82
+ /**
83
+ * Shrinks images before they reach a model. Absent on hosts with a process table,
84
+ * which keep resizing with the `vipsheader` / `vipsthumbnail` binaries.
85
+ */
86
+ imageResizer?: ImageResizer
80
87
  /** Absolute path to the OS temp directory (equivalent to `os.tmpdir()` on Node/Bun). */
81
88
  tmpDir: string
82
89
  }
@@ -122,18 +122,11 @@ export type BudgetExceededEvent = (typeof limitsEvents)['Events']['budget_exceed
122
122
  // ============================================================================
123
123
 
124
124
  /**
125
- * Resolve tool name from agent state for tool_completed/tool_failed events.
126
- * Must run BEFORE core reducer (which clears executingToolCall).
125
+ * Name of the tool behind a tool_completed/tool_failed event. Plugin reducers run after the
126
+ * core reducer, which has already cleared the running call and recorded the named result.
127
127
  */
128
- function resolveToolName(
129
- sessionState: { agents: Map<AgentId, { executingToolCall?: { toolName: string }; pendingToolCalls: Array<{ id: string; name: string }> }> },
130
- agentId: AgentId,
131
- toolCallId: string,
132
- ): string {
133
- const agent = sessionState.agents.get(agentId)
134
- if (!agent) return 'unknown'
135
- return agent.executingToolCall?.toolName
136
- ?? agent.pendingToolCalls.find((tc) => tc.id === toolCallId)?.name
128
+ function resolveToolName(sessionState: SessionState, agentId: AgentId, toolCallId: ToolCallId): string {
129
+ return sessionState.agents.get(agentId)?.pendingToolResults.find((result) => result.toolCallId === toolCallId)?.toolName
137
130
  ?? 'unknown'
138
131
  }
139
132
 
@@ -279,7 +272,6 @@ export const limitsGuardPlugin = definePlugin('limits-guard')
279
272
  const counters = limits.get(event.agentId)
280
273
  if (!counters) return limits
281
274
 
282
- // Resolve tool name from agent state (must run before core clears executingToolCall)
283
275
  const toolName = resolveToolName(sessionState, event.agentId, event.toolCallId)
284
276
  const { [toolName]: _, ...restFailures } = counters.consecutiveToolFailures
285
277
 
@@ -296,7 +288,6 @@ export const limitsGuardPlugin = definePlugin('limits-guard')
296
288
  const counters = limits.get(event.agentId)
297
289
  if (!counters) return limits
298
290
 
299
- // Resolve tool name from agent state (must run before core clears executingToolCall)
300
291
  const toolName = resolveToolName(sessionState, event.agentId, event.toolCallId)
301
292
  const currentEntry = counters.consecutiveToolFailures[toolName]
302
293
 
@@ -31,7 +31,7 @@ const statusChangedSchema = z.object({
31
31
  // Constants
32
32
  // ============================================================================
33
33
 
34
- const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
34
+ const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
35
35
 
36
36
  /** Allowed MIME type patterns */
37
37
  const ALLOWED_MIME_TYPES = [
@@ -68,6 +68,8 @@ const uploadStorageError = (): DomainError => createDomainError('upload_storage_
68
68
  export interface UploadsPluginConfig {
69
69
  dataFileStore: FileStore
70
70
  preprocessorRegistry?: PreprocessorRegistry
71
+ /** Largest accepted file in bytes. Defaults to 10 MB. */
72
+ maxFileSize?: number
71
73
  processingTimeoutMs?: number
72
74
  processingAbortGraceMs?: number
73
75
  /** Bound in ms on the close-time wait for in-flight uploads and mutations. Must be positive; anything else uses the 30s default. */
@@ -181,9 +183,9 @@ function toStoredFilename(filename: string): string {
181
183
  return filename.normalize('NFC')
182
184
  }
183
185
 
184
- function validateUploadInput(input: Pick<UploadInput, 'size' | 'mimeType' | 'filename'>): string | undefined {
186
+ function validateUploadInput(input: Pick<UploadInput, 'size' | 'mimeType' | 'filename'>, maxFileSize: number): string | undefined {
185
187
  if (!isBasename(input.filename)) return 'Invalid filename'
186
- if (input.size > MAX_FILE_SIZE) return `File too large: max ${MAX_FILE_SIZE / (1024 * 1024)}MB`
188
+ if (input.size > maxFileSize) return `File too large: max ${maxFileSize / (1024 * 1024)}MB`
187
189
  if (!isAllowedMimeType(input.mimeType)) return `Unsupported file type: ${input.mimeType}`
188
190
  return undefined
189
191
  }
@@ -290,11 +292,12 @@ function runTrackedMutation<T>(args: {
290
292
  async function prepareUpload(args: {
291
293
  pluginContext: UploadsPluginContext
292
294
  dataFileStore: FileStore
295
+ maxFileSize: number
293
296
  logger: Logger
294
297
  input: UploadInput
295
298
  }): Promise<Result<PreparedUpload, DomainError>> {
296
299
  const filename = toStoredFilename(args.input.filename)
297
- const validationError = validateUploadInput({ ...args.input, filename })
300
+ const validationError = validateUploadInput({ ...args.input, filename }, args.maxFileSize)
298
301
  if (validationError) return Err(ValidationErrors.invalid(validationError))
299
302
 
300
303
  const uploadId = generateUploadId()
@@ -1013,6 +1016,7 @@ export const uploadsPlugin = definePlugin('uploads')
1013
1016
  const preparedResult = await prepareUpload({
1014
1017
  pluginContext: ctx.pluginContext,
1015
1018
  dataFileStore: ctx.pluginConfig.dataFileStore,
1019
+ maxFileSize: ctx.pluginConfig.maxFileSize ?? DEFAULT_MAX_FILE_SIZE,
1016
1020
  logger: ctx.logger,
1017
1021
  input,
1018
1022
  })
@@ -1076,6 +1080,7 @@ export const uploadsPlugin = definePlugin('uploads')
1076
1080
  const preparedResult = await prepareUpload({
1077
1081
  pluginContext: ctx.pluginContext,
1078
1082
  dataFileStore: ctx.pluginConfig.dataFileStore,
1083
+ maxFileSize: ctx.pluginConfig.maxFileSize ?? DEFAULT_MAX_FILE_SIZE,
1079
1084
  logger: ctx.logger,
1080
1085
  input,
1081
1086
  })
@@ -58,6 +58,7 @@ export class TestHarness {
58
58
  eventStore?: MemoryEventStore
59
59
  /** Resident session idle timeout. Zero or absent disables eviction. */
60
60
  sessionIdleTimeoutMs?: number
61
+ uploadMaxFileSize?: number
61
62
  /** Extra notification sink, run after the recorder. Throwing here simulates a hostile embedder. */
62
63
  onUserOutput?: (notification: PluginNotification) => void
63
64
  }) {
@@ -116,6 +117,7 @@ export class TestHarness {
116
117
  llmLogger: options.llmLogger,
117
118
  platform,
118
119
  sessionIdleTimeoutMs: options.sessionIdleTimeoutMs,
120
+ uploadMaxFileSize: options.uploadMaxFileSize,
119
121
  systemPlugins: mergeSystemPlugins([...fullPlugins, ...(options.systemPlugins ?? [])]),
120
122
  })
121
123
  }