@tanstack/ai 0.38.0 → 0.39.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/esm/activities/chat/index.js +28 -0
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +7 -1
- package/dist/esm/activities/chat/middleware/compose.js +27 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
- package/dist/esm/activities/chat/middleware/index.d.ts +1 -1
- package/dist/esm/activities/chat/middleware/sandbox-runtime.d.ts +8 -0
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js +9 -0
- package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -0
- package/dist/esm/activities/chat/middleware/types.d.ts +22 -0
- package/dist/esm/adapter-internals.d.ts +2 -0
- package/dist/esm/adapter-internals.js +4 -0
- package/dist/esm/adapter-internals.js.map +1 -1
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/logger/internal-logger.d.ts +2 -0
- package/dist/esm/logger/internal-logger.js +6 -1
- package/dist/esm/logger/internal-logger.js.map +1 -1
- package/dist/esm/logger/resolve.js +6 -3
- package/dist/esm/logger/resolve.js.map +1 -1
- package/dist/esm/logger/types.d.ts +5 -0
- package/dist/esm/types.d.ts +18 -0
- package/package.json +1 -1
- package/skills/ai-core/adapter-configuration/SKILL.md +25 -12
- package/src/activities/chat/index.ts +39 -0
- package/src/activities/chat/middleware/compose.ts +34 -0
- package/src/activities/chat/middleware/index.ts +2 -0
- package/src/activities/chat/middleware/sandbox-runtime.ts +21 -0
- package/src/activities/chat/middleware/types.ts +37 -0
- package/src/adapter-internals.ts +6 -0
- package/src/index.ts +4 -0
- package/src/logger/internal-logger.ts +6 -0
- package/src/logger/resolve.ts +3 -0
- package/src/logger/types.ts +5 -0
- package/src/types.ts +20 -0
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import { maxIterations as maxIterationsStrategy } from './agent-loop-strategies'
|
|
28
28
|
import { convertMessagesToModelMessages, generateMessageId } from './messages'
|
|
29
29
|
import { MiddlewareRunner } from './middleware/compose'
|
|
30
|
+
import { provideSandboxRuntime } from './middleware/sandbox-runtime'
|
|
30
31
|
import { CapabilityRegistry } from './middleware/capabilities'
|
|
31
32
|
import { validateCapabilities } from './middleware/validate'
|
|
32
33
|
import { MCPManager } from './mcp/manager'
|
|
@@ -67,6 +68,7 @@ import type {
|
|
|
67
68
|
ChatMiddleware,
|
|
68
69
|
ChatMiddlewareConfig,
|
|
69
70
|
ChatMiddlewareContext,
|
|
71
|
+
SandboxFileEvent,
|
|
70
72
|
StructuredOutputMiddlewareConfig,
|
|
71
73
|
} from './middleware/types'
|
|
72
74
|
import type { CheckCoverage } from './middleware/builder'
|
|
@@ -542,6 +544,7 @@ class TextEngine<
|
|
|
542
544
|
// Middleware support
|
|
543
545
|
private readonly middlewareRunner: MiddlewareRunner<TContext>
|
|
544
546
|
private readonly middlewareCtx: ChatMiddlewareContext<TContext>
|
|
547
|
+
private readonly sandboxFileQueue: Array<StreamChunk> = []
|
|
545
548
|
private readonly deferredPromises: Array<Promise<unknown>> = []
|
|
546
549
|
private abortReason?: string
|
|
547
550
|
private readonly middlewareAbortController?: AbortController
|
|
@@ -701,6 +704,21 @@ class TextEngine<
|
|
|
701
704
|
capability[0](this.middlewareCtx, { optional: true }),
|
|
702
705
|
provide: (capability, value) => capability[1](this.middlewareCtx, value),
|
|
703
706
|
}
|
|
707
|
+
|
|
708
|
+
// Provide the internal SandboxRuntime capability so harness adapters and
|
|
709
|
+
// sandbox middleware can emit file events. The sink logs, fans the event
|
|
710
|
+
// out through the middleware `onFile*` hooks (fire-and-forget), and queues
|
|
711
|
+
// a `sandbox.file` custom chunk to be drained into the public stream.
|
|
712
|
+
provideSandboxRuntime(this.middlewareCtx, {
|
|
713
|
+
logger: this.logger,
|
|
714
|
+
emit: (event: SandboxFileEvent) => {
|
|
715
|
+
this.logger.sandbox(`file ${event.type} ${event.path}`, { event })
|
|
716
|
+
void this.middlewareRunner.runSandboxFile(this.middlewareCtx, event)
|
|
717
|
+
this.sandboxFileQueue.push(
|
|
718
|
+
this.createCustomEventChunk('sandbox.file', { ...event }),
|
|
719
|
+
)
|
|
720
|
+
},
|
|
721
|
+
})
|
|
704
722
|
}
|
|
705
723
|
|
|
706
724
|
/** Get the accumulated content after the chat loop completes */
|
|
@@ -1021,6 +1039,10 @@ class TextEngine<
|
|
|
1021
1039
|
threadId: this.threadId,
|
|
1022
1040
|
runId: this.runIdOverride,
|
|
1023
1041
|
parentRunId: this.parentRunIdOverride,
|
|
1042
|
+
// Expose provided capabilities (e.g. sandbox) to harness adapters.
|
|
1043
|
+
capabilities: this.middlewareCtx,
|
|
1044
|
+
// Client approval decisions, for harness interactive-approval resolution.
|
|
1045
|
+
approvals: this.initialApprovals,
|
|
1024
1046
|
...(combinedSchema ? { outputSchema: combinedSchema } : {}),
|
|
1025
1047
|
})) {
|
|
1026
1048
|
if (this.isCancelled()) {
|
|
@@ -1105,10 +1127,16 @@ class TextEngine<
|
|
|
1105
1127
|
await this.middlewareRunner.runOnUsage(this.middlewareCtx, chunk.usage)
|
|
1106
1128
|
}
|
|
1107
1129
|
|
|
1130
|
+
// Drain any sandbox.file events emitted while processing this chunk.
|
|
1131
|
+
yield* this.drainSandboxFileQueue()
|
|
1132
|
+
|
|
1108
1133
|
if (this.earlyTermination) {
|
|
1109
1134
|
break
|
|
1110
1135
|
}
|
|
1111
1136
|
}
|
|
1137
|
+
|
|
1138
|
+
// Drain any remaining sandbox.file events emitted after the stream ended.
|
|
1139
|
+
yield* this.drainSandboxFileQueue()
|
|
1112
1140
|
}
|
|
1113
1141
|
|
|
1114
1142
|
private handleStreamChunk(chunk: StreamChunk): void {
|
|
@@ -2502,6 +2530,17 @@ class TextEngine<
|
|
|
2502
2530
|
}
|
|
2503
2531
|
}
|
|
2504
2532
|
|
|
2533
|
+
/**
|
|
2534
|
+
* Drain queued `sandbox.file` chunks (emitted via the SandboxRuntime sink)
|
|
2535
|
+
* through the middleware pipeline and into the public stream.
|
|
2536
|
+
*/
|
|
2537
|
+
private async *drainSandboxFileQueue(): AsyncGenerator<StreamChunk> {
|
|
2538
|
+
while (this.sandboxFileQueue.length > 0) {
|
|
2539
|
+
const chunk = this.sandboxFileQueue.shift()
|
|
2540
|
+
if (chunk) yield* this.pipeThroughMiddleware(chunk)
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2505
2544
|
/**
|
|
2506
2545
|
* Drain an executeToolCalls async generator, yielding any CustomEvent chunks
|
|
2507
2546
|
* through the middleware pipeline and returning the final ExecuteToolCallsResult.
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ErrorInfo,
|
|
12
12
|
FinishInfo,
|
|
13
13
|
IterationInfo,
|
|
14
|
+
SandboxFileEvent,
|
|
14
15
|
StructuredOutputMiddlewareConfig,
|
|
15
16
|
ToolCallHookContext,
|
|
16
17
|
ToolPhaseCompleteInfo,
|
|
@@ -344,6 +345,39 @@ export class MiddlewareRunner<TContext = unknown> {
|
|
|
344
345
|
return chunks
|
|
345
346
|
}
|
|
346
347
|
|
|
348
|
+
/**
|
|
349
|
+
* Dispatch a sandbox file event to every middleware's `sandbox` hooks, in
|
|
350
|
+
* array order: the catch-all `onFile` then the type-specific hook. Errors are
|
|
351
|
+
* logged and swallowed so one bad hook can't break the run.
|
|
352
|
+
*/
|
|
353
|
+
async runSandboxFile(
|
|
354
|
+
ctx: ChatMiddlewareContext<TContext>,
|
|
355
|
+
event: SandboxFileEvent,
|
|
356
|
+
): Promise<void> {
|
|
357
|
+
const typed = (
|
|
358
|
+
{
|
|
359
|
+
create: 'onFileCreate',
|
|
360
|
+
change: 'onFileChange',
|
|
361
|
+
delete: 'onFileDelete',
|
|
362
|
+
} as const
|
|
363
|
+
)[event.type]
|
|
364
|
+
for (const mw of this.middlewares) {
|
|
365
|
+
const hooks = mw.sandbox
|
|
366
|
+
if (!hooks) continue
|
|
367
|
+
for (const fn of [hooks.onFile, hooks[typed]]) {
|
|
368
|
+
if (!fn) continue
|
|
369
|
+
try {
|
|
370
|
+
await fn(ctx, event)
|
|
371
|
+
} catch (error) {
|
|
372
|
+
this.logger.sandbox(
|
|
373
|
+
`hook=${typed} middleware=${mw.name ?? 'unnamed'} threw`,
|
|
374
|
+
{ middleware: mw.name ?? 'unnamed', error },
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
347
381
|
/**
|
|
348
382
|
* Run onBeforeToolCall through middleware in order.
|
|
349
383
|
* Returns the first non-void decision, or undefined to continue normally.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal runtime seam the chat engine PROVIDES so the sandbox middleware can
|
|
3
|
+
* surface file events without a public ctx method. `emit` runs every
|
|
4
|
+
* middleware's `sandbox` hooks AND emits a CUSTOM `sandbox.file` chunk into the
|
|
5
|
+
* stream; `logger` lets the sandbox layer log under the `sandbox` debug
|
|
6
|
+
* category. Consumed (optionally) by `withSandbox` in `@tanstack/ai-sandbox`.
|
|
7
|
+
*/
|
|
8
|
+
import { createCapability } from './capabilities'
|
|
9
|
+
import type { InternalLogger } from '../../../logger/internal-logger'
|
|
10
|
+
import type { SandboxFileEvent } from './types'
|
|
11
|
+
|
|
12
|
+
export interface SandboxRuntime {
|
|
13
|
+
emit: (event: SandboxFileEvent) => void
|
|
14
|
+
logger: InternalLogger
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const SandboxRuntimeCapability =
|
|
18
|
+
createCapability<SandboxRuntime>()('sandbox-runtime')
|
|
19
|
+
|
|
20
|
+
export const [getSandboxRuntime, provideSandboxRuntime] =
|
|
21
|
+
SandboxRuntimeCapability
|
|
@@ -13,6 +13,37 @@ import type {
|
|
|
13
13
|
CapabilityRegistry,
|
|
14
14
|
} from './capabilities'
|
|
15
15
|
|
|
16
|
+
/** A file change observed inside a sandbox during a chat run. */
|
|
17
|
+
export interface SandboxFileEvent {
|
|
18
|
+
type: 'create' | 'change' | 'delete'
|
|
19
|
+
/** Absolute path inside the sandbox (under the workspace root). */
|
|
20
|
+
path: string
|
|
21
|
+
timestamp: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Sandbox file-event hooks a chat middleware can declare. Fire server-side for
|
|
26
|
+
* every file create/change/delete observed in the sandbox during the run.
|
|
27
|
+
*/
|
|
28
|
+
export interface ChatSandboxHooks<TContext = unknown> {
|
|
29
|
+
onFile?: (
|
|
30
|
+
ctx: ChatMiddlewareContext<TContext>,
|
|
31
|
+
e: SandboxFileEvent,
|
|
32
|
+
) => void | Promise<void>
|
|
33
|
+
onFileCreate?: (
|
|
34
|
+
ctx: ChatMiddlewareContext<TContext>,
|
|
35
|
+
e: SandboxFileEvent,
|
|
36
|
+
) => void | Promise<void>
|
|
37
|
+
onFileChange?: (
|
|
38
|
+
ctx: ChatMiddlewareContext<TContext>,
|
|
39
|
+
e: SandboxFileEvent,
|
|
40
|
+
) => void | Promise<void>
|
|
41
|
+
onFileDelete?: (
|
|
42
|
+
ctx: ChatMiddlewareContext<TContext>,
|
|
43
|
+
e: SandboxFileEvent,
|
|
44
|
+
) => void | Promise<void>
|
|
45
|
+
}
|
|
46
|
+
|
|
16
47
|
// ===========================
|
|
17
48
|
// Middleware Context
|
|
18
49
|
// ===========================
|
|
@@ -539,6 +570,12 @@ export interface ChatMiddleware<TContext = unknown> {
|
|
|
539
570
|
ctx: ChatMiddlewareContext<TContext>,
|
|
540
571
|
info: ErrorInfo,
|
|
541
572
|
) => void | Promise<void>
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Sandbox file-event hooks. Fire when a sandbox provided by `withSandbox` is
|
|
576
|
+
* active during the run and a file is created/changed/deleted. Server-side.
|
|
577
|
+
*/
|
|
578
|
+
sandbox?: ChatSandboxHooks<TContext>
|
|
542
579
|
}
|
|
543
580
|
|
|
544
581
|
/** A `ChatMiddleware` with a permissive context — for use as a constraint. */
|
package/src/adapter-internals.ts
CHANGED
|
@@ -10,3 +10,9 @@ export {
|
|
|
10
10
|
toRunErrorPayload,
|
|
11
11
|
toRunErrorRawEvent,
|
|
12
12
|
} from './activities/error-payload'
|
|
13
|
+
export {
|
|
14
|
+
getSandboxRuntime,
|
|
15
|
+
provideSandboxRuntime,
|
|
16
|
+
SandboxRuntimeCapability,
|
|
17
|
+
} from './activities/chat/middleware/sandbox-runtime'
|
|
18
|
+
export type { SandboxRuntime } from './activities/chat/middleware/sandbox-runtime'
|
package/src/index.ts
CHANGED
|
@@ -120,6 +120,8 @@ export type {
|
|
|
120
120
|
FinishInfo,
|
|
121
121
|
AbortInfo,
|
|
122
122
|
ErrorInfo,
|
|
123
|
+
SandboxFileEvent,
|
|
124
|
+
ChatSandboxHooks,
|
|
123
125
|
} from './activities/chat/middleware/index'
|
|
124
126
|
|
|
125
127
|
// Base, activity-agnostic middleware. The observe-only superset that media
|
|
@@ -149,6 +151,8 @@ export type {
|
|
|
149
151
|
CapabilityContext,
|
|
150
152
|
CapabilityGetter,
|
|
151
153
|
CapabilityProvider,
|
|
154
|
+
DefinedChatMiddleware,
|
|
155
|
+
AnyChatMiddleware,
|
|
152
156
|
} from './activities/chat/middleware/index'
|
|
153
157
|
|
|
154
158
|
// All types
|
|
@@ -31,6 +31,7 @@ const CATEGORY_EMOJI: Record<keyof ResolvedCategories, string> = {
|
|
|
31
31
|
agentLoop: '🔁',
|
|
32
32
|
config: '⚙️',
|
|
33
33
|
errors: '❌',
|
|
34
|
+
sandbox: '📦',
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export class InternalLogger {
|
|
@@ -82,6 +83,11 @@ export class InternalLogger {
|
|
|
82
83
|
this.emit('debug', 'tools', message, meta)
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
/** Log sandbox internals (watcher, file events, hook dispatch). Chat-only. */
|
|
87
|
+
sandbox(message: string, meta?: Record<string, unknown>): void {
|
|
88
|
+
this.emit('debug', 'sandbox', message, meta)
|
|
89
|
+
}
|
|
90
|
+
|
|
85
91
|
/** Log an agent-loop iteration marker or phase transition. Chat-only. */
|
|
86
92
|
agentLoop(message: string, meta?: Record<string, unknown>): void {
|
|
87
93
|
this.emit('debug', 'agentLoop', message, meta)
|
package/src/logger/resolve.ts
CHANGED
|
@@ -12,6 +12,7 @@ const ALL_OFF: ResolvedCategories = {
|
|
|
12
12
|
config: false,
|
|
13
13
|
errors: false,
|
|
14
14
|
request: false,
|
|
15
|
+
sandbox: false,
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
const ALL_ON: ResolvedCategories = {
|
|
@@ -23,6 +24,7 @@ const ALL_ON: ResolvedCategories = {
|
|
|
23
24
|
config: true,
|
|
24
25
|
errors: true,
|
|
25
26
|
request: true,
|
|
27
|
+
sandbox: true,
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
const errorsOnlyCategories = (): ResolvedCategories => ({
|
|
@@ -41,6 +43,7 @@ const resolveCategoriesFromPartial = (
|
|
|
41
43
|
config: partial.config ?? true,
|
|
42
44
|
errors: partial.errors ?? true,
|
|
43
45
|
request: partial.request ?? true,
|
|
46
|
+
sandbox: partial.sandbox ?? true,
|
|
44
47
|
})
|
|
45
48
|
|
|
46
49
|
/**
|
package/src/logger/types.ts
CHANGED
|
@@ -60,6 +60,11 @@ export interface DebugCategories {
|
|
|
60
60
|
* Outgoing call metadata (provider, model, message/tool counts) emitted before each adapter SDK call.
|
|
61
61
|
*/
|
|
62
62
|
request?: boolean
|
|
63
|
+
/**
|
|
64
|
+
* Sandbox internals: watcher start/stop + mechanism, file events, sandbox
|
|
65
|
+
* hook dispatch, ensure/bootstrap and lifecycle transitions. Chat-only.
|
|
66
|
+
*/
|
|
67
|
+
sandbox?: boolean
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
/**
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
} from '@standard-schema/spec'
|
|
5
5
|
import type { InternalLogger } from './logger/internal-logger'
|
|
6
6
|
import type { SystemPrompt } from './system-prompts'
|
|
7
|
+
import type { CapabilityContext } from './activities/chat/middleware/capabilities'
|
|
7
8
|
// The canonical usage types live in the leaf `@tanstack/ai-event-client`
|
|
8
9
|
// package (which `@tanstack/ai` already depends on) so there is a single source
|
|
9
10
|
// of truth without a dependency cycle. They are re-exported below.
|
|
@@ -946,6 +947,25 @@ export interface TextOptions<
|
|
|
946
947
|
* Surfaced for observability/middleware; not consumed by the LLM call.
|
|
947
948
|
*/
|
|
948
949
|
parentRunId?: string
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Middleware capability context for this run. The engine populates it with
|
|
953
|
+
* the live middleware context so harness adapters that declare
|
|
954
|
+
* `requires: [SomeCapability]` can read provided capabilities from inside
|
|
955
|
+
* `chatStream` — e.g. `getSandbox(options.capabilities)`. Capabilities are
|
|
956
|
+
* provisioned by middleware `setup` before the adapter runs. Undefined for
|
|
957
|
+
* direct adapter usage outside the chat engine.
|
|
958
|
+
*/
|
|
959
|
+
capabilities?: CapabilityContext
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Client approval decisions for this run, keyed by approval id. The engine
|
|
963
|
+
* populates this from approvals carried on the incoming messages. Harness
|
|
964
|
+
* adapters consult it to resolve `ask`-policy permission requests (the agent
|
|
965
|
+
* pauses on a risky action; the client re-runs with a decision recorded
|
|
966
|
+
* here). Undefined for direct adapter usage outside the chat engine.
|
|
967
|
+
*/
|
|
968
|
+
approvals?: ReadonlyMap<string, boolean>
|
|
949
969
|
}
|
|
950
970
|
|
|
951
971
|
// ============================================================================
|