@jini-ai/daemon 0.6.7 → 0.6.9
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/CHANGELOG.md +8 -0
- package/dist/agent-executor/antigravity-permissions.d.ts +11 -0
- package/dist/agent-executor/antigravity-permissions.js +64 -0
- package/dist/agent-executor/contracts.d.ts +31 -6
- package/dist/agent-executor/event-translation.d.ts +6 -1
- package/dist/agent-executor/event-translation.js +1 -1
- package/dist/agent-executor/events.d.ts +1 -0
- package/dist/agent-executor/events.js +85 -74
- package/dist/agent-executor/launch.d.ts +4 -2
- package/dist/agent-executor/launch.js +306 -217
- package/dist/agent-executor/mcp-bridge-probe.js +51 -35
- package/dist/agent-executor/mcp-bridge.d.ts +18 -8
- package/dist/agent-executor/mcp-bridge.js +105 -65
- package/dist/agent-executor/run-output.d.ts +20 -0
- package/dist/agent-executor/run-output.js +80 -0
- package/dist/attachment-content.js +18 -15
- package/dist/core/read-only-tools.d.ts +26 -3
- package/dist/core/read-only-tools.js +38 -5
- package/dist/core/tool-audit.d.ts +3 -1
- package/dist/core/tool-audit.js +35 -22
- package/dist/core/tool-candidates.d.ts +8 -0
- package/dist/core/tool-candidates.js +6 -0
- package/dist/core/tool-gaps.d.ts +60 -0
- package/dist/core/tool-gaps.js +199 -0
- package/dist/delegated-tool-bridge.d.ts +1 -1
- package/dist/delegated-tool-bridge.js +161 -122
- package/dist/event-log.d.ts +45 -2
- package/dist/event-log.js +83 -4
- package/dist/http/attachments.d.ts +26 -1
- package/dist/http/attachments.js +129 -65
- package/dist/http/connectors.d.ts +1 -32
- package/dist/http/connectors.js +13 -68
- package/dist/http/delegated-tools.d.ts +5 -1
- package/dist/http/delegated-tools.js +29 -29
- package/dist/http/host-tools.d.ts +1 -0
- package/dist/http/host-tools.js +64 -59
- package/dist/http/index.d.ts +2 -2
- package/dist/http/index.js +1 -1
- package/dist/http/media.js +48 -69
- package/dist/http/memory.js +45 -36
- package/dist/http/model-proxy-fields.d.ts +23 -0
- package/dist/http/model-proxy-fields.js +29 -0
- package/dist/http/model-proxy.js +193 -395
- package/dist/http/research-tavily.adapter.js +72 -49
- package/dist/http/research.js +33 -23
- package/dist/http/resource-request.d.ts +37 -0
- package/dist/http/resource-request.js +59 -0
- package/dist/http/routines.js +49 -90
- package/dist/http/runs.d.ts +7 -5
- package/dist/http/runs.js +21 -7
- package/dist/http/terminals.js +23 -48
- package/dist/http/tool-catalog.d.ts +5 -0
- package/dist/http/xai.d.ts +11 -1
- package/dist/http/xai.js +125 -124
- package/dist/legacy-data-migration.js +41 -24
- package/dist/run/core/retry.js +51 -35
- package/dist/run/diagnostics/diagnostics.js +5 -17
- package/dist/run-credentials.js +49 -27
- package/dist/run-lifecycle.d.ts +12 -2
- package/dist/run-lifecycle.js +47 -26
- package/dist/store/agent-sessions/sqlite.d.ts +6 -0
- package/dist/store/agent-sessions/sqlite.js +6 -4
- package/dist/surface-exchanges.js +2 -1
- package/dist/terminal-session.d.ts +1 -1
- package/dist/terminal-session.js +26 -33
- package/dist/tool-audit.d.ts +1 -0
- package/dist/tool-audit.js +1 -0
- package/dist/tool-executor.d.ts +24 -3
- package/dist/tool-executor.js +277 -139
- package/dist/tool-recovery/recovery.js +64 -35
- package/dist/tool-recovery/stack.d.ts +2 -1
- package/dist/tool-recovery/stack.js +1 -1
- package/package.json +10 -10
|
@@ -102,6 +102,7 @@ type ResolvedEntry = {
|
|
|
102
102
|
} | {
|
|
103
103
|
readonly available: false;
|
|
104
104
|
};
|
|
105
|
+
/** Resolves how to launch `entry`: its CLI shim first, then (darwin only) its app bundle, else unavailable. */
|
|
105
106
|
export declare function resolveEntry({ entry }: {
|
|
106
107
|
readonly entry: CatalogueEntry;
|
|
107
108
|
}, { probeEnv }?: {
|
package/dist/http/host-tools.js
CHANGED
|
@@ -24,7 +24,8 @@ import { createApiError } from '@jini-ai/protocol';
|
|
|
24
24
|
import { defineJsonRoute, mountJsonRoute } from '@jini-ai/http-kit';
|
|
25
25
|
import { validationError } from '@jini-ai/http-kit';
|
|
26
26
|
import { err, ok } from '@jini-ai/http-kit';
|
|
27
|
-
import { denyAllWorkspaceRoots
|
|
27
|
+
import { denyAllWorkspaceRoots } from './workspace-root.js';
|
|
28
|
+
import { isRecord, readOptionalNonEmptyString, resolveResourceRoot, withoutUndefined } from './resource-request.js';
|
|
28
29
|
const MAC_OPEN_COMMAND = '/usr/bin/open';
|
|
29
30
|
/** The cross-platform-staple + macOS-bundle editor/tool catalogue, unchanged from the origin. */
|
|
30
31
|
export const CATALOGUE = [
|
|
@@ -148,33 +149,38 @@ export async function probeMacBundle({ name, probeEnv }, _optional = {}) {
|
|
|
148
149
|
}
|
|
149
150
|
return null;
|
|
150
151
|
}
|
|
152
|
+
/** The CLI-shim half of {@link resolveEntry}: `null` when the entry has no `command` or it is not found. */
|
|
153
|
+
async function resolveViaCommand({ entry, probeEnv }) {
|
|
154
|
+
if (!entry.command)
|
|
155
|
+
return null;
|
|
156
|
+
const resolved = await probeCommandOnPath({ command: entry.command, probeEnv });
|
|
157
|
+
if (!resolved)
|
|
158
|
+
return null;
|
|
159
|
+
const commandArgs = entry.commandArgs ?? (({ workingDir }) => [workingDir]);
|
|
160
|
+
return { available: true, resolvedPath: resolved, launch: { command: resolved, argsForDir: ({ workingDir }) => commandArgs({ workingDir }) } };
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The macOS app-bundle half of {@link resolveEntry} (launched via `open -a`): `null` without a
|
|
164
|
+
* `macOpenBundle` or when no bundle is found. Off darwin it is `null` too: {@link probeMacBundle}
|
|
165
|
+
* itself returns `null` before any filesystem access on every non-darwin platform, so it is the
|
|
166
|
+
* single darwin gate (this function used to repeat that check).
|
|
167
|
+
*/
|
|
168
|
+
async function resolveViaMacBundle({ entry, probeEnv }) {
|
|
169
|
+
if (!entry.macOpenBundle)
|
|
170
|
+
return null;
|
|
171
|
+
const bundle = await probeMacBundle({ name: entry.macOpenBundle, probeEnv });
|
|
172
|
+
if (!bundle)
|
|
173
|
+
return null;
|
|
174
|
+
const macOpenArgs = entry.macOpenArgs ?? (({ bundleName, workingDir }) => ['-a', bundleName, workingDir]);
|
|
175
|
+
return {
|
|
176
|
+
available: true,
|
|
177
|
+
resolvedPath: bundle.path,
|
|
178
|
+
launch: { command: await resolveMacOpenCommand(probeEnv), argsForDir: ({ workingDir }) => macOpenArgs({ bundleName: bundle.name, workingDir }) },
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** Resolves how to launch `entry`: its CLI shim first, then (darwin only) its app bundle, else unavailable. */
|
|
151
182
|
export async function resolveEntry({ entry }, { probeEnv = defaultProbeEnv({}) } = {}) {
|
|
152
|
-
|
|
153
|
-
const resolved = await probeCommandOnPath({ command: entry.command, probeEnv });
|
|
154
|
-
if (resolved) {
|
|
155
|
-
return {
|
|
156
|
-
available: true,
|
|
157
|
-
resolvedPath: resolved,
|
|
158
|
-
launch: { command: resolved, argsForDir: entry.commandArgs ? ({ workingDir }) => entry.commandArgs({ workingDir }) : ({ workingDir }) => [workingDir] },
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if (entry.macOpenBundle && probeEnv.platform === 'darwin') {
|
|
163
|
-
const bundle = await probeMacBundle({ name: entry.macOpenBundle, probeEnv });
|
|
164
|
-
if (bundle) {
|
|
165
|
-
return {
|
|
166
|
-
available: true,
|
|
167
|
-
resolvedPath: bundle.path,
|
|
168
|
-
launch: {
|
|
169
|
-
command: await resolveMacOpenCommand(probeEnv),
|
|
170
|
-
argsForDir: entry.macOpenArgs
|
|
171
|
-
? ({ workingDir }) => entry.macOpenArgs({ bundleName: bundle.name, workingDir })
|
|
172
|
-
: ({ workingDir }) => ['-a', bundle.name, workingDir],
|
|
173
|
-
},
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return { available: false };
|
|
183
|
+
return (await resolveViaCommand({ entry, probeEnv })) ?? (await resolveViaMacBundle({ entry, probeEnv })) ?? { available: false };
|
|
178
184
|
}
|
|
179
185
|
/** Resolves the concrete `{command, args}` a caller would spawn to open `workingDir` in `editorId`, or `available: false` if that tool isn't installed/known. */
|
|
180
186
|
export async function resolveHostToolLaunchPlan({ editorId, workingDir }, { probeEnv = defaultProbeEnv({}) } = {}) {
|
|
@@ -249,9 +255,6 @@ export async function listAvailableEditors(_required, { probeEnv = defaultProbeE
|
|
|
249
255
|
}
|
|
250
256
|
/** `GET /api/editors` — lists the host-tool catalogue applicable to this platform with live availability. */
|
|
251
257
|
export const hostEditorsRoute = defineJsonRoute({ method: 'get', path: '/api/editors', parse: () => ok({ value: undefined }), handle: async () => ok({ value: await listAvailableEditors({}) }) });
|
|
252
|
-
function isRecord(value) {
|
|
253
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
254
|
-
}
|
|
255
258
|
function parseOpenResourceInEditor(input) {
|
|
256
259
|
const resourceRef = input.params.resourceRef;
|
|
257
260
|
if (typeof resourceRef !== 'string' || resourceRef.length === 0) {
|
|
@@ -263,11 +266,31 @@ function parseOpenResourceInEditor(input) {
|
|
|
263
266
|
if (typeof editorId !== 'string' || editorId.length === 0) {
|
|
264
267
|
return err({ error: validationError({ message: 'editorId is required' }, { issues: [{ path: 'editorId', message: 'required non-empty string' }] }) });
|
|
265
268
|
}
|
|
266
|
-
const detail = input.body
|
|
267
|
-
if (detail
|
|
268
|
-
return
|
|
269
|
+
const detail = readOptionalNonEmptyString({ body: input.body, field: 'detail' });
|
|
270
|
+
if (!detail.ok)
|
|
271
|
+
return detail;
|
|
272
|
+
return ok({ value: { resourceRef, editorId, ...withoutUndefined({ detail: detail.value }) } });
|
|
273
|
+
}
|
|
274
|
+
/** Looks `editorId` up in {@link CATALOGUE} and checks it applies to `platform`; both refusals are `BAD_REQUEST`. */
|
|
275
|
+
function selectEditor({ editorId, platform }) {
|
|
276
|
+
const entry = CATALOGUE.find((c) => c.id === editorId);
|
|
277
|
+
if (!entry)
|
|
278
|
+
return err({ error: createApiError({ code: 'BAD_REQUEST', message: `unknown editor: ${editorId}` }) });
|
|
279
|
+
if (!applicableForPlatform({ entry, platform })) {
|
|
280
|
+
return err({ error: createApiError({ code: 'BAD_REQUEST', message: `${entry.label} is not available on ${platform}` }) });
|
|
281
|
+
}
|
|
282
|
+
return ok({ value: entry });
|
|
283
|
+
}
|
|
284
|
+
/** Probes `entry` and launches it at `workingDir`: `CONFLICT` when it is not installed, `INTERNAL_ERROR` (with the OS's own reason) when the launch is refused. */
|
|
285
|
+
async function launchEditorAt({ entry, workingDir }, { probeEnv, spawnImpl }) {
|
|
286
|
+
const probe = await resolveEntry({ entry }, { probeEnv });
|
|
287
|
+
if (!probe.available)
|
|
288
|
+
return err({ error: createApiError({ code: 'CONFLICT', message: `${entry.label} is not installed` }) });
|
|
289
|
+
const launch = await launchHostTool({ command: probe.launch.command, args: probe.launch.argsForDir({ workingDir }) }, { spawnImpl });
|
|
290
|
+
if (!launch.ok) {
|
|
291
|
+
return err({ error: createApiError({ code: 'INTERNAL_ERROR', message: `failed to launch ${entry.label}: ${launch.error}` }) });
|
|
269
292
|
}
|
|
270
|
-
return ok({ value: {
|
|
293
|
+
return ok({ value: { ok: true, editorId: entry.id, path: workingDir } });
|
|
271
294
|
}
|
|
272
295
|
/**
|
|
273
296
|
* `POST /api/resources/:resourceRef/open-in` — launches `editorId` (from
|
|
@@ -293,32 +316,14 @@ function parseOpenResourceInEditor(input) {
|
|
|
293
316
|
*/
|
|
294
317
|
export const openResourceInEditorRoute = defineJsonRoute({
|
|
295
318
|
method: 'post', path: '/api/resources/:resourceRef/open-in', parse: parseOpenResourceInEditor, handle: async ({ input, deps }) => {
|
|
296
|
-
const entry = CATALOGUE.find((c) => c.id === input.editorId);
|
|
297
|
-
if (!entry)
|
|
298
|
-
return err({ error: createApiError({ code: 'BAD_REQUEST', message: `unknown editor: ${input.editorId}` }) });
|
|
299
319
|
const probeEnv = deps.probeEnv ?? defaultProbeEnv({});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
catch (error) {
|
|
308
|
-
if (error instanceof WorkspaceRootDeniedError) {
|
|
309
|
-
return err({ error: createApiError({ code: 'NOT_FOUND', message: `resource "${input.resourceRef}" was not found` }) });
|
|
310
|
-
}
|
|
311
|
-
throw error;
|
|
312
|
-
}
|
|
313
|
-
const launchPlan = await resolveHostToolLaunchPlan({ editorId: input.editorId, workingDir: resolvedDir }, { probeEnv });
|
|
314
|
-
if (!launchPlan.available || !launchPlan.command || !launchPlan.args) {
|
|
315
|
-
return err({ error: createApiError({ code: 'CONFLICT', message: `${entry.label} is not installed` }) });
|
|
316
|
-
}
|
|
317
|
-
const launch = await launchHostTool({ command: launchPlan.command, args: launchPlan.args }, { spawnImpl: deps.spawnImpl ?? nodeSpawn });
|
|
318
|
-
if (!launch.ok) {
|
|
319
|
-
return err({ error: createApiError({ code: 'INTERNAL_ERROR', message: `failed to launch ${entry.label}: ${launch.error}` }) });
|
|
320
|
-
}
|
|
321
|
-
return ok({ value: { ok: true, editorId: input.editorId, path: resolvedDir } });
|
|
320
|
+
const editor = selectEditor({ editorId: input.editorId, platform: probeEnv.platform });
|
|
321
|
+
if (!editor.ok)
|
|
322
|
+
return editor;
|
|
323
|
+
const root = await resolveResourceRoot({ resourceRef: input.resourceRef, detail: input.detail }, { resolver: deps.resolveRoot ?? denyAllWorkspaceRoots });
|
|
324
|
+
if (!root.ok)
|
|
325
|
+
return root;
|
|
326
|
+
return launchEditorAt({ entry: editor.value, workingDir: root.value }, { probeEnv, spawnImpl: deps.spawnImpl ?? nodeSpawn });
|
|
322
327
|
}
|
|
323
328
|
}, { requireSameOrigin: true });
|
|
324
329
|
/** Mounts `GET /api/editors` and `POST /api/resources/:resourceRef/open-in` on `app`. A pack's `http(app, services)` calls this directly. */
|
package/dist/http/index.d.ts
CHANGED
|
@@ -38,8 +38,8 @@ export type { ModelProxyHttpDeps, ModelProxyInternalErrorContext } from './model
|
|
|
38
38
|
export { registerModelProxyRoutes } from './model-proxy.js';
|
|
39
39
|
export type { HealthHttpDeps, HealthReadinessResult, LivenessResponse, ReadinessResponse, VersionResponse } from './health.js';
|
|
40
40
|
export { apiHealthRoute, apiReadyRoute, apiVersionInfoRoute, healthRoute, readyRoute, registerHealthRoutes, versionInfoRoute, } from './health.js';
|
|
41
|
-
export type { AuthCredentials, AuthProvider, AuthSession, AuthUser,
|
|
42
|
-
export { connectorsAuthSessionRoute, connectorsAuthSignInRoute, connectorsAuthSignOutRoute, connectorsAuthSignUpRoute, connectorsDbDeleteRoute, connectorsDbGetRoute, connectorsDbInsertRoute, connectorsDbQueryRoute, connectorsDbUpdateRoute,
|
|
41
|
+
export type { AuthCredentials, AuthProvider, AuthSession, AuthUser, ConnectorsAuthSessionResponse, ConnectorsAuthUserResponse, ConnectorsAuthVerifyResponse, ConnectorsDbQueryResponse, ConnectorsDbRecordResponse, ConnectorsHttpDeps, ConnectorsInternalErrorContext, ConnectorsOkResponse, ConnectorsStorageGetResponse, ConnectorsStorageListResponse, ConnectorsStorageMetaResponse, DbProvider, DbQuery, DbRecord, RealtimeProvider, StorageObjectMeta, StorageProvider, StoragePutOptions, } from './connectors.js';
|
|
42
|
+
export { connectorsAuthSessionRoute, connectorsAuthSignInRoute, connectorsAuthSignOutRoute, connectorsAuthSignUpRoute, connectorsDbDeleteRoute, connectorsDbGetRoute, connectorsDbInsertRoute, connectorsDbQueryRoute, connectorsDbUpdateRoute, connectorsRealtimePublishRoute, connectorsStorageDeleteRoute, connectorsStorageGetRoute, connectorsStorageListRoute, connectorsStoragePutRoute, registerConnectorsRoutes, } from './connectors.js';
|
|
43
43
|
export type { ResearchProvider, ResearchSearchRequest, ResearchHttpDeps, ResearchInternalErrorContext, ResearchProviderCredentials, ResearchSearchResponse, ResearchSource, } from './research.js';
|
|
44
44
|
export { createResearchSearchRoute, registerResearchRoutes, researchSearchRoute } from './research.js';
|
|
45
45
|
export type { MediaGenerateResponse, MediaHttpDeps, MediaInternalErrorContext, MediaTaskDeleteResponse, MediaTaskListResponse, MediaTaskResponse, } from './media.js';
|
package/dist/http/index.js
CHANGED
|
@@ -18,7 +18,7 @@ export { createFrontendControl } from './frontend-control.js';
|
|
|
18
18
|
export { registerTerminalEventStream, registerTerminalRoutes, terminalCreateRoute, terminalDeleteRoute, terminalKillRoute, terminalListRoute, terminalResizeRoute, terminalStdinRoute, } from './terminals.js';
|
|
19
19
|
export { registerModelProxyRoutes } from './model-proxy.js';
|
|
20
20
|
export { apiHealthRoute, apiReadyRoute, apiVersionInfoRoute, healthRoute, readyRoute, registerHealthRoutes, versionInfoRoute, } from './health.js';
|
|
21
|
-
export { connectorsAuthSessionRoute, connectorsAuthSignInRoute, connectorsAuthSignOutRoute, connectorsAuthSignUpRoute, connectorsDbDeleteRoute, connectorsDbGetRoute, connectorsDbInsertRoute, connectorsDbQueryRoute, connectorsDbUpdateRoute,
|
|
21
|
+
export { connectorsAuthSessionRoute, connectorsAuthSignInRoute, connectorsAuthSignOutRoute, connectorsAuthSignUpRoute, connectorsDbDeleteRoute, connectorsDbGetRoute, connectorsDbInsertRoute, connectorsDbQueryRoute, connectorsDbUpdateRoute, connectorsRealtimePublishRoute, connectorsStorageDeleteRoute, connectorsStorageGetRoute, connectorsStorageListRoute, connectorsStoragePutRoute, registerConnectorsRoutes, } from './connectors.js';
|
|
22
22
|
export { createResearchSearchRoute, registerResearchRoutes, researchSearchRoute } from './research.js';
|
|
23
23
|
export { mediaGenerateRoute, mediaTaskDeleteRoute, mediaTaskGetRoute, mediaTaskListRoute, registerMediaRoutes, } from './media.js';
|
|
24
24
|
export { ATTACHMENTS_ROUTE_PATH, AttachmentRejectedError, createDiskAttachmentStore, detectAttachmentKind, handleAttachmentCleanup, handleAttachmentUpload, isUnchangedAttachment, registerAttachmentRoutes, sanitizeAttachmentName, writeBoundedAttachmentBody, } from './attachments.js';
|
package/dist/http/media.js
CHANGED
|
@@ -115,85 +115,64 @@ function parseOptionalImageRefs(body) {
|
|
|
115
115
|
}
|
|
116
116
|
return ok({ value: refs });
|
|
117
117
|
}
|
|
118
|
+
function parseOptionalMember(body, key, allowed) {
|
|
119
|
+
const value = body[key];
|
|
120
|
+
if (value === undefined)
|
|
121
|
+
return ok({ value: undefined });
|
|
122
|
+
if (typeof value !== 'string' || !allowed.has(value)) {
|
|
123
|
+
return err({ error: validationError({ message: `${key} must be one of: ${[...allowed].join(', ')} when provided` }) });
|
|
124
|
+
}
|
|
125
|
+
return ok({ value });
|
|
126
|
+
}
|
|
127
|
+
/** Table order is the validation order: the first failing field is the one a caller sees. */
|
|
128
|
+
const OPTIONAL_FIELD_PARSERS = [
|
|
129
|
+
['audioKind', (body) => parseOptionalMember(body, 'audioKind', AUDIO_KINDS)],
|
|
130
|
+
['speechFormat', (body) => parseOptionalMember(body, 'speechFormat', SPEECH_FORMATS)],
|
|
131
|
+
['prompt', (body) => parseOptionalString(body, 'prompt')],
|
|
132
|
+
['aspect', (body) => parseOptionalString(body, 'aspect')],
|
|
133
|
+
['voice', (body) => parseOptionalString(body, 'voice')],
|
|
134
|
+
['language', (body) => parseOptionalString(body, 'language')],
|
|
135
|
+
['wireModel', (body) => parseOptionalString(body, 'wireModel')],
|
|
136
|
+
['length', (body) => parseOptionalNumber(body, 'length')],
|
|
137
|
+
['duration', (body) => parseOptionalNumber(body, 'duration')],
|
|
138
|
+
['promptInfluence', (body) => parseOptionalNumber(body, 'promptInfluence')],
|
|
139
|
+
['loop', (body) => parseOptionalBoolean(body, 'loop')],
|
|
140
|
+
['imageRef', parseOptionalImageRef],
|
|
141
|
+
['imageRefs', parseOptionalImageRefs],
|
|
142
|
+
];
|
|
143
|
+
/** Collects every provided optional field; an absent field is omitted, never written as `undefined`. */
|
|
144
|
+
function parseOptionalFields(body) {
|
|
145
|
+
const fields = {};
|
|
146
|
+
for (const [key, parse] of OPTIONAL_FIELD_PARSERS) {
|
|
147
|
+
const parsed = parse(body);
|
|
148
|
+
if (!parsed.ok)
|
|
149
|
+
return parsed;
|
|
150
|
+
if (parsed.value !== undefined)
|
|
151
|
+
fields[key] = parsed.value;
|
|
152
|
+
}
|
|
153
|
+
return ok({ value: fields });
|
|
154
|
+
}
|
|
155
|
+
function requiredStringError(key) {
|
|
156
|
+
return err({ error: validationError({ message: `${key} must be a non-empty string` }, { issues: [{ path: key, message: 'required non-empty string' }] }) });
|
|
157
|
+
}
|
|
118
158
|
function parseMediaGenerate(input) {
|
|
119
159
|
if (!isRecord(input.body))
|
|
120
160
|
return err({ error: validationError({ message: 'body must be a JSON object' }) });
|
|
121
161
|
const body = input.body;
|
|
122
162
|
const ownerRef = requireNonEmptyString(body, 'ownerRef');
|
|
123
|
-
if (ownerRef === undefined)
|
|
124
|
-
return
|
|
125
|
-
}
|
|
163
|
+
if (ownerRef === undefined)
|
|
164
|
+
return requiredStringError('ownerRef');
|
|
126
165
|
const surface = body.surface;
|
|
127
166
|
if (typeof surface !== 'string' || !SURFACES.has(surface)) {
|
|
128
167
|
return err({ error: validationError({ message: 'surface must be one of: image, video, audio' }) });
|
|
129
168
|
}
|
|
130
169
|
const model = requireNonEmptyString(body, 'model');
|
|
131
|
-
if (model === undefined)
|
|
132
|
-
return
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
const speechFormat = body.speechFormat;
|
|
139
|
-
if (speechFormat !== undefined && (typeof speechFormat !== 'string' || !SPEECH_FORMATS.has(speechFormat))) {
|
|
140
|
-
return err({ error: validationError({ message: 'speechFormat must be one of: mp3, wav, flac, aac, opus when provided' }) });
|
|
141
|
-
}
|
|
142
|
-
const prompt = parseOptionalString(body, 'prompt');
|
|
143
|
-
if (!prompt.ok)
|
|
144
|
-
return prompt;
|
|
145
|
-
const aspect = parseOptionalString(body, 'aspect');
|
|
146
|
-
if (!aspect.ok)
|
|
147
|
-
return aspect;
|
|
148
|
-
const voice = parseOptionalString(body, 'voice');
|
|
149
|
-
if (!voice.ok)
|
|
150
|
-
return voice;
|
|
151
|
-
const language = parseOptionalString(body, 'language');
|
|
152
|
-
if (!language.ok)
|
|
153
|
-
return language;
|
|
154
|
-
const wireModel = parseOptionalString(body, 'wireModel');
|
|
155
|
-
if (!wireModel.ok)
|
|
156
|
-
return wireModel;
|
|
157
|
-
const length = parseOptionalNumber(body, 'length');
|
|
158
|
-
if (!length.ok)
|
|
159
|
-
return length;
|
|
160
|
-
const duration = parseOptionalNumber(body, 'duration');
|
|
161
|
-
if (!duration.ok)
|
|
162
|
-
return duration;
|
|
163
|
-
const promptInfluence = parseOptionalNumber(body, 'promptInfluence');
|
|
164
|
-
if (!promptInfluence.ok)
|
|
165
|
-
return promptInfluence;
|
|
166
|
-
const loop = parseOptionalBoolean(body, 'loop');
|
|
167
|
-
if (!loop.ok)
|
|
168
|
-
return loop;
|
|
169
|
-
const imageRef = parseOptionalImageRef(body);
|
|
170
|
-
if (!imageRef.ok)
|
|
171
|
-
return imageRef;
|
|
172
|
-
const imageRefs = parseOptionalImageRefs(body);
|
|
173
|
-
if (!imageRefs.ok)
|
|
174
|
-
return imageRefs;
|
|
175
|
-
return ok({
|
|
176
|
-
value: {
|
|
177
|
-
ownerRef,
|
|
178
|
-
request: {
|
|
179
|
-
surface: surface,
|
|
180
|
-
model,
|
|
181
|
-
...(prompt.value !== undefined ? { prompt: prompt.value } : {}),
|
|
182
|
-
...(aspect.value !== undefined ? { aspect: aspect.value } : {}),
|
|
183
|
-
...(length.value !== undefined ? { length: length.value } : {}),
|
|
184
|
-
...(duration.value !== undefined ? { duration: duration.value } : {}),
|
|
185
|
-
...(voice.value !== undefined ? { voice: voice.value } : {}),
|
|
186
|
-
...(audioKind !== undefined ? { audioKind: audioKind } : {}),
|
|
187
|
-
...(language.value !== undefined ? { language: language.value } : {}),
|
|
188
|
-
...(loop.value !== undefined ? { loop: loop.value } : {}),
|
|
189
|
-
...(promptInfluence.value !== undefined ? { promptInfluence: promptInfluence.value } : {}),
|
|
190
|
-
...(imageRef.value !== undefined ? { imageRef: imageRef.value } : {}),
|
|
191
|
-
...(imageRefs.value !== undefined ? { imageRefs: imageRefs.value } : {}),
|
|
192
|
-
...(wireModel.value !== undefined ? { wireModel: wireModel.value } : {}),
|
|
193
|
-
...(speechFormat !== undefined ? { speechFormat: speechFormat } : {}),
|
|
194
|
-
},
|
|
195
|
-
}
|
|
196
|
-
});
|
|
170
|
+
if (model === undefined)
|
|
171
|
+
return requiredStringError('model');
|
|
172
|
+
const options = parseOptionalFields(body);
|
|
173
|
+
if (!options.ok)
|
|
174
|
+
return options;
|
|
175
|
+
return ok({ value: { ownerRef, request: { surface: surface, model, ...options.value } } });
|
|
197
176
|
}
|
|
198
177
|
function parseTaskId(input) {
|
|
199
178
|
const id = input.params.id;
|
package/dist/http/memory.js
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import { createApiError } from '@jini-ai/protocol';
|
|
2
|
-
import { defineJsonRoute, mountJsonRoute } from '@jini-ai/http-kit';
|
|
3
|
-
import { guardSameOrigin } from '@jini-ai/http-kit';
|
|
4
|
-
import { validationError } from '@jini-ai/http-kit';
|
|
5
|
-
import { sendApiError, statusForError } from '@jini-ai/http-kit';
|
|
6
|
-
import { createSseChannel } from '@jini-ai/http-kit';
|
|
7
|
-
import { err, ok } from '@jini-ai/http-kit';
|
|
2
|
+
import { createSseChannel, defineJsonRoute, err, guardSameOrigin, mountJsonRoute, ok, sendApiError, statusForError, validationError, } from '@jini-ai/http-kit';
|
|
8
3
|
function isRecord(value) {
|
|
9
4
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
10
5
|
}
|
|
11
6
|
function errorMessage(error) {
|
|
12
7
|
return error instanceof Error ? error.message : String(error);
|
|
13
8
|
}
|
|
9
|
+
/** Reads an optional string field: absent (`undefined`) passes through, anything else that is not a string — `null` included — is refused. */
|
|
10
|
+
function readOptionalString(body, key) {
|
|
11
|
+
const value = body[key];
|
|
12
|
+
if (value === undefined || typeof value === 'string')
|
|
13
|
+
return ok({ value });
|
|
14
|
+
return err({ error: validationError({ message: `${key} must be a string when provided` }) });
|
|
15
|
+
}
|
|
16
|
+
/** Reads a required, non-empty string field, refusing with a per-field validation issue. */
|
|
17
|
+
function readRequiredString(body, key) {
|
|
18
|
+
const value = body[key];
|
|
19
|
+
if (typeof value === 'string' && value.length > 0)
|
|
20
|
+
return ok({ value });
|
|
21
|
+
return err({ error: validationError({ message: `${key} is required` }, { issues: [{ path: key, message: 'required non-empty string' }] }) });
|
|
22
|
+
}
|
|
23
|
+
/** The `BAD_REQUEST` every mutating route answers when its store call throws, carrying the store's own message. */
|
|
24
|
+
function storeFailure(error) {
|
|
25
|
+
return err({ error: createApiError({ code: 'BAD_REQUEST', message: errorMessage(error) }) });
|
|
26
|
+
}
|
|
14
27
|
function parseIdParam(input) {
|
|
15
28
|
const id = input.params.id;
|
|
16
29
|
return typeof id === 'string' && id.length > 0 ? ok({ value: id }) : err({ error: validationError({ message: 'id must be a non-empty path parameter' }) });
|
|
@@ -45,12 +58,11 @@ function parseUpdateTreeNode(input) {
|
|
|
45
58
|
return err({ error: validationError({ message: 'body must be a JSON object' }) });
|
|
46
59
|
const patch = {};
|
|
47
60
|
for (const key of ['name', 'description', 'type', 'body']) {
|
|
48
|
-
const value = input.body
|
|
49
|
-
if (value
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
patch[key] = value;
|
|
61
|
+
const value = readOptionalString(input.body, key);
|
|
62
|
+
if (!value.ok)
|
|
63
|
+
return value;
|
|
64
|
+
if (value.value !== undefined)
|
|
65
|
+
patch[key] = value.value;
|
|
54
66
|
}
|
|
55
67
|
return ok({ value: { id: parsedId.value, patch } });
|
|
56
68
|
}
|
|
@@ -105,7 +117,7 @@ export const memoryWriteIndexRoute = defineJsonRoute({
|
|
|
105
117
|
return ok({ value: { index } });
|
|
106
118
|
}
|
|
107
119
|
catch (error) {
|
|
108
|
-
return
|
|
120
|
+
return storeFailure(error);
|
|
109
121
|
}
|
|
110
122
|
},
|
|
111
123
|
}, { requireSameOrigin: true });
|
|
@@ -128,7 +140,7 @@ export const memoryWriteConfigRoute = defineJsonRoute({
|
|
|
128
140
|
return ok({ value: { enabled: next.enabled } });
|
|
129
141
|
}
|
|
130
142
|
catch (error) {
|
|
131
|
-
return
|
|
143
|
+
return storeFailure(error);
|
|
132
144
|
}
|
|
133
145
|
},
|
|
134
146
|
}, { requireSameOrigin: true });
|
|
@@ -171,27 +183,24 @@ export const memoryRemoveVerificationRoute = defineJsonRoute({
|
|
|
171
183
|
function parseEntryInput(input) {
|
|
172
184
|
if (!isRecord(input.body))
|
|
173
185
|
return err({ error: validationError({ message: 'body must be a JSON object' }) });
|
|
174
|
-
const
|
|
175
|
-
if (
|
|
176
|
-
return
|
|
177
|
-
|
|
178
|
-
if (
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (body !== undefined && typeof body !== 'string') {
|
|
187
|
-
return err({ error: validationError({ message: 'body must be a string when provided' }) });
|
|
188
|
-
}
|
|
186
|
+
const name = readRequiredString(input.body, 'name');
|
|
187
|
+
if (!name.ok)
|
|
188
|
+
return name;
|
|
189
|
+
const type = readRequiredString(input.body, 'type');
|
|
190
|
+
if (!type.ok)
|
|
191
|
+
return type;
|
|
192
|
+
const description = readOptionalString(input.body, 'description');
|
|
193
|
+
if (!description.ok)
|
|
194
|
+
return description;
|
|
195
|
+
const body = readOptionalString(input.body, 'body');
|
|
196
|
+
if (!body.ok)
|
|
197
|
+
return body;
|
|
189
198
|
return ok({
|
|
190
199
|
value: {
|
|
191
|
-
name,
|
|
192
|
-
type,
|
|
193
|
-
...(description === undefined ? {} : { description }),
|
|
194
|
-
...(body === undefined ? {} : { body }),
|
|
200
|
+
name: name.value,
|
|
201
|
+
type: type.value,
|
|
202
|
+
...(description.value === undefined ? {} : { description: description.value }),
|
|
203
|
+
...(body.value === undefined ? {} : { body: body.value }),
|
|
195
204
|
}
|
|
196
205
|
});
|
|
197
206
|
}
|
|
@@ -205,7 +214,7 @@ export const memoryCreateEntryRoute = defineJsonRoute({
|
|
|
205
214
|
return ok({ value: { entry } });
|
|
206
215
|
}
|
|
207
216
|
catch (error) {
|
|
208
|
-
return
|
|
217
|
+
return storeFailure(error);
|
|
209
218
|
}
|
|
210
219
|
},
|
|
211
220
|
}, { requireSameOrigin: true, successStatus: 201 });
|
|
@@ -237,7 +246,7 @@ export const memoryUpdateEntryRoute = defineJsonRoute({
|
|
|
237
246
|
return ok({ value: { entry } });
|
|
238
247
|
}
|
|
239
248
|
catch (error) {
|
|
240
|
-
return
|
|
249
|
+
return storeFailure(error);
|
|
241
250
|
}
|
|
242
251
|
},
|
|
243
252
|
}, { requireSameOrigin: true });
|
|
@@ -251,7 +260,7 @@ export const memoryDeleteEntryRoute = defineJsonRoute({
|
|
|
251
260
|
return ok({ value: { ok: true } });
|
|
252
261
|
}
|
|
253
262
|
catch (error) {
|
|
254
|
-
return
|
|
263
|
+
return storeFailure(error);
|
|
255
264
|
}
|
|
256
265
|
},
|
|
257
266
|
}, { requireSameOrigin: true });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module model-proxy-fields
|
|
3
|
+
*
|
|
4
|
+
* Request-body field helpers for `model-proxy.ts`'s provider parsers: primitive type tests, the
|
|
5
|
+
* table-driven "optional field present but badly typed" check, the required-field 400 builder, and
|
|
6
|
+
* the "forward an optional field only when present" copy. Transport-level shape only — nothing here
|
|
7
|
+
* knows any provider's wire protocol (see `model-proxy.ts`'s module doc).
|
|
8
|
+
*/
|
|
9
|
+
import type { ApiError } from '@jini-ai/protocol';
|
|
10
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
11
|
+
export declare function nonEmptyString(value: unknown): string | null;
|
|
12
|
+
export declare const isString: (value: unknown) => boolean;
|
|
13
|
+
export declare const isNumber: (value: unknown) => boolean;
|
|
14
|
+
export declare const isArray: (value: unknown) => boolean;
|
|
15
|
+
/** One optional body field: its name, the primitive-type test it must pass when present, and the 400 message when it does not. */
|
|
16
|
+
export type OptionalFieldCheck = readonly [field: string, isValid: (value: unknown) => boolean, message: string];
|
|
17
|
+
/** The first optional field that is present (not `undefined`) but fails its type test, as a 400 error; `undefined` when every present field is well-typed. */
|
|
18
|
+
export declare function firstOptionalFieldError(record: Record<string, unknown>, checks: readonly OptionalFieldCheck[]): ApiError | undefined;
|
|
19
|
+
/** A 400 for a required field, carrying the single-issue `details` the client can point at. */
|
|
20
|
+
export declare function requiredFieldError(path: string, message: string, issue: string): ApiError;
|
|
21
|
+
/** Copies only the listed keys whose value is not `undefined` — the "forward an optional field only when present" rule every parser and turn-runner call shares. */
|
|
22
|
+
export declare function pickDefined<T extends object, K extends keyof T>(source: T, keys: readonly K[]): Partial<Pick<T, K>>;
|
|
23
|
+
//# sourceMappingURL=model-proxy-fields.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { validationError } from '@jini-ai/http-kit';
|
|
2
|
+
export function isRecord(value) {
|
|
3
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
export function nonEmptyString(value) {
|
|
6
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : null;
|
|
7
|
+
}
|
|
8
|
+
export const isString = (value) => typeof value === 'string';
|
|
9
|
+
export const isNumber = (value) => typeof value === 'number';
|
|
10
|
+
export const isArray = (value) => Array.isArray(value);
|
|
11
|
+
/** The first optional field that is present (not `undefined`) but fails its type test, as a 400 error; `undefined` when every present field is well-typed. */
|
|
12
|
+
export function firstOptionalFieldError(record, checks) {
|
|
13
|
+
const failed = checks.find(([field, isValid]) => record[field] !== undefined && !isValid(record[field]));
|
|
14
|
+
return failed ? validationError({ message: failed[2] }) : undefined;
|
|
15
|
+
}
|
|
16
|
+
/** A 400 for a required field, carrying the single-issue `details` the client can point at. */
|
|
17
|
+
export function requiredFieldError(path, message, issue) {
|
|
18
|
+
return validationError({ message }, { issues: [{ path, message: issue }] });
|
|
19
|
+
}
|
|
20
|
+
/** Copies only the listed keys whose value is not `undefined` — the "forward an optional field only when present" rule every parser and turn-runner call shares. */
|
|
21
|
+
export function pickDefined(source, keys) {
|
|
22
|
+
const picked = {};
|
|
23
|
+
for (const key of keys) {
|
|
24
|
+
if (source[key] !== undefined)
|
|
25
|
+
picked[key] = source[key];
|
|
26
|
+
}
|
|
27
|
+
return picked;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=model-proxy-fields.js.map
|