@tangle-network/agent-app 0.11.1 → 0.12.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/TimelineEditor-OXPJZDP2.js +12 -0
- package/dist/TimelineEditor-OXPJZDP2.js.map +1 -0
- package/dist/apply-Cp8c3K9D.d.ts +249 -0
- package/dist/chunk-3WAJWYKD.js +1730 -0
- package/dist/chunk-3WAJWYKD.js.map +1 -0
- package/dist/chunk-CF5DZELC.js +111 -0
- package/dist/chunk-CF5DZELC.js.map +1 -0
- package/dist/{chunk-4YTWB5MG.js → chunk-ETX4O4BB.js} +98 -1
- package/dist/chunk-ETX4O4BB.js.map +1 -0
- package/dist/chunk-IHR6K3GF.js +2367 -0
- package/dist/chunk-IHR6K3GF.js.map +1 -0
- package/dist/{chunk-OLCVUGGI.js → chunk-IJZJWKUK.js} +1 -61
- package/dist/chunk-IJZJWKUK.js.map +1 -0
- package/dist/chunk-ZYBWGSAZ.js +130 -0
- package/dist/chunk-ZYBWGSAZ.js.map +1 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +128 -6
- package/dist/mcp-CIupfjxV.d.ts +112 -0
- package/dist/runtime/index.d.ts +108 -1
- package/dist/runtime/index.js +7 -1
- package/dist/sequences/drizzle.d.ts +1244 -0
- package/dist/sequences/drizzle.js +368 -0
- package/dist/sequences/drizzle.js.map +1 -0
- package/dist/sequences/index.d.ts +327 -0
- package/dist/sequences/index.js +114 -0
- package/dist/sequences/index.js.map +1 -0
- package/dist/sequences-react/index.d.ts +752 -0
- package/dist/sequences-react/index.js +241 -0
- package/dist/sequences-react/index.js.map +1 -0
- package/dist/store-gckrNq-g.d.ts +242 -0
- package/dist/tools/index.d.ts +24 -108
- package/dist/tools/index.js +12 -6
- package/package.json +37 -2
- package/dist/chunk-4YTWB5MG.js.map +0 -1
- package/dist/chunk-OLCVUGGI.js.map +0 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COMMAND_HISTORY_LIMIT,
|
|
3
|
+
DEFAULT_MAX_MEDIA_ELEMENTS,
|
|
4
|
+
PreviewCanvas,
|
|
5
|
+
SEEK_TIMEOUT_MS,
|
|
6
|
+
SEEK_TOLERANCE_SECONDS,
|
|
7
|
+
SEQUENCE_MEDIA_DRAG_TYPE,
|
|
8
|
+
SnapIndicatorLine,
|
|
9
|
+
TimelineClipChip,
|
|
10
|
+
TimelineEditor,
|
|
11
|
+
TimelinePlayhead,
|
|
12
|
+
TimelineRuler,
|
|
13
|
+
TimelineTrackRow,
|
|
14
|
+
ZoomControl,
|
|
15
|
+
addCaptionCommand,
|
|
16
|
+
applySnap,
|
|
17
|
+
captionFontPx,
|
|
18
|
+
chooseMoveSnap,
|
|
19
|
+
classifyMediaUrl,
|
|
20
|
+
clipChipGeometry,
|
|
21
|
+
collectSnapPoints,
|
|
22
|
+
compositeCommand,
|
|
23
|
+
computeWaveform,
|
|
24
|
+
containFitRect,
|
|
25
|
+
createCommandStack,
|
|
26
|
+
createImageFrameProvider,
|
|
27
|
+
createMediaElementPool,
|
|
28
|
+
createPlaybackClock,
|
|
29
|
+
createVideoElementFrameProvider,
|
|
30
|
+
createZoomMath,
|
|
31
|
+
deleteClipCommand,
|
|
32
|
+
drawWaveform,
|
|
33
|
+
frameToPixel,
|
|
34
|
+
framesFromPixelDelta,
|
|
35
|
+
letterboxRect,
|
|
36
|
+
loadWaveform,
|
|
37
|
+
moveClipCommand,
|
|
38
|
+
moveDragStartFrame,
|
|
39
|
+
needsSeek,
|
|
40
|
+
pixelToFrame,
|
|
41
|
+
placeClipCommand,
|
|
42
|
+
selectTickStepSeconds,
|
|
43
|
+
setClipTextCommand,
|
|
44
|
+
snapPixel,
|
|
45
|
+
splitClipCommand,
|
|
46
|
+
toggleClipDisabledCommand,
|
|
47
|
+
trimClipCommand,
|
|
48
|
+
trimEndDrag,
|
|
49
|
+
trimStartDrag
|
|
50
|
+
} from "../chunk-IHR6K3GF.js";
|
|
51
|
+
import "../chunk-ZYBWGSAZ.js";
|
|
52
|
+
|
|
53
|
+
// src/sequences-react/media/transcription.ts
|
|
54
|
+
var DEFAULT_WHISPER_MODEL = "onnx-community/whisper-large-v3-turbo";
|
|
55
|
+
var WHISPER_SAMPLE_RATE = 16e3;
|
|
56
|
+
var TRANSCRIPTION_PEER = "@huggingface/transformers";
|
|
57
|
+
var PEER_MISSING_MESSAGE = "transcription requires optional peer @huggingface/transformers";
|
|
58
|
+
function mixdownToMono(buffer) {
|
|
59
|
+
if (buffer.numberOfChannels < 1) throw new Error("audio buffer has no channels \u2014 cannot mix down");
|
|
60
|
+
if (buffer.numberOfChannels === 1) return buffer.getChannelData(0);
|
|
61
|
+
const mono = new Float32Array(buffer.length);
|
|
62
|
+
for (let channel = 0; channel < buffer.numberOfChannels; channel++) {
|
|
63
|
+
const data = buffer.getChannelData(channel);
|
|
64
|
+
if (data.length !== buffer.length) {
|
|
65
|
+
throw new Error(`channel ${channel} has ${data.length} samples, expected ${buffer.length}`);
|
|
66
|
+
}
|
|
67
|
+
for (let i = 0; i < data.length; i++) {
|
|
68
|
+
mono[i] = mono[i] + data[i];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const scale = 1 / buffer.numberOfChannels;
|
|
72
|
+
for (let i = 0; i < mono.length; i++) {
|
|
73
|
+
mono[i] = mono[i] * scale;
|
|
74
|
+
}
|
|
75
|
+
return mono;
|
|
76
|
+
}
|
|
77
|
+
function mapWhisperOutput(output, durationSeconds, mediaUrl) {
|
|
78
|
+
const first = Array.isArray(output) ? output[0] : output;
|
|
79
|
+
if (first === void 0) throw new Error(`whisper produced no output for ${mediaUrl}`);
|
|
80
|
+
const chunks = first.chunks;
|
|
81
|
+
if (chunks === void 0 || chunks.length === 0) {
|
|
82
|
+
const text = first.text.trim();
|
|
83
|
+
return text.length === 0 ? [] : [{ text, startSeconds: 0, endSeconds: durationSeconds }];
|
|
84
|
+
}
|
|
85
|
+
const segments = [];
|
|
86
|
+
for (const chunk of chunks) {
|
|
87
|
+
const text = chunk.text.trim();
|
|
88
|
+
if (text.length === 0) continue;
|
|
89
|
+
const [start, end] = chunk.timestamp;
|
|
90
|
+
if (!Number.isFinite(start)) {
|
|
91
|
+
throw new Error(`whisper returned a non-numeric start timestamp for "${text}" in ${mediaUrl}`);
|
|
92
|
+
}
|
|
93
|
+
segments.push({ text, startSeconds: start, endSeconds: end === null ? durationSeconds : end });
|
|
94
|
+
}
|
|
95
|
+
return segments;
|
|
96
|
+
}
|
|
97
|
+
async function loadTransformers() {
|
|
98
|
+
try {
|
|
99
|
+
return await import(
|
|
100
|
+
/* @vite-ignore */
|
|
101
|
+
/* webpackIgnore: true */
|
|
102
|
+
TRANSCRIPTION_PEER
|
|
103
|
+
);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new Error(PEER_MISSING_MESSAGE, { cause: error });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function fetchMonoAudio(mediaUrl) {
|
|
109
|
+
const response = await fetch(mediaUrl);
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error(`failed to fetch media for transcription: ${response.status} ${response.statusText} from ${mediaUrl}`);
|
|
112
|
+
}
|
|
113
|
+
const bytes = await response.arrayBuffer();
|
|
114
|
+
if (typeof OfflineAudioContext === "undefined") {
|
|
115
|
+
throw new Error("transcription requires Web Audio (OfflineAudioContext) \u2014 call transcribe() from a browser");
|
|
116
|
+
}
|
|
117
|
+
const context = new OfflineAudioContext(1, 1, WHISPER_SAMPLE_RATE);
|
|
118
|
+
const decoded = await context.decodeAudioData(bytes);
|
|
119
|
+
return { samples: mixdownToMono(decoded), durationSeconds: decoded.duration };
|
|
120
|
+
}
|
|
121
|
+
function createWhisperTranscriptionProvider(opts) {
|
|
122
|
+
const model = opts?.model ?? DEFAULT_WHISPER_MODEL;
|
|
123
|
+
let availability = null;
|
|
124
|
+
let pipelinePromise = null;
|
|
125
|
+
const probeAvailability = () => {
|
|
126
|
+
if (availability !== null) return availability;
|
|
127
|
+
const resolve = import.meta.resolve;
|
|
128
|
+
if (typeof resolve !== "function") {
|
|
129
|
+
availability = false;
|
|
130
|
+
return availability;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
resolve.call(import.meta, TRANSCRIPTION_PEER);
|
|
134
|
+
availability = true;
|
|
135
|
+
} catch {
|
|
136
|
+
availability = false;
|
|
137
|
+
}
|
|
138
|
+
return availability;
|
|
139
|
+
};
|
|
140
|
+
const getPipeline = (transformers, onProgress) => {
|
|
141
|
+
if (pipelinePromise === null) {
|
|
142
|
+
const device = typeof navigator !== "undefined" && "gpu" in navigator ? "webgpu" : "wasm";
|
|
143
|
+
pipelinePromise = transformers.pipeline("automatic-speech-recognition", model, {
|
|
144
|
+
dtype: "q4",
|
|
145
|
+
device,
|
|
146
|
+
// Model download progress only — it dwarfs inference time on first
|
|
147
|
+
// use, and only the first transcribe ever sees a download.
|
|
148
|
+
progress_callback: (event) => {
|
|
149
|
+
if (event.status === "progress" && typeof event.progress === "number" && onProgress !== void 0) {
|
|
150
|
+
onProgress(Math.min(1, Math.max(0, event.progress / 100)));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
pipelinePromise.catch(() => {
|
|
155
|
+
pipelinePromise = null;
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return pipelinePromise;
|
|
159
|
+
};
|
|
160
|
+
return {
|
|
161
|
+
get available() {
|
|
162
|
+
return probeAvailability();
|
|
163
|
+
},
|
|
164
|
+
async transcribe(mediaUrl, transcribeOpts) {
|
|
165
|
+
const transformers = await loadTransformers();
|
|
166
|
+
availability = true;
|
|
167
|
+
const audio = await fetchMonoAudio(mediaUrl);
|
|
168
|
+
const transcriber = await getPipeline(transformers, transcribeOpts?.onProgress);
|
|
169
|
+
const options = {
|
|
170
|
+
return_timestamps: true,
|
|
171
|
+
chunk_length_s: 30,
|
|
172
|
+
stride_length_s: 5
|
|
173
|
+
};
|
|
174
|
+
if (transcribeOpts?.language !== void 0) options.language = transcribeOpts.language;
|
|
175
|
+
const output = await transcriber(audio.samples, options);
|
|
176
|
+
const segments = mapWhisperOutput(output, audio.durationSeconds, mediaUrl);
|
|
177
|
+
transcribeOpts?.onProgress?.(1);
|
|
178
|
+
return segments;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/sequences-react/lazy.tsx
|
|
184
|
+
import React from "react";
|
|
185
|
+
var SequenceTimelineEditorLazy = React.lazy(() => import("../TimelineEditor-OXPJZDP2.js"));
|
|
186
|
+
export {
|
|
187
|
+
COMMAND_HISTORY_LIMIT,
|
|
188
|
+
DEFAULT_MAX_MEDIA_ELEMENTS,
|
|
189
|
+
DEFAULT_WHISPER_MODEL,
|
|
190
|
+
PreviewCanvas,
|
|
191
|
+
SEEK_TIMEOUT_MS,
|
|
192
|
+
SEEK_TOLERANCE_SECONDS,
|
|
193
|
+
SEQUENCE_MEDIA_DRAG_TYPE,
|
|
194
|
+
SequenceTimelineEditorLazy,
|
|
195
|
+
SnapIndicatorLine,
|
|
196
|
+
TimelineClipChip,
|
|
197
|
+
TimelineEditor,
|
|
198
|
+
TimelinePlayhead,
|
|
199
|
+
TimelineRuler,
|
|
200
|
+
TimelineTrackRow,
|
|
201
|
+
ZoomControl,
|
|
202
|
+
addCaptionCommand,
|
|
203
|
+
applySnap,
|
|
204
|
+
captionFontPx,
|
|
205
|
+
chooseMoveSnap,
|
|
206
|
+
classifyMediaUrl,
|
|
207
|
+
clipChipGeometry,
|
|
208
|
+
collectSnapPoints,
|
|
209
|
+
compositeCommand,
|
|
210
|
+
computeWaveform,
|
|
211
|
+
containFitRect,
|
|
212
|
+
createCommandStack,
|
|
213
|
+
createImageFrameProvider,
|
|
214
|
+
createMediaElementPool,
|
|
215
|
+
createPlaybackClock,
|
|
216
|
+
createVideoElementFrameProvider,
|
|
217
|
+
createWhisperTranscriptionProvider,
|
|
218
|
+
createZoomMath,
|
|
219
|
+
deleteClipCommand,
|
|
220
|
+
drawWaveform,
|
|
221
|
+
frameToPixel,
|
|
222
|
+
framesFromPixelDelta,
|
|
223
|
+
letterboxRect,
|
|
224
|
+
loadWaveform,
|
|
225
|
+
mapWhisperOutput,
|
|
226
|
+
mixdownToMono,
|
|
227
|
+
moveClipCommand,
|
|
228
|
+
moveDragStartFrame,
|
|
229
|
+
needsSeek,
|
|
230
|
+
pixelToFrame,
|
|
231
|
+
placeClipCommand,
|
|
232
|
+
selectTickStepSeconds,
|
|
233
|
+
setClipTextCommand,
|
|
234
|
+
snapPixel,
|
|
235
|
+
splitClipCommand,
|
|
236
|
+
toggleClipDisabledCommand,
|
|
237
|
+
trimClipCommand,
|
|
238
|
+
trimEndDrag,
|
|
239
|
+
trimStartDrag
|
|
240
|
+
};
|
|
241
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/sequences-react/media/transcription.ts","../../src/sequences-react/lazy.tsx"],"sourcesContent":["/**\n * Whisper transcription behind the `TranscriptionProvider` seam, powered by\n * the OPTIONAL peer `@huggingface/transformers`. The peer is loaded with a\n * dynamic variable-specifier import inside `transcribe()` so bundlers leave\n * it external and apps that never transcribe never pay for it. `available`\n * is the sync UI affordance signal; `transcribe()` re-verifies with the real\n * import and is the source of truth.\n */\n\nimport type { TranscriptionProvider, TranscriptionSegment } from '../contracts'\nimport type { AudioBufferLike } from './waveform'\n\nexport const DEFAULT_WHISPER_MODEL = 'onnx-community/whisper-large-v3-turbo'\n\n/** Whisper models are trained on 16 kHz mono — decoding through a 16 kHz\n * OfflineAudioContext resamples any source rate in one step. */\nconst WHISPER_SAMPLE_RATE = 16_000\n\nconst TRANSCRIPTION_PEER = '@huggingface/transformers'\nconst PEER_MISSING_MESSAGE = 'transcription requires optional peer @huggingface/transformers'\n\ninterface WhisperChunk {\n text: string\n timestamp: [number, number | null]\n}\n\nexport interface WhisperOutput {\n text: string\n chunks?: WhisperChunk[]\n}\n\ntype WhisperPipeline = (\n audio: Float32Array,\n options: Record<string, unknown>,\n) => Promise<WhisperOutput | WhisperOutput[]>\n\ninterface TransformersProgressEvent {\n status: string\n progress?: number\n}\n\ninterface TransformersModule {\n pipeline(\n task: 'automatic-speech-recognition',\n model: string,\n options?: Record<string, unknown>,\n ): Promise<WhisperPipeline>\n}\n\n/** Mean-mixdown to mono. Single-channel buffers return the live channel data\n * without copying — callers must treat the result as read-only. */\nexport function mixdownToMono(buffer: AudioBufferLike): Float32Array {\n if (buffer.numberOfChannels < 1) throw new Error('audio buffer has no channels — cannot mix down')\n if (buffer.numberOfChannels === 1) return buffer.getChannelData(0)\n const mono = new Float32Array(buffer.length)\n for (let channel = 0; channel < buffer.numberOfChannels; channel++) {\n const data = buffer.getChannelData(channel)\n if (data.length !== buffer.length) {\n throw new Error(`channel ${channel} has ${data.length} samples, expected ${buffer.length}`)\n }\n for (let i = 0; i < data.length; i++) {\n // in range by the length assertion above; casts silence noUncheckedIndexedAccess\n mono[i] = (mono[i] as number) + (data[i] as number)\n }\n }\n const scale = 1 / buffer.numberOfChannels\n for (let i = 0; i < mono.length; i++) {\n mono[i] = (mono[i] as number) * scale\n }\n return mono\n}\n\n/** Map whisper chunk output to contract segments. A `null` end timestamp is\n * whisper's \"ran past the end of the audio\" sentinel on the final chunk and\n * resolves to the audio duration. */\nexport function mapWhisperOutput(\n output: WhisperOutput | WhisperOutput[],\n durationSeconds: number,\n mediaUrl: string,\n): TranscriptionSegment[] {\n const first = Array.isArray(output) ? output[0] : output\n if (first === undefined) throw new Error(`whisper produced no output for ${mediaUrl}`)\n const chunks = first.chunks\n if (chunks === undefined || chunks.length === 0) {\n const text = first.text.trim()\n return text.length === 0 ? [] : [{ text, startSeconds: 0, endSeconds: durationSeconds }]\n }\n const segments: TranscriptionSegment[] = []\n for (const chunk of chunks) {\n const text = chunk.text.trim()\n if (text.length === 0) continue\n const [start, end] = chunk.timestamp\n if (!Number.isFinite(start)) {\n throw new Error(`whisper returned a non-numeric start timestamp for \"${text}\" in ${mediaUrl}`)\n }\n segments.push({ text, startSeconds: start, endSeconds: end === null ? durationSeconds : end })\n }\n return segments\n}\n\nasync function loadTransformers(): Promise<TransformersModule> {\n try {\n // Variable specifier + ignore pragmas keep bundlers from statically\n // resolving an optional peer that may not be installed.\n return (await import(/* @vite-ignore */ /* webpackIgnore: true */ TRANSCRIPTION_PEER)) as TransformersModule\n } catch (error) {\n throw new Error(PEER_MISSING_MESSAGE, { cause: error })\n }\n}\n\nasync function fetchMonoAudio(mediaUrl: string): Promise<{ samples: Float32Array; durationSeconds: number }> {\n const response = await fetch(mediaUrl)\n if (!response.ok) {\n throw new Error(`failed to fetch media for transcription: ${response.status} ${response.statusText} from ${mediaUrl}`)\n }\n const bytes = await response.arrayBuffer()\n if (typeof OfflineAudioContext === 'undefined') {\n throw new Error('transcription requires Web Audio (OfflineAudioContext) — call transcribe() from a browser')\n }\n // decodeAudioData resamples to the context rate per the Web Audio spec, so\n // a 16 kHz context yields whisper-ready samples from any source rate.\n const context = new OfflineAudioContext(1, 1, WHISPER_SAMPLE_RATE)\n const decoded = await context.decodeAudioData(bytes)\n return { samples: mixdownToMono(decoded), durationSeconds: decoded.duration }\n}\n\nexport function createWhisperTranscriptionProvider(opts?: { model?: string }): TranscriptionProvider {\n const model = opts?.model ?? DEFAULT_WHISPER_MODEL\n let availability: boolean | null = null\n let pipelinePromise: Promise<WhisperPipeline> | null = null\n\n const probeAvailability = (): boolean => {\n if (availability !== null) return availability\n const resolve = (import.meta as ImportMeta & { resolve?: (specifier: string) => string }).resolve\n if (typeof resolve !== 'function') {\n // No sync resolver in this runtime — report unavailable rather than\n // guess; transcribe() still attempts the real import either way.\n availability = false\n return availability\n }\n try {\n resolve.call(import.meta, TRANSCRIPTION_PEER)\n availability = true\n } catch {\n availability = false\n }\n return availability\n }\n\n const getPipeline = (\n transformers: TransformersModule,\n onProgress?: (fraction: number) => void,\n ): Promise<WhisperPipeline> => {\n if (pipelinePromise === null) {\n const device = typeof navigator !== 'undefined' && 'gpu' in navigator ? 'webgpu' : 'wasm'\n pipelinePromise = transformers.pipeline('automatic-speech-recognition', model, {\n dtype: 'q4',\n device,\n // Model download progress only — it dwarfs inference time on first\n // use, and only the first transcribe ever sees a download.\n progress_callback: (event: TransformersProgressEvent) => {\n if (event.status === 'progress' && typeof event.progress === 'number' && onProgress !== undefined) {\n onProgress(Math.min(1, Math.max(0, event.progress / 100)))\n }\n },\n })\n // A failed load must not poison the cache — the next transcribe retries.\n pipelinePromise.catch(() => {\n pipelinePromise = null\n })\n }\n return pipelinePromise\n }\n\n return {\n get available() {\n return probeAvailability()\n },\n async transcribe(mediaUrl, transcribeOpts) {\n const transformers = await loadTransformers()\n // A successful import is ground truth and overrides a false probe.\n availability = true\n const audio = await fetchMonoAudio(mediaUrl)\n const transcriber = await getPipeline(transformers, transcribeOpts?.onProgress)\n const options: Record<string, unknown> = {\n return_timestamps: true,\n chunk_length_s: 30,\n stride_length_s: 5,\n }\n if (transcribeOpts?.language !== undefined) options.language = transcribeOpts.language\n const output = await transcriber(audio.samples, options)\n const segments = mapWhisperOutput(output, audio.durationSeconds, mediaUrl)\n transcribeOpts?.onProgress?.(1)\n return segments\n },\n }\n}\n","/**\n * Code-split entry for the timeline editor. The editor pulls in canvas\n * painting, waveform decode, and gesture machinery products only need on the\n * sequence route — `React.lazy` keeps it out of their main bundle. Mount\n * inside a `<Suspense>` boundary.\n */\n\nimport React from 'react'\n\nexport const SequenceTimelineEditorLazy = React.lazy(() => import('./components/TimelineEditor'))\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Frame-accurate sequence timeline model — the product-agnostic spine of the
|
|
3
|
+
* sequences surface. A sequence is a fixed-fps, fixed-duration timeline of
|
|
4
|
+
* tracks; clips sit on tracks at integer frame positions with non-destructive
|
|
5
|
+
* source in/out points. Products bind this model to their own storage through
|
|
6
|
+
* `SequenceStore` (./store) and surface it to agents through the MCP toolset
|
|
7
|
+
* (./mcp).
|
|
8
|
+
*
|
|
9
|
+
* All positions and durations are integer FRAMES at the sequence's fps.
|
|
10
|
+
* Seconds appear only at the API edge (agent tools speak seconds; the
|
|
11
|
+
* dispatcher converts exactly once). Nothing here touches a database, the DOM,
|
|
12
|
+
* or React.
|
|
13
|
+
*/
|
|
14
|
+
declare const MIN_SEQUENCE_CLIP_FRAMES = 1;
|
|
15
|
+
/** Track kinds. `reference` holds non-rendered guide media; `agent` holds the
|
|
16
|
+
* agent-decision lane rendered as markers, never as media. */
|
|
17
|
+
type SequenceTrackKind = 'video' | 'audio' | 'caption' | 'reference' | 'agent';
|
|
18
|
+
type SequenceStatus = 'draft' | 'active' | 'exporting' | 'archived';
|
|
19
|
+
type SequenceExportFormat = 'mp4' | 'otio' | 'xml' | 'edl' | 'vtt' | 'srt' | 'contact_sheet';
|
|
20
|
+
type SequenceExportStatus = 'queued' | 'processing' | 'completed' | 'failed' | 'cancelled';
|
|
21
|
+
type SequenceMediaKind = 'video' | 'image' | 'audio';
|
|
22
|
+
interface SequenceMeta {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
fps: number;
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
aspectRatio: string;
|
|
29
|
+
durationFrames: number;
|
|
30
|
+
status: SequenceStatus;
|
|
31
|
+
metadata: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
interface SequenceTrack {
|
|
34
|
+
id: string;
|
|
35
|
+
kind: SequenceTrackKind;
|
|
36
|
+
name: string;
|
|
37
|
+
sortOrder: number;
|
|
38
|
+
locked: boolean;
|
|
39
|
+
muted: boolean;
|
|
40
|
+
metadata: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/** Resolved playable media behind a clip. The store resolves product-specific
|
|
43
|
+
* references (generation rows, asset rows) into this shape; the core model
|
|
44
|
+
* never sees the product's tables. */
|
|
45
|
+
interface SequenceClipMedia {
|
|
46
|
+
url: string;
|
|
47
|
+
kind: SequenceMediaKind;
|
|
48
|
+
/** Natural duration of the source media when known. */
|
|
49
|
+
durationSeconds?: number;
|
|
50
|
+
/** Provider job state for media still rendering upstream. */
|
|
51
|
+
providerStatus?: 'queued' | 'processing' | 'completed' | 'failed';
|
|
52
|
+
}
|
|
53
|
+
interface SequenceClip {
|
|
54
|
+
id: string;
|
|
55
|
+
trackId: string;
|
|
56
|
+
label: string;
|
|
57
|
+
startFrame: number;
|
|
58
|
+
durationFrames: number;
|
|
59
|
+
/** Source-relative in point (frames into the source media). */
|
|
60
|
+
sourceInFrame: number;
|
|
61
|
+
/** Source-relative out point; null = natural end of the source. */
|
|
62
|
+
sourceOutFrame: number | null;
|
|
63
|
+
disabled: boolean;
|
|
64
|
+
/** Caption/text body for clips on caption tracks. */
|
|
65
|
+
text?: string;
|
|
66
|
+
/** BCP-47 language tag for caption clips (e.g. 'en', 'es', 'ja'). */
|
|
67
|
+
language?: string;
|
|
68
|
+
/** Opaque product reference to a generation row, when the clip came from one. */
|
|
69
|
+
generationId?: string;
|
|
70
|
+
/** Opaque product reference to an asset row, when the clip came from one. */
|
|
71
|
+
assetId?: string;
|
|
72
|
+
media?: SequenceClipMedia;
|
|
73
|
+
metadata: Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
/** One entry in the sequence's decision log — human edits, agent proposals,
|
|
76
|
+
* agent edits, exports, and notes all land here so the edit history is a
|
|
77
|
+
* single auditable lane. */
|
|
78
|
+
interface SequenceDecision {
|
|
79
|
+
id: string;
|
|
80
|
+
clipId: string | null;
|
|
81
|
+
kind: 'human_edit' | 'agent_proposal' | 'agent_edit' | 'export' | 'note';
|
|
82
|
+
instruction: string;
|
|
83
|
+
reasoningSummary: string | null;
|
|
84
|
+
accepted: boolean | null;
|
|
85
|
+
metadata: Record<string, unknown>;
|
|
86
|
+
createdAt: Date;
|
|
87
|
+
}
|
|
88
|
+
interface SequenceExportRecord {
|
|
89
|
+
id: string;
|
|
90
|
+
format: SequenceExportFormat;
|
|
91
|
+
status: SequenceExportStatus;
|
|
92
|
+
resultUrl: string | null;
|
|
93
|
+
metadata: Record<string, unknown>;
|
|
94
|
+
createdAt: Date;
|
|
95
|
+
}
|
|
96
|
+
/** The full timeline aggregate — what `get_timeline_state` returns and what
|
|
97
|
+
* every operation validates against. */
|
|
98
|
+
interface SequenceTimeline {
|
|
99
|
+
sequence: SequenceMeta;
|
|
100
|
+
tracks: SequenceTrack[];
|
|
101
|
+
clips: SequenceClip[];
|
|
102
|
+
}
|
|
103
|
+
/** What is on screen/audible at a single frame — the answer shape for
|
|
104
|
+
* "what is happening at 0:34". */
|
|
105
|
+
interface SequenceFrameSnapshot {
|
|
106
|
+
frame: number;
|
|
107
|
+
seconds: number;
|
|
108
|
+
/** Active (enabled, in-range) clips at this frame, with their track. */
|
|
109
|
+
active: Array<{
|
|
110
|
+
track: SequenceTrack;
|
|
111
|
+
clip: SequenceClip;
|
|
112
|
+
}>;
|
|
113
|
+
/** Caption text visible at this frame, in track sort order. */
|
|
114
|
+
captions: Array<{
|
|
115
|
+
text: string;
|
|
116
|
+
language?: string;
|
|
117
|
+
clipId: string;
|
|
118
|
+
}>;
|
|
119
|
+
}
|
|
120
|
+
declare function secondsToFrames(seconds: number, fps: number): number;
|
|
121
|
+
declare function framesToSeconds(frames: number, fps: number): number;
|
|
122
|
+
declare function formatSeconds(seconds: number): string;
|
|
123
|
+
/** `m:ss.ff` timecode for UI and agent-readable frame references. */
|
|
124
|
+
declare function formatTimecode(frames: number, fps: number): string;
|
|
125
|
+
interface TimelineClipBounds {
|
|
126
|
+
startFrame: number;
|
|
127
|
+
durationFrames: number;
|
|
128
|
+
}
|
|
129
|
+
interface TimelineInterval {
|
|
130
|
+
startFrame: number;
|
|
131
|
+
endFrame: number;
|
|
132
|
+
}
|
|
133
|
+
declare function clampClipStart(input: {
|
|
134
|
+
startFrame: number;
|
|
135
|
+
durationFrames: number;
|
|
136
|
+
sequenceDurationFrames: number;
|
|
137
|
+
}): number;
|
|
138
|
+
declare function clampClipDuration(input: {
|
|
139
|
+
startFrame: number;
|
|
140
|
+
durationFrames: number;
|
|
141
|
+
sequenceDurationFrames: number;
|
|
142
|
+
}): number;
|
|
143
|
+
declare function assertClipFitsSequence(input: {
|
|
144
|
+
startFrame: number;
|
|
145
|
+
durationFrames: number;
|
|
146
|
+
sequenceDurationFrames: number;
|
|
147
|
+
label: string;
|
|
148
|
+
}): void;
|
|
149
|
+
/** Place a caption near the playhead inside FREE space only — the caption
|
|
150
|
+
* track never double-books. Prefers fps*3 frames, floors at fps. The gap
|
|
151
|
+
* holding (or first after) the playhead wins; with everything ahead occupied
|
|
152
|
+
* the latest earlier gap is used instead. Throws when no gap can hold the
|
|
153
|
+
* minimum — the caller must supply explicit bounds or clear space. */
|
|
154
|
+
declare function chooseCaptionPlacement(input: {
|
|
155
|
+
playheadFrame: number;
|
|
156
|
+
fps: number;
|
|
157
|
+
sequenceDurationFrames: number;
|
|
158
|
+
occupiedIntervals: TimelineInterval[];
|
|
159
|
+
}): TimelineClipBounds;
|
|
160
|
+
/** Resolve everything active at one frame — the core of `get_frame_at_time`. */
|
|
161
|
+
declare function snapshotFrame(timeline: SequenceTimeline, frame: number): SequenceFrameSnapshot;
|
|
162
|
+
/** Occupied intervals on one track, for placement collision checks. */
|
|
163
|
+
declare function trackIntervals(timeline: SequenceTimeline, trackId: string): TimelineInterval[];
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Storage contract binding the sequence model to a product's database. The
|
|
167
|
+
* product constructs one store per (workspace, sequence, actor) request scope —
|
|
168
|
+
* RBAC and workspace isolation happen BEFORE construction (the product's
|
|
169
|
+
* `requireWorkspaceAccess` equivalent); the store never re-checks identity.
|
|
170
|
+
*
|
|
171
|
+
* Every method throws on failure — no silent nulls, no `{ ok: false }` wrappers
|
|
172
|
+
* at this layer. The MCP dispatcher (./mcp) is the boundary that converts
|
|
173
|
+
* thrown errors into structured tool errors the model can read and react to.
|
|
174
|
+
*
|
|
175
|
+
* Mutations append to the decision log themselves only when the operation
|
|
176
|
+
* dispatcher asks (`recordDecision`); plain CRUD stays log-free so human edits
|
|
177
|
+
* driven by the UI can batch their own decision entries.
|
|
178
|
+
*/
|
|
179
|
+
|
|
180
|
+
interface NewSequenceTrack {
|
|
181
|
+
kind: SequenceTrackKind;
|
|
182
|
+
name: string;
|
|
183
|
+
sortOrder?: number;
|
|
184
|
+
}
|
|
185
|
+
interface NewSequenceClip {
|
|
186
|
+
trackId: string;
|
|
187
|
+
label: string;
|
|
188
|
+
startFrame: number;
|
|
189
|
+
durationFrames: number;
|
|
190
|
+
sourceInFrame?: number;
|
|
191
|
+
sourceOutFrame?: number | null;
|
|
192
|
+
text?: string;
|
|
193
|
+
language?: string;
|
|
194
|
+
generationId?: string;
|
|
195
|
+
assetId?: string;
|
|
196
|
+
metadata?: Record<string, unknown>;
|
|
197
|
+
}
|
|
198
|
+
interface SequenceClipPatch {
|
|
199
|
+
trackId?: string;
|
|
200
|
+
label?: string;
|
|
201
|
+
startFrame?: number;
|
|
202
|
+
durationFrames?: number;
|
|
203
|
+
sourceInFrame?: number;
|
|
204
|
+
sourceOutFrame?: number | null;
|
|
205
|
+
disabled?: boolean;
|
|
206
|
+
text?: string;
|
|
207
|
+
language?: string;
|
|
208
|
+
metadata?: Record<string, unknown>;
|
|
209
|
+
}
|
|
210
|
+
interface NewSequenceDecision {
|
|
211
|
+
clipId?: string | null;
|
|
212
|
+
kind: SequenceDecision['kind'];
|
|
213
|
+
instruction: string;
|
|
214
|
+
reasoningSummary?: string | null;
|
|
215
|
+
accepted?: boolean | null;
|
|
216
|
+
metadata?: Record<string, unknown>;
|
|
217
|
+
}
|
|
218
|
+
interface SequenceStore {
|
|
219
|
+
/** Full aggregate: sequence meta + tracks + clips with resolved media. */
|
|
220
|
+
getTimeline(): Promise<SequenceTimeline>;
|
|
221
|
+
getClip(clipId: string): Promise<SequenceClip>;
|
|
222
|
+
createTrack(input: NewSequenceTrack): Promise<SequenceTrack>;
|
|
223
|
+
createClip(input: NewSequenceClip): Promise<SequenceClip>;
|
|
224
|
+
updateClip(clipId: string, patch: SequenceClipPatch): Promise<SequenceClip>;
|
|
225
|
+
deleteClip(clipId: string): Promise<void>;
|
|
226
|
+
/** Grow (or shrink, never below the last clip end) the sequence duration. */
|
|
227
|
+
updateSequenceDuration(durationFrames: number): Promise<SequenceMeta>;
|
|
228
|
+
recordDecision(input: NewSequenceDecision): Promise<SequenceDecision>;
|
|
229
|
+
createExport(format: SequenceExportFormat, metadata?: Record<string, unknown>): Promise<SequenceExportRecord>;
|
|
230
|
+
listDecisions(limit?: number): Promise<SequenceDecision[]>;
|
|
231
|
+
listExports(limit?: number): Promise<SequenceExportRecord[]>;
|
|
232
|
+
}
|
|
233
|
+
/** Per-request scope a product binds when constructing its store. Carried so
|
|
234
|
+
* decision rows and export rows attribute to the acting user; never trusted
|
|
235
|
+
* from tool arguments. */
|
|
236
|
+
interface SequenceStoreScope {
|
|
237
|
+
workspaceId: string;
|
|
238
|
+
sequenceId: string;
|
|
239
|
+
userId: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export { snapshotFrame as A, trackIntervals as B, MIN_SEQUENCE_CLIP_FRAMES as M, type NewSequenceClip as N, type SequenceClip as S, type TimelineClipBounds as T, type NewSequenceDecision as a, type NewSequenceTrack as b, type SequenceClipMedia as c, type SequenceClipPatch as d, type SequenceDecision as e, type SequenceExportFormat as f, type SequenceExportRecord as g, type SequenceExportStatus as h, type SequenceFrameSnapshot as i, type SequenceMediaKind as j, type SequenceMeta as k, type SequenceStatus as l, type SequenceStore as m, type SequenceStoreScope as n, type SequenceTimeline as o, type SequenceTrack as p, type SequenceTrackKind as q, type TimelineInterval as r, assertClipFitsSequence as s, chooseCaptionPlacement as t, clampClipDuration as u, clampClipStart as v, formatSeconds as w, formatTimecode as x, framesToSeconds as y, secondsToFrames as z };
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { b as
|
|
1
|
+
import { b as AppToolName, f as ToolHeaderNames } from '../mcp-CIupfjxV.js';
|
|
2
|
+
export { A as APP_TOOL_NAMES, a as AppToolMcpServer, c as AuthenticateOptions, B as BuildHttpMcpServerOptions, d as BuildMcpServerOptions, D as DEFAULT_APP_TOOL_PATHS, e as DEFAULT_HEADER_NAMES, O as OpenAIFunctionTool, T as ToolAuthResult, g as authenticateToolRequest, h as buildAppToolMcpServer, i as buildAppToolOpenAITools, j as buildHttpMcpServer, k as isAppToolName, r as readToolArgs } from '../mcp-CIupfjxV.js';
|
|
3
|
+
import { c as AppToolHandlers, f as AppToolTaxonomy, b as AppToolContext, e as AppToolProducedEvent, d as AppToolOutcome } from '../types-By4B3K37.js';
|
|
2
4
|
export { A as AddCitationArgs, a as AddCitationResult, R as RenderUiArgs, g as RenderUiResult, S as ScheduleFollowupArgs, h as ScheduleFollowupResult, i as SubmitProposalArgs, j as SubmitProposalResult } from '../types-By4B3K37.js';
|
|
3
5
|
|
|
4
6
|
/** A correctable bad-input error a tool handler throws; the HTTP layer maps it
|
|
@@ -39,65 +41,29 @@ declare function createCapabilityToken(userId: string, opts: CapabilityTokenOpti
|
|
|
39
41
|
/** Verify a capability token against `userId`. Returns false (never throws) for
|
|
40
42
|
* an unconfigured secret, a wrong prefix, a malformed token, or a mismatch. */
|
|
41
43
|
declare function verifyCapabilityToken(userId: string, token: string, opts: CapabilityTokenOptions): Promise<boolean>;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
*/
|
|
48
|
-
interface ToolHeaderNames {
|
|
49
|
-
userId: string;
|
|
50
|
-
workspaceId: string;
|
|
51
|
-
threadId: string;
|
|
52
|
-
}
|
|
53
|
-
declare const DEFAULT_HEADER_NAMES: ToolHeaderNames;
|
|
54
|
-
interface AuthenticateOptions {
|
|
55
|
-
/** Verify the bearer capability token belongs to `userId`. The product's
|
|
56
|
-
* HMAC/JWT impl — the seam that keeps token crypto out of this package. */
|
|
57
|
-
verifyToken: (userId: string, bearer: string) => Promise<boolean>;
|
|
58
|
-
headerNames?: ToolHeaderNames;
|
|
59
|
-
}
|
|
60
|
-
type ToolAuthResult = {
|
|
61
|
-
ok: true;
|
|
62
|
-
ctx: AppToolContext;
|
|
63
|
-
} | {
|
|
64
|
-
ok: false;
|
|
65
|
-
response: Response;
|
|
66
|
-
};
|
|
67
|
-
/**
|
|
68
|
-
* Recover + verify the trusted context for a tool request. The user comes from
|
|
69
|
-
* a server-set header and the bearer token MUST verify against THAT user; the
|
|
70
|
-
* workspace comes from a header too — never from tool args — so the model can
|
|
71
|
-
* neither forge identity nor target another workspace. Fail-closed: any missing
|
|
72
|
-
* credential or a token minted for another user yields a 401/400 Response.
|
|
73
|
-
*/
|
|
74
|
-
declare function authenticateToolRequest(request: Request, opts: AuthenticateOptions): Promise<ToolAuthResult>;
|
|
75
|
-
/** Read a tool's argument object from the request body, tolerant of MCP host
|
|
76
|
-
* aliases (`args` / `arguments`) or a bare body. Returns null on non-JSON. */
|
|
77
|
-
declare function readToolArgs<T>(request: Request): Promise<T | null>;
|
|
78
|
-
|
|
79
|
-
/** The four canonical app-tool names. Stable identifiers the model calls in
|
|
80
|
-
* both the sandbox (MCP server name) and runtime (function-tool name) paths. */
|
|
81
|
-
declare const APP_TOOL_NAMES: readonly ["submit_proposal", "schedule_followup", "render_ui", "add_citation"];
|
|
82
|
-
type AppToolName = (typeof APP_TOOL_NAMES)[number];
|
|
83
|
-
declare function isAppToolName(name: string): name is AppToolName;
|
|
84
|
-
/** A minimal OpenAI Chat Completions function-tool shape — structurally
|
|
85
|
-
* compatible with `@tangle-network/agent-runtime`'s `OpenAIChatTool` without
|
|
86
|
-
* importing it (keeps this package runtime-free). */
|
|
87
|
-
interface OpenAIFunctionTool {
|
|
88
|
-
type: 'function';
|
|
89
|
-
function: {
|
|
90
|
-
name: string;
|
|
91
|
-
description: string;
|
|
92
|
-
parameters: Record<string, unknown>;
|
|
93
|
-
};
|
|
44
|
+
interface ExpiringCapabilityTokenOptions extends CapabilityTokenOptions {
|
|
45
|
+
/** Token lifetime. Expired tokens verify false regardless of signature. */
|
|
46
|
+
expiresInMs: number;
|
|
47
|
+
/** Clock injection for tests; defaults to Date.now. */
|
|
48
|
+
now?: () => number;
|
|
94
49
|
}
|
|
95
50
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
51
|
+
* Mint an EXPIRING capability token: `<prefix><base64url(payload)>.<sig>` where
|
|
52
|
+
* the payload carries `{ sub, exp, n }` (subject, epoch-ms expiry, random
|
|
53
|
+
* nonce) and the signature is HMAC-SHA256 over the encoded payload. Use this
|
|
54
|
+
* for user-initiated scoped channels (e.g. a per-sequence MCP endpoint) where
|
|
55
|
+
* a captured token must not stay valid past its window; the bare
|
|
56
|
+
* {@link createCapabilityToken} remains for turn-scoped tool bridges whose
|
|
57
|
+
* mint+verify happen inside one request cycle. Fail-closed like the bare
|
|
58
|
+
* variant: no secret → no token.
|
|
99
59
|
*/
|
|
100
|
-
declare function
|
|
60
|
+
declare function createExpiringCapabilityToken(subject: string, opts: ExpiringCapabilityTokenOptions): Promise<string | undefined>;
|
|
61
|
+
/** Verify an expiring token against `subject`: prefix, payload integrity,
|
|
62
|
+
* subject match, and expiry all checked; returns false (never throws) on any
|
|
63
|
+
* failure including a malformed payload. */
|
|
64
|
+
declare function verifyExpiringCapabilityToken(subject: string, token: string, opts: CapabilityTokenOptions & {
|
|
65
|
+
now?: () => number;
|
|
66
|
+
}): Promise<boolean>;
|
|
101
67
|
|
|
102
68
|
interface DispatchOptions {
|
|
103
69
|
handlers: AppToolHandlers;
|
|
@@ -168,54 +134,4 @@ interface HandleToolRequestOptions extends DispatchOptions {
|
|
|
168
134
|
*/
|
|
169
135
|
declare function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response>;
|
|
170
136
|
|
|
171
|
-
|
|
172
|
-
* at these paths (or supplies its own via {@link BuildMcpServerOptions.paths}). */
|
|
173
|
-
declare const DEFAULT_APP_TOOL_PATHS: Record<AppToolName, string>;
|
|
174
|
-
/** The portable MCP server entry the sandbox SDK accepts (transport + url +
|
|
175
|
-
* headers). Matches `AgentProfileMcpServer` structurally without importing the
|
|
176
|
-
* sandbox SDK — products spread it into their profile's `mcp` map. */
|
|
177
|
-
interface AppToolMcpServer {
|
|
178
|
-
transport: 'http';
|
|
179
|
-
url: string;
|
|
180
|
-
headers: Record<string, string>;
|
|
181
|
-
enabled: true;
|
|
182
|
-
metadata: {
|
|
183
|
-
description: string;
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
interface BuildHttpMcpServerOptions {
|
|
187
|
-
/** Route path on the app the sandbox POSTs to (e.g. `/api/tools/propose`). */
|
|
188
|
-
path: string;
|
|
189
|
-
/** App base URL the sandbox reaches back to (no trailing slash required). */
|
|
190
|
-
baseUrl: string;
|
|
191
|
-
/** Per-user capability token, baked into the Authorization header. */
|
|
192
|
-
token: string;
|
|
193
|
-
ctx: AppToolContext;
|
|
194
|
-
/** Tool description the model sees. */
|
|
195
|
-
description: string;
|
|
196
|
-
headerNames?: ToolHeaderNames;
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* Build ONE HTTP MCP server entry — the generic agent→app bridge. The
|
|
200
|
-
* capability token + the user/workspace/thread ids ride in server-set headers
|
|
201
|
-
* (never tool args), so the model can't forge identity or target another
|
|
202
|
-
* workspace. Workspace/thread headers are omitted when their `ctx` value is
|
|
203
|
-
* empty/null (e.g. an integration-invoke bridge that's user-scoped only). Used
|
|
204
|
-
* directly for non-app-tool bridges (integration_invoke) and via
|
|
205
|
-
* {@link buildAppToolMcpServer} for the four app tools.
|
|
206
|
-
*/
|
|
207
|
-
declare function buildHttpMcpServer(opts: BuildHttpMcpServerOptions): AppToolMcpServer;
|
|
208
|
-
interface BuildMcpServerOptions {
|
|
209
|
-
tool: AppToolName;
|
|
210
|
-
baseUrl: string;
|
|
211
|
-
token: string;
|
|
212
|
-
ctx: AppToolContext;
|
|
213
|
-
description: string;
|
|
214
|
-
headerNames?: ToolHeaderNames;
|
|
215
|
-
paths?: Partial<Record<AppToolName, string>>;
|
|
216
|
-
}
|
|
217
|
-
/** Build one of the four app-tool MCP servers — a thin wrapper over
|
|
218
|
-
* {@link buildHttpMcpServer} that maps the tool name to its route path. */
|
|
219
|
-
declare function buildAppToolMcpServer(opts: BuildMcpServerOptions): AppToolMcpServer;
|
|
220
|
-
|
|
221
|
-
export { APP_TOOL_NAMES, AppToolContext, AppToolHandlers, type AppToolMcpServer, type AppToolName, AppToolOutcome, AppToolProducedEvent, type AppToolRuntimeExecutor, AppToolTaxonomy, type AuthenticateOptions, type BuildHttpMcpServerOptions, type BuildMcpServerOptions, type CapabilityTokenOptions, DEFAULT_APP_TOOL_PATHS, DEFAULT_HEADER_NAMES, type DispatchOptions, type HandleToolRequestOptions, type OpenAIFunctionTool, type RuntimeExecutorOptions, type ToolAuthResult, type ToolHeaderNames, ToolInputError, authenticateToolRequest, buildAppToolMcpServer, buildAppToolOpenAITools, buildHttpMcpServer, createAppToolRuntimeExecutor, createCapabilityToken, dispatchAppTool, handleAppToolRequest, isAppToolName, outcomeStatus, readToolArgs, verifyCapabilityToken };
|
|
137
|
+
export { AppToolContext, AppToolHandlers, AppToolName, AppToolOutcome, AppToolProducedEvent, type AppToolRuntimeExecutor, AppToolTaxonomy, type CapabilityTokenOptions, type DispatchOptions, type ExpiringCapabilityTokenOptions, type HandleToolRequestOptions, type RuntimeExecutorOptions, ToolHeaderNames, ToolInputError, createAppToolRuntimeExecutor, createCapabilityToken, createExpiringCapabilityToken, dispatchAppTool, handleAppToolRequest, outcomeStatus, verifyCapabilityToken, verifyExpiringCapabilityToken };
|