@runappsai/sdk 0.0.0-stage → 0.3.1
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/LICENSE +21 -0
- package/README.md +274 -2
- package/dist/activity-tracker.d.ts +113 -0
- package/dist/activity-tracker.d.ts.map +1 -0
- package/dist/activity-tracker.js +176 -0
- package/dist/activity-tracker.js.map +1 -0
- package/dist/audio.d.ts +129 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +267 -0
- package/dist/audio.js.map +1 -0
- package/dist/browser-auth.d.ts +179 -0
- package/dist/browser-auth.d.ts.map +1 -0
- package/dist/browser-auth.js +1361 -0
- package/dist/browser-auth.js.map +1 -0
- package/dist/chat.d.ts +221 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +155 -0
- package/dist/chat.js.map +1 -0
- package/dist/client.d.ts +155 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +157 -0
- package/dist/client.js.map +1 -0
- package/dist/computer.d.ts +57 -0
- package/dist/computer.d.ts.map +1 -0
- package/dist/computer.js +21 -0
- package/dist/computer.js.map +1 -0
- package/dist/embeddings.d.ts +64 -0
- package/dist/embeddings.d.ts.map +1 -0
- package/dist/embeddings.js +30 -0
- package/dist/embeddings.js.map +1 -0
- package/dist/errors.d.ts +24 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +29 -0
- package/dist/errors.js.map +1 -0
- package/dist/event-wrap.d.ts +59 -0
- package/dist/event-wrap.d.ts.map +1 -0
- package/dist/event-wrap.js +142 -0
- package/dist/event-wrap.js.map +1 -0
- package/dist/events.d.ts +119 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +87 -0
- package/dist/events.js.map +1 -0
- package/dist/files.d.ts +218 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +212 -0
- package/dist/files.js.map +1 -0
- package/dist/image.d.ts +185 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +148 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/media.d.ts +56 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +133 -0
- package/dist/media.js.map +1 -0
- package/dist/model_options.d.ts +165 -0
- package/dist/model_options.d.ts.map +1 -0
- package/dist/model_options.js +59 -0
- package/dist/model_options.js.map +1 -0
- package/dist/models.d.ts +88 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +55 -0
- package/dist/models.js.map +1 -0
- package/dist/sdk.umd.js +4 -0
- package/dist/sdk.umd.js.map +1 -0
- package/dist/transport.d.ts +120 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +214 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validate.d.ts +16 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +329 -0
- package/dist/validate.js.map +1 -0
- package/dist/video.d.ts +134 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +60 -0
- package/dist/video.js.map +1 -0
- package/package.json +54 -4
- package/src/activity-tracker.ts +251 -0
- package/src/audio.ts +440 -0
- package/src/browser-auth.ts +1529 -0
- package/src/chat.ts +368 -0
- package/src/client.ts +250 -0
- package/src/computer.ts +80 -0
- package/src/embeddings.ts +97 -0
- package/src/errors.ts +29 -0
- package/src/event-wrap.ts +168 -0
- package/src/events.ts +172 -0
- package/src/files.ts +347 -0
- package/src/image.ts +339 -0
- package/src/index.ts +151 -0
- package/src/media.ts +147 -0
- package/src/model_options.ts +220 -0
- package/src/models.ts +121 -0
- package/src/transport.ts +292 -0
- package/src/types.ts +31 -0
- package/src/validate.ts +364 -0
- package/src/video.ts +215 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RunApps SDK for JavaScript / TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* Top-level entry point — exports the `RunApps` client class plus all
|
|
5
|
+
* public types so callers can write strongly-typed wrappers without
|
|
6
|
+
* reaching into per-service modules.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { RunApps, RunApps as Client, RunApps as RunJobs, type ClientOptions, type AuthProvider } from "./client.js";
|
|
10
|
+
|
|
11
|
+
// SDK telemetry — `client.events` fires these around every LLM-ish
|
|
12
|
+
// service call so UI overlays (the badge being the canonical example)
|
|
13
|
+
// can render real-time state. The ActivityTracker turns the raw
|
|
14
|
+
// stream into the snapshot the badge reads each frame; exported
|
|
15
|
+
// alongside the bus so power users can build their own status UI.
|
|
16
|
+
export {
|
|
17
|
+
SDKEvents,
|
|
18
|
+
newRequestId,
|
|
19
|
+
type RequestStartEvent,
|
|
20
|
+
type RequestStreamDeltaEvent,
|
|
21
|
+
type RequestEndEvent,
|
|
22
|
+
type RequestErrorEvent,
|
|
23
|
+
type SDKEventMap,
|
|
24
|
+
type SDKCapability,
|
|
25
|
+
type Unsubscribe,
|
|
26
|
+
} from "./events.js";
|
|
27
|
+
export {
|
|
28
|
+
ActivityTracker,
|
|
29
|
+
type ActiveCall,
|
|
30
|
+
type CompletedCall,
|
|
31
|
+
type SessionStats,
|
|
32
|
+
type ActivitySnapshot,
|
|
33
|
+
} from "./activity-tracker.js";
|
|
34
|
+
export { BrowserAuth, type BrowserAuthOptions, type BrowserUser } from "./browser-auth.js";
|
|
35
|
+
export { APIError } from "./errors.js";
|
|
36
|
+
export type { Usage } from "./types.js";
|
|
37
|
+
|
|
38
|
+
// Chat — service class is internal; types + builders are public.
|
|
39
|
+
export type {
|
|
40
|
+
ChatMessage,
|
|
41
|
+
ContentPart,
|
|
42
|
+
ChatTool,
|
|
43
|
+
StreamOptions,
|
|
44
|
+
ChatCompletionParams,
|
|
45
|
+
ChatToolCall,
|
|
46
|
+
ChatChoiceMessage,
|
|
47
|
+
ChatChoice,
|
|
48
|
+
ChatCompletion,
|
|
49
|
+
ChatChunkDelta,
|
|
50
|
+
ChatChunkChoice,
|
|
51
|
+
ChatCompletionChunk,
|
|
52
|
+
} from "./chat.js";
|
|
53
|
+
export {
|
|
54
|
+
userMessage,
|
|
55
|
+
systemMessage,
|
|
56
|
+
assistantMessage,
|
|
57
|
+
toolResultMessage,
|
|
58
|
+
userMessageParts,
|
|
59
|
+
textPart,
|
|
60
|
+
imagePart,
|
|
61
|
+
videoPart,
|
|
62
|
+
audioPart,
|
|
63
|
+
} from "./chat.js";
|
|
64
|
+
|
|
65
|
+
// Models
|
|
66
|
+
export type { Model, ModelListOptions, Tag } from "./models.js";
|
|
67
|
+
export {
|
|
68
|
+
hasCapabilityTag,
|
|
69
|
+
acceptsModality,
|
|
70
|
+
supportsVoiceClone,
|
|
71
|
+
supportsInstructText,
|
|
72
|
+
defaultVoice,
|
|
73
|
+
} from "./models.js";
|
|
74
|
+
|
|
75
|
+
// Model options schema (input validation + capability introspection)
|
|
76
|
+
export type {
|
|
77
|
+
Schema,
|
|
78
|
+
FieldSchema,
|
|
79
|
+
Constraint,
|
|
80
|
+
ConstraintKind,
|
|
81
|
+
Catalog,
|
|
82
|
+
} from "./model_options.js";
|
|
83
|
+
export {
|
|
84
|
+
getOptionsSchema,
|
|
85
|
+
acceptsField,
|
|
86
|
+
requiresField,
|
|
87
|
+
allowedValuesFor,
|
|
88
|
+
} from "./model_options.js";
|
|
89
|
+
export type { ValidationError } from "./validate.js";
|
|
90
|
+
export { validateRequest } from "./validate.js";
|
|
91
|
+
|
|
92
|
+
// Image
|
|
93
|
+
export type {
|
|
94
|
+
ImageGenerateParams,
|
|
95
|
+
ImageResult,
|
|
96
|
+
ImageUsage,
|
|
97
|
+
ImageResponse,
|
|
98
|
+
ImageEditParams,
|
|
99
|
+
ImageFileInput,
|
|
100
|
+
AsyncImageJob,
|
|
101
|
+
} from "./image.js";
|
|
102
|
+
|
|
103
|
+
// Audio
|
|
104
|
+
export type {
|
|
105
|
+
SpeechParams,
|
|
106
|
+
SpeechResponse,
|
|
107
|
+
TranscribeParams,
|
|
108
|
+
TranscribeResponse,
|
|
109
|
+
AudioFileInput,
|
|
110
|
+
} from "./audio.js";
|
|
111
|
+
|
|
112
|
+
// Video
|
|
113
|
+
export type {
|
|
114
|
+
VideoGenerateParams,
|
|
115
|
+
VideoTask,
|
|
116
|
+
VideoStatus,
|
|
117
|
+
VideoUsageTokens,
|
|
118
|
+
WaitOptions,
|
|
119
|
+
} from "./video.js";
|
|
120
|
+
|
|
121
|
+
// Media helpers (shared by image + video + chat-multimodal)
|
|
122
|
+
export { encodeImageUrl, decodeMediaUrl } from "./media.js";
|
|
123
|
+
|
|
124
|
+
// Computer
|
|
125
|
+
export type {
|
|
126
|
+
ComputerStepParams,
|
|
127
|
+
ComputerContentBlock,
|
|
128
|
+
ComputerResponse,
|
|
129
|
+
} from "./computer.js";
|
|
130
|
+
|
|
131
|
+
// Files (per-project file system)
|
|
132
|
+
export type {
|
|
133
|
+
FileObject,
|
|
134
|
+
FileListResult,
|
|
135
|
+
PutOptions as FilePutOptions,
|
|
136
|
+
ListOptions as FileListOptions,
|
|
137
|
+
PutFromURLOptions as FilePutFromURLOptions,
|
|
138
|
+
BatchOp as FileBatchOp,
|
|
139
|
+
BatchResult as FileBatchResult,
|
|
140
|
+
} from "./files.js";
|
|
141
|
+
|
|
142
|
+
// Embeddings
|
|
143
|
+
export type {
|
|
144
|
+
EmbeddingsParams,
|
|
145
|
+
Embedding,
|
|
146
|
+
EmbeddingsUsage,
|
|
147
|
+
EmbeddingsResponse,
|
|
148
|
+
} from "./embeddings.js";
|
|
149
|
+
|
|
150
|
+
// Server-tool names for chat.create({ server_tools }), with autocomplete.
|
|
151
|
+
export { ServerTools, type ServerToolName } from "./chat.js";
|
package/src/media.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media helpers shared by image / video / chat-multimodal endpoints.
|
|
3
|
+
*
|
|
4
|
+
* The gateway accepts media payloads as `data:<mime>;base64,<payload>`
|
|
5
|
+
* URIs on every `*_url` field — `encodeImageUrl` is the bridge from
|
|
6
|
+
* raw bytes to that wire format. Replaces the deprecated `*_b64` SDK
|
|
7
|
+
* fields, which were removed in favour of routing every payload
|
|
8
|
+
* through `*_url`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Wrap raw image bytes as a `data:<mime>;base64,<payload>` URI.
|
|
13
|
+
*
|
|
14
|
+
* MIME is sniffed from the magic bytes (PNG / JPEG / GIF / WebP). On
|
|
15
|
+
* unrecognised input we default to `application/octet-stream` — most
|
|
16
|
+
* upstreams accept and re-sniff server-side.
|
|
17
|
+
*
|
|
18
|
+
* @param bytes Image bytes — `Uint8Array`, `ArrayBuffer`, or `Blob`.
|
|
19
|
+
* @returns A data: URI suitable for any `*_url` field on the gateway.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { encodeImageUrl } from "@runjobs/sdk";
|
|
24
|
+
* const png = await fs.readFile("frame.png");
|
|
25
|
+
* await client.video.generate("Veo 3.1", {
|
|
26
|
+
* prompt: "...",
|
|
27
|
+
* first_frame_url: encodeImageUrl(png),
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function encodeImageUrl(
|
|
32
|
+
bytes: Uint8Array | ArrayBuffer | Blob,
|
|
33
|
+
): string {
|
|
34
|
+
// Normalize to Uint8Array so the rest of the function is uniform.
|
|
35
|
+
if (bytes instanceof Blob) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"encodeImageUrl: Blob input requires async; await blob.arrayBuffer() and pass the result instead",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
const u8 = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
41
|
+
|
|
42
|
+
const mime = sniffImageMime(u8);
|
|
43
|
+
|
|
44
|
+
// btoa(String.fromCharCode(...u8)) blows the stack on large arrays;
|
|
45
|
+
// chunk to keep arg count bounded. 0x8000 is the conventional safe
|
|
46
|
+
// limit on JS engines.
|
|
47
|
+
const CHUNK = 0x8000;
|
|
48
|
+
let binary = "";
|
|
49
|
+
for (let i = 0; i < u8.length; i += CHUNK) {
|
|
50
|
+
binary += String.fromCharCode.apply(
|
|
51
|
+
null,
|
|
52
|
+
Array.from(u8.subarray(i, i + CHUNK)),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
// btoa is browser-native and shimmed in Node ≥ 16 globalThis.
|
|
56
|
+
const b64 = (globalThis as { btoa?: (s: string) => string }).btoa
|
|
57
|
+
? (globalThis as { btoa: (s: string) => string }).btoa(binary)
|
|
58
|
+
: Buffer.from(binary, "binary").toString("base64");
|
|
59
|
+
|
|
60
|
+
return `data:${mime};base64,${b64}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a media URL to raw bytes plus the declared mime label.
|
|
65
|
+
*
|
|
66
|
+
* Inverse of `encodeImageUrl`. Two transport modes handled:
|
|
67
|
+
* - "data:<mime>;base64,<payload>" — decode the inline payload
|
|
68
|
+
* - "https://...", "http://..." — `fetch()` + `.arrayBuffer()`,
|
|
69
|
+
* mime from the response's
|
|
70
|
+
* `Content-Type` header
|
|
71
|
+
*
|
|
72
|
+
* Returns `{bytes: Uint8Array, contentType: string}`. Pairs with
|
|
73
|
+
* `encodeImageUrl` so callers can round-trip bytes through the gateway
|
|
74
|
+
* without thinking about which transport mode the response chose.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { decodeMediaUrl } from "@runjobs/sdk";
|
|
79
|
+
* const job = await client.image.generateAsync("Seedream 5.0", {...});
|
|
80
|
+
* const { bytes, contentType } = await decodeMediaUrl(job.data[0].url);
|
|
81
|
+
* await fs.writeFile("out.png", bytes);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export async function decodeMediaUrl(
|
|
85
|
+
url: string,
|
|
86
|
+
): Promise<{ bytes: Uint8Array; contentType: string }> {
|
|
87
|
+
if (url.startsWith("data:")) {
|
|
88
|
+
const rest = url.slice(5);
|
|
89
|
+
const semi = rest.indexOf(";");
|
|
90
|
+
const comma = rest.indexOf(",");
|
|
91
|
+
if (semi === -1 || comma === -1 || comma < semi) {
|
|
92
|
+
throw new Error("decodeMediaUrl: malformed data URI");
|
|
93
|
+
}
|
|
94
|
+
const mime = rest.slice(0, semi);
|
|
95
|
+
const b64 = rest.slice(comma + 1);
|
|
96
|
+
return { bytes: base64ToBytes(b64), contentType: mime };
|
|
97
|
+
}
|
|
98
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
99
|
+
const resp = await fetch(url);
|
|
100
|
+
if (!resp.ok) {
|
|
101
|
+
throw new Error(`decodeMediaUrl: upstream ${resp.status}`);
|
|
102
|
+
}
|
|
103
|
+
const buf = await resp.arrayBuffer();
|
|
104
|
+
return {
|
|
105
|
+
bytes: new Uint8Array(buf),
|
|
106
|
+
contentType: resp.headers.get("Content-Type") ?? "",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
throw new Error(`decodeMediaUrl: unsupported url scheme: ${url.slice(0, 32)}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Decode standard base64 to Uint8Array. Browser `atob` + Node `Buffer`
|
|
113
|
+
* fallback — same impl as the audio service uses internally. */
|
|
114
|
+
function base64ToBytes(b64: string): Uint8Array {
|
|
115
|
+
if (typeof Buffer !== "undefined") {
|
|
116
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
117
|
+
}
|
|
118
|
+
const bin = atob(b64);
|
|
119
|
+
const out = new Uint8Array(bin.length);
|
|
120
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Sniff a small set of image mimes from magic bytes. Mirrors the
|
|
126
|
+
* gateway's own sniff so encode / decode round-trip lands on the same
|
|
127
|
+
* label.
|
|
128
|
+
*/
|
|
129
|
+
function sniffImageMime(b: Uint8Array): string {
|
|
130
|
+
if (b.length >= 8 &&
|
|
131
|
+
b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47) {
|
|
132
|
+
return "image/png";
|
|
133
|
+
}
|
|
134
|
+
if (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff) {
|
|
135
|
+
return "image/jpeg";
|
|
136
|
+
}
|
|
137
|
+
if (b.length >= 6 &&
|
|
138
|
+
b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x38) {
|
|
139
|
+
return "image/gif";
|
|
140
|
+
}
|
|
141
|
+
if (b.length >= 12 &&
|
|
142
|
+
b[0] === 0x52 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x46 &&
|
|
143
|
+
b[8] === 0x57 && b[9] === 0x45 && b[10] === 0x42 && b[11] === 0x50) {
|
|
144
|
+
return "image/webp";
|
|
145
|
+
}
|
|
146
|
+
return "application/octet-stream";
|
|
147
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// Mirror of internal/api/options_schema/types.go on the JS side.
|
|
2
|
+
// See API.md "Options schema" for the full contract; the gateway
|
|
3
|
+
// materialises this from the legacy options blob at /v1/models
|
|
4
|
+
// response time.
|
|
5
|
+
|
|
6
|
+
import type { Model } from "./models.js";
|
|
7
|
+
|
|
8
|
+
/** Wire-format options shape returned by /v1/models. */
|
|
9
|
+
export interface Schema {
|
|
10
|
+
/**
|
|
11
|
+
* Per-field constraints, keyed by canonical request field name
|
|
12
|
+
* (matching the JSON keys on Image/Video/Speech/Embed/Transcribe
|
|
13
|
+
* params). A field absent from this map is NOT accepted by the
|
|
14
|
+
* model.
|
|
15
|
+
*/
|
|
16
|
+
inputs?: Record<string, FieldSchema>;
|
|
17
|
+
/** Cross-field rules. */
|
|
18
|
+
constraints?: Constraint[];
|
|
19
|
+
/** Rich content (TTS voices, emotions). */
|
|
20
|
+
catalog?: Catalog;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FieldSchema {
|
|
24
|
+
type:
|
|
25
|
+
| "string"
|
|
26
|
+
| "int"
|
|
27
|
+
| "float"
|
|
28
|
+
| "bool"
|
|
29
|
+
| "url"
|
|
30
|
+
| "url[]"
|
|
31
|
+
| "string[]"
|
|
32
|
+
| "file";
|
|
33
|
+
/** Default when omitted is "optional". */
|
|
34
|
+
presence?: "required" | "optional" | "forbidden";
|
|
35
|
+
min?: number;
|
|
36
|
+
max?: number;
|
|
37
|
+
default?: unknown;
|
|
38
|
+
enum?: unknown[];
|
|
39
|
+
max_items?: number;
|
|
40
|
+
/** Lower-bound counterpart of max_items. Applies to array types
|
|
41
|
+
* AND `repeatable` object groups. Nil = no minimum. */
|
|
42
|
+
min_items?: number;
|
|
43
|
+
/** Semantic hint for media-input fields. */
|
|
44
|
+
role?: "source" | "reference" | "motion";
|
|
45
|
+
/** When present, this field is a structured sub-object. The wire
|
|
46
|
+
* value is `{<inner-name>: <value>, ...}` matching this inner
|
|
47
|
+
* schema; renderers recurse into it. With `repeatable: true` the
|
|
48
|
+
* wire value becomes an array of such objects (e.g. multi-utterance
|
|
49
|
+
* TTS "+ add another group"). */
|
|
50
|
+
fields?: Record<string, FieldSchema>;
|
|
51
|
+
/** Pairs with `fields`: array-of-objects rather than a single
|
|
52
|
+
* nested object. Ignored when `fields` is absent. */
|
|
53
|
+
repeatable?: boolean;
|
|
54
|
+
|
|
55
|
+
// ─── Display metadata — drives auto-rendered playground / form UIs.
|
|
56
|
+
// All optional; renderers fall back to type/role-based rendering
|
|
57
|
+
// when absent. See API.md "Options schema" for the full vocabulary.
|
|
58
|
+
|
|
59
|
+
/** Human-readable display label. Falls back to the field name. */
|
|
60
|
+
label?: string;
|
|
61
|
+
/** One-sentence tooltip / sub-text explainer. */
|
|
62
|
+
help?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Widget override hint:
|
|
65
|
+
* "textarea" — large multi-line string
|
|
66
|
+
* "slider" — numeric with min/max
|
|
67
|
+
* "radio" — small enum
|
|
68
|
+
* "voice_picker" — string with values from catalog.voices
|
|
69
|
+
* "code" — long string with syntax highlighting
|
|
70
|
+
* "segmented" — pill-shaped row of enum values (compact,
|
|
71
|
+
* horizontal). Best for short ordered enums
|
|
72
|
+
* like the resolution tier (512P / 1K / 2K /
|
|
73
|
+
* 4K) in image generation. Renders as a single
|
|
74
|
+
* line; selected option has solid background.
|
|
75
|
+
* "tile" — grid of square cards, each carrying a small
|
|
76
|
+
* visual proxy for the value (aspect-ratio
|
|
77
|
+
* shape icon, or fit-brackets for the
|
|
78
|
+
* "adaptive" sentinel). Used for aspect_ratio
|
|
79
|
+
* in image generation. When the enum includes
|
|
80
|
+
* "adaptive" or "auto", that entry is
|
|
81
|
+
* promoted to a row-spanning anchor block.
|
|
82
|
+
* "tabs" — horizontal tab strip. Same data contract as
|
|
83
|
+
* "radio" but a different visual. Drives
|
|
84
|
+
* `ui.show_when` on sibling fields.
|
|
85
|
+
* "card_group" — bordered card containing the field's nested
|
|
86
|
+
* `fields`. With `repeatable: true` the card
|
|
87
|
+
* becomes a "+ add another"-able stack.
|
|
88
|
+
* Unknown values fall back to the type default (forward-compat).
|
|
89
|
+
*/
|
|
90
|
+
widget?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Nested presentational hints:
|
|
93
|
+
* "group" (string) — sectional grouping ("main"/"advanced"/"output").
|
|
94
|
+
* "order" (int) — display order within the group; lower first.
|
|
95
|
+
* "card_label" (string) — title above a card_group; "{{i}}" is
|
|
96
|
+
* substituted with the card index for
|
|
97
|
+
* repeatable groups.
|
|
98
|
+
* "show_when" ({field, equals})
|
|
99
|
+
* — purely-visual conditional: hide this
|
|
100
|
+
* field unless a sibling's value matches.
|
|
101
|
+
* Validator ignores it; server still
|
|
102
|
+
* accepts conditionally-hidden fields.
|
|
103
|
+
*/
|
|
104
|
+
ui?: {
|
|
105
|
+
group?: string;
|
|
106
|
+
order?: number;
|
|
107
|
+
card_label?: string;
|
|
108
|
+
show_when?: { field: string; equals: unknown };
|
|
109
|
+
[k: string]: unknown;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export type ConstraintKind =
|
|
114
|
+
| "any_of_required"
|
|
115
|
+
| "mutually_exclusive"
|
|
116
|
+
| "group_mutex"
|
|
117
|
+
| "requires_all"
|
|
118
|
+
| "pixel_bounds";
|
|
119
|
+
|
|
120
|
+
export interface Group {
|
|
121
|
+
/** Stable id; also the value the discriminator field
|
|
122
|
+
* (`Constraint.name`) takes when this group is active. */
|
|
123
|
+
name?: string;
|
|
124
|
+
/** Tab strip display label. Falls back to `name`. */
|
|
125
|
+
label?: string;
|
|
126
|
+
fields: string[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface Constraint {
|
|
130
|
+
kind: ConstraintKind;
|
|
131
|
+
fields?: string[];
|
|
132
|
+
/**
|
|
133
|
+
* On a `group_mutex` constraint, names the discriminator field
|
|
134
|
+
* selecting which group is active. The renderer auto-injects a
|
|
135
|
+
* `tabs` input with this name (enum = group names) and gives
|
|
136
|
+
* every group member an implicit `show_when` pointing at it.
|
|
137
|
+
* When omitted, the constraint behaves as legacy anonymous-group
|
|
138
|
+
* mutex — all members render flat, mutex enforced at submit.
|
|
139
|
+
*/
|
|
140
|
+
name?: string;
|
|
141
|
+
/**
|
|
142
|
+
* Human-readable title for the synthesized tab widget (e.g.
|
|
143
|
+
* "Animation mode"). Falls back to `name` when omitted.
|
|
144
|
+
*/
|
|
145
|
+
label?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Used by `group_mutex`. Wire shape is either the legacy
|
|
148
|
+
* `string[][]` (each `[a,b,...]` reads as an anonymous
|
|
149
|
+
* `{fields:[a,b,...]}` Group) or the new `Group[]`. Consumers
|
|
150
|
+
* should normalize via `normalizeGroups()` before reading.
|
|
151
|
+
*/
|
|
152
|
+
groups?: Group[] | string[][];
|
|
153
|
+
when?: string;
|
|
154
|
+
then?: string[];
|
|
155
|
+
min?: number;
|
|
156
|
+
max?: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Normalize either legacy `string[][]` or new `Group[]` into a
|
|
161
|
+
* uniform `Group[]`. Mirror of sdk-go's `Group.UnmarshalJSON`.
|
|
162
|
+
*/
|
|
163
|
+
export function normalizeGroups(g: Constraint["groups"]): Group[] {
|
|
164
|
+
if (!g) return [];
|
|
165
|
+
return g.map(item =>
|
|
166
|
+
Array.isArray(item) ? { fields: item as string[] } : (item as Group),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface Catalog {
|
|
171
|
+
voices?: Array<Record<string, unknown>>;
|
|
172
|
+
emotions?: string[];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Parse the model's wire-format `options` blob into the typed Schema.
|
|
177
|
+
* Returns null when the model has no options or the blob can't be
|
|
178
|
+
* structurally decoded — callers fall back to whatever default
|
|
179
|
+
* behaviour they had pre-schema.
|
|
180
|
+
*/
|
|
181
|
+
export function getOptionsSchema(model: Model): Schema | null {
|
|
182
|
+
const opts = model.options;
|
|
183
|
+
if (!opts || typeof opts !== "object") return null;
|
|
184
|
+
// The Model interface types options as Record<string, unknown>;
|
|
185
|
+
// Schema has the same JSON layout, so a structural cast is safe
|
|
186
|
+
// as long as the gateway emits the wire shape we expect. SDK
|
|
187
|
+
// consumers should call validateRequest() to surface errors —
|
|
188
|
+
// this getter is a thin typed view over the same data.
|
|
189
|
+
return opts as unknown as Schema;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* True iff the model declares an Inputs entry for `name` with
|
|
194
|
+
* presence != "forbidden". Use for "should I show this UI chip"
|
|
195
|
+
* decisions.
|
|
196
|
+
*/
|
|
197
|
+
export function acceptsField(model: Model, name: string): boolean {
|
|
198
|
+
const schema = getOptionsSchema(model);
|
|
199
|
+
const f = schema?.inputs?.[name];
|
|
200
|
+
if (!f) return false;
|
|
201
|
+
return f.presence !== "forbidden";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* True iff the schema marks `name` as presence: "required". Use for
|
|
206
|
+
* red-star UI marks and submit-button gating.
|
|
207
|
+
*/
|
|
208
|
+
export function requiresField(model: Model, name: string): boolean {
|
|
209
|
+
const schema = getOptionsSchema(model);
|
|
210
|
+
return schema?.inputs?.[name]?.presence === "required";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Discrete enum for the named field, or null if no enum constraint.
|
|
215
|
+
* Use to populate dropdown options on the client.
|
|
216
|
+
*/
|
|
217
|
+
export function allowedValuesFor(model: Model, name: string): unknown[] | null {
|
|
218
|
+
const schema = getOptionsSchema(model);
|
|
219
|
+
return schema?.inputs?.[name]?.enum ?? null;
|
|
220
|
+
}
|
package/src/models.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Transport } from "./transport.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One model available on the gateway. Pricing is in **pips** (1 USD =
|
|
5
|
+
* 1,000,000 pips), per million tokens — multiply by tokens / 10⁶ for the
|
|
6
|
+
* billed pip cost, or by tokens / 10¹² for USD.
|
|
7
|
+
*/
|
|
8
|
+
export interface Model {
|
|
9
|
+
id: string;
|
|
10
|
+
object: string;
|
|
11
|
+
capability: string;
|
|
12
|
+
provider?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Server-derived "what this model can actually do" tags
|
|
15
|
+
* (e.g. {id:"t2v", label:"Text-to-Video"}, {id:"i2v", label:"Image-to-Video"}).
|
|
16
|
+
* Use `hasCapabilityTag` for stable filter checks; iterate for display.
|
|
17
|
+
* Labels are in English — frontends localise on their side.
|
|
18
|
+
*/
|
|
19
|
+
capability_tags?: Tag[];
|
|
20
|
+
/**
|
|
21
|
+
* Accepted input modalities for chat models. Omitted on capabilities
|
|
22
|
+
* where it doesn't apply (image generation, TTS, etc.). Mirrors
|
|
23
|
+
* Anthropic / Gemini's own `inputModalities` field — operator-set on
|
|
24
|
+
* the gateway, surfaced verbatim here.
|
|
25
|
+
*
|
|
26
|
+
* Canonical values: `"text"`, `"image"`, `"video"`, `"audio"`. The
|
|
27
|
+
* list is open — future modalities ride through without an SDK
|
|
28
|
+
* update. Use `acceptsModality` for the stable filter check.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const videoCapable = models.filter(m => acceptsModality(m, "video"));
|
|
32
|
+
*/
|
|
33
|
+
input_modalities?: string[];
|
|
34
|
+
options?: Record<string, unknown>;
|
|
35
|
+
input_price_per_mtok: number;
|
|
36
|
+
output_price_per_mtok: number;
|
|
37
|
+
fixed_price: boolean;
|
|
38
|
+
fixed_cost: number;
|
|
39
|
+
max_tokens: number;
|
|
40
|
+
max_input_tokens: number;
|
|
41
|
+
icon_url?: string;
|
|
42
|
+
available_voices?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* One capability tag. Mirrors options_schema.Tag on the gateway. IDs
|
|
47
|
+
* are stable across releases; Labels are subject to translation.
|
|
48
|
+
*/
|
|
49
|
+
export interface Tag {
|
|
50
|
+
id: string;
|
|
51
|
+
label: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** True iff `model.capability_tags` includes a tag with the given stable ID. */
|
|
55
|
+
export const hasCapabilityTag = (model: Model, id: string): boolean =>
|
|
56
|
+
Array.isArray(model.capability_tags) &&
|
|
57
|
+
model.capability_tags.some((t) => t.id === id);
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* True iff the chat model accepts the given input modality. Lets you
|
|
61
|
+
* filter by `"image"` / `"video"` / `"audio"` (or `"text"`, always
|
|
62
|
+
* true for chat models that set the field at all) without remembering
|
|
63
|
+
* whether the gateway uses canonical or aliased names.
|
|
64
|
+
*
|
|
65
|
+
* Returns `false` when `input_modalities` is unset — text-only is the
|
|
66
|
+
* conservative default.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const videoModels = (await client.models.list())
|
|
70
|
+
* .filter(m => acceptsModality(m, "video"));
|
|
71
|
+
*/
|
|
72
|
+
export const acceptsModality = (model: Model, modality: string): boolean =>
|
|
73
|
+
Array.isArray(model.input_modalities) &&
|
|
74
|
+
model.input_modalities.includes(modality);
|
|
75
|
+
|
|
76
|
+
export interface ModelListOptions {
|
|
77
|
+
/** Filter to one capability (e.g. "text", "vision", "image", "tts"). */
|
|
78
|
+
capability?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** True iff the gateway flag in `options[key]` is set. Accepts bool or numeric 1. */
|
|
82
|
+
function optBool(model: Model, key: string): boolean {
|
|
83
|
+
const v = model.options?.[key];
|
|
84
|
+
if (typeof v === "boolean") return v;
|
|
85
|
+
if (typeof v === "number") return v !== 0;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Convenience: TTS model accepts a reference audio clip as `voice`? */
|
|
90
|
+
export const supportsVoiceClone = (model: Model): boolean =>
|
|
91
|
+
optBool(model, "supports_voice_clone");
|
|
92
|
+
|
|
93
|
+
/** Convenience: TTS model accepts a free-form `instruct_text` directive? */
|
|
94
|
+
export const supportsInstructText = (model: Model): boolean =>
|
|
95
|
+
optBool(model, "supports_instruct_text");
|
|
96
|
+
|
|
97
|
+
/** Convenience: admin-set default voice for a TTS model, or null. */
|
|
98
|
+
export const defaultVoice = (model: Model): string | null => {
|
|
99
|
+
const v = model.options?.["default_voice"];
|
|
100
|
+
return typeof v === "string" ? v : null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export class ModelsService {
|
|
104
|
+
constructor(private readonly transport: Transport) {}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* List models available on the gateway. Pass `{ capability: "..." }` to
|
|
108
|
+
* filter server-side.
|
|
109
|
+
*/
|
|
110
|
+
async list(
|
|
111
|
+
opts: ModelListOptions = {},
|
|
112
|
+
init?: { signal?: AbortSignal },
|
|
113
|
+
): Promise<Model[]> {
|
|
114
|
+
let path = "/v1/models";
|
|
115
|
+
if (opts.capability) {
|
|
116
|
+
path += `?capability=${encodeURIComponent(opts.capability)}`;
|
|
117
|
+
}
|
|
118
|
+
const resp = await this.transport.getJSON<{ data: Model[] }>(path, init);
|
|
119
|
+
return resp.data;
|
|
120
|
+
}
|
|
121
|
+
}
|