@moxxy/sdk 0.1.3 → 0.3.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/client-session.d.ts +10 -0
- package/dist/client-session.d.ts.map +1 -1
- package/dist/compactor-helpers.d.ts.map +1 -1
- package/dist/compactor-helpers.js +13 -2
- package/dist/compactor-helpers.js.map +1 -1
- package/dist/define.d.ts +2 -0
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +3 -0
- package/dist/define.js.map +1 -1
- package/dist/events.d.ts +11 -8
- package/dist/events.d.ts.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mode-helpers.d.ts.map +1 -1
- package/dist/mode-helpers.js +8 -0
- package/dist/mode-helpers.js.map +1 -1
- package/dist/mode.d.ts +26 -0
- package/dist/mode.d.ts.map +1 -1
- package/dist/plugin.d.ts +10 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/provider.d.ts +21 -0
- package/dist/provider.d.ts.map +1 -1
- package/dist/requirements.d.ts +1 -1
- package/dist/requirements.d.ts.map +1 -1
- package/dist/schemas.d.ts +16 -16
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +2 -0
- package/dist/schemas.js.map +1 -1
- package/dist/session-like.d.ts +13 -1
- package/dist/session-like.d.ts.map +1 -1
- package/dist/synthesizer.d.ts +63 -0
- package/dist/synthesizer.d.ts.map +1 -0
- package/dist/synthesizer.js +21 -0
- package/dist/synthesizer.js.map +1 -0
- package/dist/tool.d.ts +13 -0
- package/dist/tool.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client-session.ts +11 -0
- package/src/compactor-helpers.ts +12 -2
- package/src/define.ts +5 -0
- package/src/events.ts +11 -8
- package/src/index.ts +11 -0
- package/src/mode-helpers.ts +7 -0
- package/src/mode.ts +27 -0
- package/src/plugin.ts +10 -1
- package/src/provider.ts +17 -1
- package/src/requirements.ts +1 -0
- package/src/schemas.ts +2 -0
- package/src/session-like.ts +13 -1
- package/src/synthesizer.ts +67 -0
- package/src/token-efficiency.test.ts +58 -0
- package/src/tool.ts +13 -0
package/src/provider.ts
CHANGED
|
@@ -10,7 +10,16 @@ export type ContentBlock =
|
|
|
10
10
|
| { readonly type: 'tool_use'; readonly id: string; readonly name: string; readonly input: unknown }
|
|
11
11
|
| { readonly type: 'tool_result'; readonly toolUseId: string; readonly content: string; readonly isError?: boolean }
|
|
12
12
|
| { readonly type: 'image'; readonly mediaType: string; readonly data: string }
|
|
13
|
-
| { readonly type: 'audio'; readonly mediaType: string; readonly data: string }
|
|
13
|
+
| { readonly type: 'audio'; readonly mediaType: string; readonly data: string }
|
|
14
|
+
/**
|
|
15
|
+
* A document the model reads natively (e.g. a PDF). `data` is base64-encoded
|
|
16
|
+
* bytes and `mediaType` the document MIME (e.g. `application/pdf`). Providers
|
|
17
|
+
* that support documents translate it to their native shape (Anthropic
|
|
18
|
+
* `document`, OpenAI `file`, Responses `input_file`); those that don't degrade
|
|
19
|
+
* to a text placeholder. Text/Office files are inlined as `text` instead — only
|
|
20
|
+
* formats a model ingests as bytes become `document` blocks.
|
|
21
|
+
*/
|
|
22
|
+
| { readonly type: 'document'; readonly mediaType: string; readonly data: string; readonly name?: string };
|
|
14
23
|
|
|
15
24
|
/**
|
|
16
25
|
* Provider-neutral instruction for where a prompt-cache breakpoint should be
|
|
@@ -68,6 +77,13 @@ export interface ModelDescriptor {
|
|
|
68
77
|
* refuses or warns instead of silently dropping the bytes.
|
|
69
78
|
*/
|
|
70
79
|
readonly supportsImages?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Whether this model accepts `document` ContentBlocks (e.g. native PDF) in
|
|
82
|
+
* user messages. When false, the desktop reader extracts text instead of
|
|
83
|
+
* shipping the raw bytes, so the attachment still reaches the model — just
|
|
84
|
+
* without full-document fidelity (figures/layout).
|
|
85
|
+
*/
|
|
86
|
+
readonly supportsDocuments?: boolean;
|
|
71
87
|
/**
|
|
72
88
|
* Whether this model accepts `audio` ContentBlocks in user messages
|
|
73
89
|
* (GPT-4o, Gemini-Live-class models). When false, channels with audio
|
package/src/requirements.ts
CHANGED
package/src/schemas.ts
CHANGED
|
@@ -15,6 +15,7 @@ const pluginKindSchema = z.enum([
|
|
|
15
15
|
'agent',
|
|
16
16
|
'command',
|
|
17
17
|
'transcriber',
|
|
18
|
+
'synthesizer',
|
|
18
19
|
]);
|
|
19
20
|
|
|
20
21
|
export const requirementSchema = z.object({
|
|
@@ -23,6 +24,7 @@ export const requirementSchema = z.object({
|
|
|
23
24
|
'provider',
|
|
24
25
|
'tool',
|
|
25
26
|
'transcriber',
|
|
27
|
+
'synthesizer',
|
|
26
28
|
'mode',
|
|
27
29
|
'compactor',
|
|
28
30
|
'channel',
|
package/src/session-like.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MoxxyEvent, UserPromptAttachment } from './events.js';
|
|
2
2
|
import type { SessionId, TurnId } from './ids.js';
|
|
3
3
|
import type { EventLogReader } from './log.js';
|
|
4
|
-
import type { ApprovalResolver } from './mode.js';
|
|
4
|
+
import type { ApprovalResolver, ModeBadge } from './mode.js';
|
|
5
5
|
import type { PermissionResolver } from './permission.js';
|
|
6
6
|
import type { ModelDescriptor } from './provider.js';
|
|
7
7
|
import type { ToolCompactPresentation } from './tool.js';
|
|
@@ -96,6 +96,13 @@ export interface SessionInfo {
|
|
|
96
96
|
readonly activeProvider: string | null;
|
|
97
97
|
readonly providers: ReadonlyArray<ProviderInfo>;
|
|
98
98
|
readonly activeMode: string | null;
|
|
99
|
+
/**
|
|
100
|
+
* Presentation hint for the active mode, when it advertises one (see
|
|
101
|
+
* {@link ModeBadge}). Lets channels render a persistent accent badge for
|
|
102
|
+
* autonomous modes like goal mode. `null` when no mode is active or the
|
|
103
|
+
* active mode declares no badge.
|
|
104
|
+
*/
|
|
105
|
+
readonly activeModeBadge: ModeBadge | null;
|
|
99
106
|
readonly modes: ReadonlyArray<string>;
|
|
100
107
|
readonly tools: ReadonlyArray<ToolInfo>;
|
|
101
108
|
readonly skills: ReadonlyArray<SkillInfo>;
|
|
@@ -105,6 +112,11 @@ export interface SessionInfo {
|
|
|
105
112
|
readonly hasTranscriber: boolean;
|
|
106
113
|
/** Name of the active transcriber, or null. Lets a thin client proxy STT. */
|
|
107
114
|
readonly activeTranscriber: string | null;
|
|
115
|
+
/** Whether any text-to-speech backend is registered. */
|
|
116
|
+
readonly hasSynthesizer: boolean;
|
|
117
|
+
/** Name of the active synthesizer, or null. Lets a thin client proxy TTS
|
|
118
|
+
* (the desktop routes "Read aloud" through it; null → OS voice fallback). */
|
|
119
|
+
readonly activeSynthesizer: string | null;
|
|
108
120
|
}
|
|
109
121
|
|
|
110
122
|
/**
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthesizers convert text into spoken audio (text-to-speech). They are the
|
|
3
|
+
* symmetric counterpart to {@link Transcriber} (speech-to-text): a separate,
|
|
4
|
+
* swappable capability so a user can pair any text provider with a dedicated
|
|
5
|
+
* TTS backend (the OS voice, ElevenLabs, OpenAI, a local engine, …).
|
|
6
|
+
*
|
|
7
|
+
* Plugins register a `SynthesizerDef` via `PluginSpec.synthesizers`. The core
|
|
8
|
+
* `SynthesizerRegistry` holds them; surfaces that read aloud (the desktop's
|
|
9
|
+
* "Read aloud" button, a future voice channel) call
|
|
10
|
+
* `session.synthesizers.getActive()` (or `tryGetActive()`) to turn text into
|
|
11
|
+
* audio. There is at most one *active* synthesizer at a time; when none is
|
|
12
|
+
* active the caller degrades gracefully (the desktop falls back to the
|
|
13
|
+
* browser/OS `speechSynthesis`).
|
|
14
|
+
*
|
|
15
|
+
* The user swaps the active synthesizer by asking the agent ("use ElevenLabs
|
|
16
|
+
* for read-aloud") — there is no settings UI. The agent activates a registered
|
|
17
|
+
* synthesizer (a plugin it authored/installed self-activates in `onInit`, or it
|
|
18
|
+
* calls a set-active tool); switching back to the OS voice deactivates it.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export interface SynthesizeOptions {
|
|
22
|
+
/** Preferred voice id/name, when the backend supports selection. */
|
|
23
|
+
readonly voice?: string;
|
|
24
|
+
/** BCP-47 language hint (e.g. `en`, `pl`). */
|
|
25
|
+
readonly language?: string;
|
|
26
|
+
/** Speaking rate multiplier (1.0 = normal), when supported. */
|
|
27
|
+
readonly rate?: number;
|
|
28
|
+
/** Cancellation signal — callers propagate a stop/abort here. */
|
|
29
|
+
readonly signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface SynthesisResult {
|
|
33
|
+
/** Encoded audio bytes ready to play (e.g. an mp3/wav/ogg payload). */
|
|
34
|
+
readonly audio: Uint8Array;
|
|
35
|
+
/** MIME type of {@link audio}, e.g. `audio/mpeg`, `audio/wav`, `audio/ogg`. */
|
|
36
|
+
readonly mimeType: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Synthesizer {
|
|
40
|
+
/** Short stable name, e.g. `elevenlabs`. */
|
|
41
|
+
readonly name: string;
|
|
42
|
+
synthesize(text: string, opts?: SynthesizeOptions): Promise<SynthesisResult>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Context handed to {@link SynthesizerDef.create} when the registry activates a
|
|
47
|
+
* synthesizer. `getSecret` reads a vault secret (the API key) the same way a
|
|
48
|
+
* tool handler does via `ctx.getSecret` — so an authored TTS plugin never has
|
|
49
|
+
* to touch `process.env`, and the key never enters the model's context.
|
|
50
|
+
*/
|
|
51
|
+
export interface SynthesizerCreateContext {
|
|
52
|
+
/** Per-synthesizer config (e.g. from `session.synthesizers.setActive(name, config)`). */
|
|
53
|
+
readonly config: Record<string, unknown>;
|
|
54
|
+
/** Read a named vault secret, or null if unset / no vault is wired. */
|
|
55
|
+
readonly getSecret?: (name: string) => Promise<string | null>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Plugin-side definition. Mirrors `TranscriberDef`: a `create(ctx)` factory the
|
|
60
|
+
* registry calls when this synthesizer is activated.
|
|
61
|
+
*/
|
|
62
|
+
export interface SynthesizerDef {
|
|
63
|
+
readonly name: string;
|
|
64
|
+
/** Optional human-readable label for UI surfaces. */
|
|
65
|
+
readonly displayName?: string;
|
|
66
|
+
create(ctx: SynthesizerCreateContext): Synthesizer;
|
|
67
|
+
}
|
|
@@ -457,3 +457,61 @@ describe('applyLazyTools', () => {
|
|
|
457
457
|
expect(messages).toBe(baseMsgs); // same reference → byte-stable
|
|
458
458
|
});
|
|
459
459
|
});
|
|
460
|
+
|
|
461
|
+
describe('attachment projection in projectMessagesFromLog', () => {
|
|
462
|
+
it('projects an image attachment to an image content block', () => {
|
|
463
|
+
const msgs = projectMessagesFromLog({
|
|
464
|
+
log: reader([
|
|
465
|
+
event(0, {
|
|
466
|
+
type: 'user_prompt',
|
|
467
|
+
turnId: t1,
|
|
468
|
+
source: 'user',
|
|
469
|
+
text: 'what is this?',
|
|
470
|
+
attachments: [{ kind: 'image', content: 'AAAA', mediaType: 'image/png', name: 'pic.png' }],
|
|
471
|
+
}),
|
|
472
|
+
]),
|
|
473
|
+
});
|
|
474
|
+
expect(msgs[0].content).toEqual([
|
|
475
|
+
{ type: 'text', text: 'what is this?' },
|
|
476
|
+
{ type: 'image', mediaType: 'image/png', data: 'AAAA' },
|
|
477
|
+
]);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('projects a document attachment to a native document content block', () => {
|
|
481
|
+
const msgs = projectMessagesFromLog({
|
|
482
|
+
log: reader([
|
|
483
|
+
event(0, {
|
|
484
|
+
type: 'user_prompt',
|
|
485
|
+
turnId: t1,
|
|
486
|
+
source: 'user',
|
|
487
|
+
text: 'summarize this',
|
|
488
|
+
attachments: [
|
|
489
|
+
{ kind: 'document', content: 'JVBERi0=', mediaType: 'application/pdf', name: 'r.pdf' },
|
|
490
|
+
],
|
|
491
|
+
}),
|
|
492
|
+
]),
|
|
493
|
+
});
|
|
494
|
+
expect(msgs[0].content).toEqual([
|
|
495
|
+
{ type: 'text', text: 'summarize this' },
|
|
496
|
+
{ type: 'document', mediaType: 'application/pdf', data: 'JVBERi0=', name: 'r.pdf' },
|
|
497
|
+
]);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it('inlines a file attachment as a labeled text block', () => {
|
|
501
|
+
const msgs = projectMessagesFromLog({
|
|
502
|
+
log: reader([
|
|
503
|
+
event(0, {
|
|
504
|
+
type: 'user_prompt',
|
|
505
|
+
turnId: t1,
|
|
506
|
+
source: 'user',
|
|
507
|
+
text: 'review',
|
|
508
|
+
attachments: [{ kind: 'file', content: 'const x = 1;', name: 'a.ts' }],
|
|
509
|
+
}),
|
|
510
|
+
]),
|
|
511
|
+
});
|
|
512
|
+
expect(msgs[0].content).toEqual([
|
|
513
|
+
{ type: 'text', text: 'review' },
|
|
514
|
+
{ type: 'text', text: '[file a.ts]\nconst x = 1;' },
|
|
515
|
+
]);
|
|
516
|
+
});
|
|
517
|
+
});
|
package/src/tool.ts
CHANGED
|
@@ -127,6 +127,19 @@ export interface ToolContext {
|
|
|
127
127
|
* `caps.subprocess` + optional `caps.commands` allowlist.
|
|
128
128
|
*/
|
|
129
129
|
readonly exec?: BrokeredExec;
|
|
130
|
+
/**
|
|
131
|
+
* Read a named secret from the host's secret store (the vault) at call
|
|
132
|
+
* time. Present when the session is wired with a vault-backed resolver
|
|
133
|
+
* (the CLI/runner does this). Returns the plaintext value, or `null` if
|
|
134
|
+
* no such secret exists.
|
|
135
|
+
*
|
|
136
|
+
* This is the blessed way for a tool/plugin to consume an API key or
|
|
137
|
+
* token: the plaintext only ever reaches the handler that asks for it —
|
|
138
|
+
* it is never placed in the model's context or in `process.env`. Store
|
|
139
|
+
* the secret once via `/vault set NAME …` (or the desktop Secrets UI),
|
|
140
|
+
* then read it here, e.g. `const key = await ctx.getSecret('ELEVENLABS_API_KEY')`.
|
|
141
|
+
*/
|
|
142
|
+
readonly getSecret?: (name: string) => Promise<string | null>;
|
|
130
143
|
}
|
|
131
144
|
|
|
132
145
|
/**
|