@x12i/api-hub-models 0.0.0-stage → 0.1.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/README.md CHANGED
@@ -1,3 +1,11 @@
1
- # Temporary Holding Version
1
+ # @x12i/api-hub-models
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Hosted-model submit and poll adapter for the x12i API hub
4
+
5
+ ESM package for Node.js 24 or newer. Import the public name:
6
+
7
+ ```js
8
+ import * as models from '@x12i/api-hub-models';
9
+ ```
10
+
11
+ The whole set is also available from [`@x12i/api-hub`](https://www.npmjs.com/package/@x12i/api-hub).
@@ -0,0 +1,32 @@
1
+ import { type ModelOutput, type ModelsClient } from '@x12i/api-hub-core';
2
+ export type PredictionStatus = 'queued' | 'running' | 'succeeded' | 'failed';
3
+ export interface Prediction {
4
+ id: string;
5
+ status: PredictionStatus;
6
+ output?: unknown;
7
+ error?: string;
8
+ }
9
+ /** One hosted-model provider: submit work, then poll it. */
10
+ export interface ModelAdapter {
11
+ readonly name: string;
12
+ submit(model: string, input: Record<string, unknown>, signal?: AbortSignal): Promise<Prediction>;
13
+ poll(id: string, signal?: AbortSignal): Promise<Prediction>;
14
+ }
15
+ /** Maps provider output (strings, URL lists, objects) onto the hub's ModelOutput shape. */
16
+ export declare function normaliseOutput(model: string, output: unknown): ModelOutput;
17
+ export interface ModelsClientOptions {
18
+ adapter: ModelAdapter;
19
+ pollIntervalMs?: number;
20
+ maxPollIntervalMs?: number;
21
+ defaultTimeoutMs?: number;
22
+ }
23
+ export declare function createModelsClient(options: ModelsClientOptions): ModelsClient;
24
+ /** Fixture adapter for tests: each submit returns the next scripted sequence of poll states. */
25
+ export declare function scriptedAdapter(script: Record<string, Prediction[]>): ModelAdapter & {
26
+ submitted: string[];
27
+ };
28
+ /**
29
+ * Hosted-model provider wiring. No provider is enabled until the owner records one in
30
+ * docs/CONVENTIONS.md; until then every call is `service_not_connected`.
31
+ */
32
+ export declare function createModelsFromEnv(_env?: NodeJS.ProcessEnv): ModelsClient;
package/dist/index.js ADDED
@@ -0,0 +1,97 @@
1
+ import { setTimeout as sleep } from 'node:timers/promises';
2
+ import { HubError, stubModels } from '@x12i/api-hub-core';
3
+ const URL_LIKE = /^https:\/\/\S+$/;
4
+ /** Maps provider output (strings, URL lists, objects) onto the hub's ModelOutput shape. */
5
+ export function normaliseOutput(model, output) {
6
+ const result = { model };
7
+ const files = [];
8
+ const texts = [];
9
+ const visit = (value) => {
10
+ if (typeof value === 'string') {
11
+ if (URL_LIKE.test(value))
12
+ files.push({ url: value });
13
+ else
14
+ texts.push(value);
15
+ }
16
+ else if (Array.isArray(value))
17
+ value.forEach(visit);
18
+ };
19
+ visit(output);
20
+ if (files.length > 0)
21
+ result.files = files;
22
+ if (texts.length > 0)
23
+ result.text = texts.join('');
24
+ if (output !== null && typeof output === 'object' && !Array.isArray(output))
25
+ result.data = output;
26
+ else if (files.length === 0 && texts.length === 0 && output !== undefined)
27
+ result.data = output;
28
+ return result;
29
+ }
30
+ export function createModelsClient(options) {
31
+ const { adapter } = options;
32
+ const first = options.pollIntervalMs ?? 500;
33
+ const max = options.maxPollIntervalMs ?? 5_000;
34
+ return {
35
+ async run(req) {
36
+ const timeout = AbortSignal.timeout(req.timeoutMs ?? options.defaultTimeoutMs ?? 120_000);
37
+ const signal = req.signal ? AbortSignal.any([req.signal, timeout]) : timeout;
38
+ try {
39
+ let prediction = await adapter.submit(req.model, req.input, signal);
40
+ let wait = first;
41
+ while (prediction.status === 'queued' || prediction.status === 'running') {
42
+ await sleep(wait, undefined, { signal });
43
+ wait = Math.min(wait * 2, max);
44
+ prediction = await adapter.poll(prediction.id, signal);
45
+ }
46
+ if (prediction.status === 'failed') {
47
+ throw new HubError('upstream_unavailable', 'The hosted model failed to produce a result');
48
+ }
49
+ return normaliseOutput(req.model, prediction.output);
50
+ }
51
+ catch (err) {
52
+ if (err instanceof HubError)
53
+ throw err;
54
+ if (timeout.aborted)
55
+ throw new HubError('timeout', 'The hosted model took too long');
56
+ if (req.signal?.aborted)
57
+ throw new HubError('timeout', 'The request was cancelled');
58
+ throw new HubError('upstream_unavailable', 'The hosted model is unavailable');
59
+ }
60
+ },
61
+ };
62
+ }
63
+ /** Fixture adapter for tests: each submit returns the next scripted sequence of poll states. */
64
+ export function scriptedAdapter(script) {
65
+ const submitted = [];
66
+ const queues = new Map();
67
+ let n = 0;
68
+ return {
69
+ name: 'scripted',
70
+ submitted,
71
+ async submit(model) {
72
+ submitted.push(model);
73
+ const steps = script[model];
74
+ if (!steps || steps.length === 0)
75
+ throw new HubError('upstream_unavailable', `No script for ${model}`);
76
+ const id = `p${++n}`;
77
+ queues.set(id, steps.map((s) => ({ ...s, id })));
78
+ return queues.get(id).shift();
79
+ },
80
+ async poll(id) {
81
+ const queue = queues.get(id);
82
+ const next = queue?.shift();
83
+ if (!next)
84
+ throw new HubError('upstream_unavailable', 'Script exhausted');
85
+ return next;
86
+ },
87
+ };
88
+ }
89
+ /**
90
+ * Hosted-model provider wiring. No provider is enabled until the owner records one in
91
+ * docs/CONVENTIONS.md; until then every call is `service_not_connected`.
92
+ */
93
+ // TODO(owner): pick a hosted-model provider (no training on inputs) and add its adapter here.
94
+ export function createModelsFromEnv(_env = process.env) {
95
+ return stubModels;
96
+ }
97
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/models/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { setTimeout as sleep } from 'node:timers/promises';\nimport { HubError, stubModels, type ModelOutput, type ModelRunRequest, type ModelsClient } from '@x12i/api-hub-core';\n\nexport type PredictionStatus = 'queued' | 'running' | 'succeeded' | 'failed';\n\nexport interface Prediction {\n id: string;\n status: PredictionStatus;\n output?: unknown;\n error?: string;\n}\n\n/** One hosted-model provider: submit work, then poll it. */\nexport interface ModelAdapter {\n readonly name: string;\n submit(model: string, input: Record<string, unknown>, signal?: AbortSignal): Promise<Prediction>;\n poll(id: string, signal?: AbortSignal): Promise<Prediction>;\n}\n\nconst URL_LIKE = /^https:\\/\\/\\S+$/;\n\n/** Maps provider output (strings, URL lists, objects) onto the hub's ModelOutput shape. */\nexport function normaliseOutput(model: string, output: unknown): ModelOutput {\n const result: ModelOutput = { model };\n const files: { url: string }[] = [];\n const texts: string[] = [];\n const visit = (value: unknown) => {\n if (typeof value === 'string') {\n if (URL_LIKE.test(value)) files.push({ url: value });\n else texts.push(value);\n } else if (Array.isArray(value)) value.forEach(visit);\n };\n visit(output);\n if (files.length > 0) result.files = files;\n if (texts.length > 0) result.text = texts.join('');\n if (output !== null && typeof output === 'object' && !Array.isArray(output)) result.data = output;\n else if (files.length === 0 && texts.length === 0 && output !== undefined) result.data = output;\n return result;\n}\n\nexport interface ModelsClientOptions {\n adapter: ModelAdapter;\n pollIntervalMs?: number;\n maxPollIntervalMs?: number;\n defaultTimeoutMs?: number;\n}\n\nexport function createModelsClient(options: ModelsClientOptions): ModelsClient {\n const { adapter } = options;\n const first = options.pollIntervalMs ?? 500;\n const max = options.maxPollIntervalMs ?? 5_000;\n\n return {\n async run(req: ModelRunRequest): Promise<ModelOutput> {\n const timeout = AbortSignal.timeout(req.timeoutMs ?? options.defaultTimeoutMs ?? 120_000);\n const signal = req.signal ? AbortSignal.any([req.signal, timeout]) : timeout;\n try {\n let prediction = await adapter.submit(req.model, req.input, signal);\n let wait = first;\n while (prediction.status === 'queued' || prediction.status === 'running') {\n await sleep(wait, undefined, { signal });\n wait = Math.min(wait * 2, max);\n prediction = await adapter.poll(prediction.id, signal);\n }\n if (prediction.status === 'failed') {\n throw new HubError('upstream_unavailable', 'The hosted model failed to produce a result');\n }\n return normaliseOutput(req.model, prediction.output);\n } catch (err) {\n if (err instanceof HubError) throw err;\n if (timeout.aborted) throw new HubError('timeout', 'The hosted model took too long');\n if (req.signal?.aborted) throw new HubError('timeout', 'The request was cancelled');\n throw new HubError('upstream_unavailable', 'The hosted model is unavailable');\n }\n },\n };\n}\n\n/** Fixture adapter for tests: each submit returns the next scripted sequence of poll states. */\nexport function scriptedAdapter(script: Record<string, Prediction[]>): ModelAdapter & { submitted: string[] } {\n const submitted: string[] = [];\n const queues = new Map<string, Prediction[]>();\n let n = 0;\n return {\n name: 'scripted',\n submitted,\n async submit(model) {\n submitted.push(model);\n const steps = script[model];\n if (!steps || steps.length === 0) throw new HubError('upstream_unavailable', `No script for ${model}`);\n const id = `p${++n}`;\n queues.set(id, steps.map((s) => ({ ...s, id })));\n return queues.get(id)!.shift()!;\n },\n async poll(id) {\n const queue = queues.get(id);\n const next = queue?.shift();\n if (!next) throw new HubError('upstream_unavailable', 'Script exhausted');\n return next;\n },\n };\n}\n\n/**\n * Hosted-model provider wiring. No provider is enabled until the owner records one in\n * docs/CONVENTIONS.md; until then every call is `service_not_connected`.\n */\n// TODO(owner): pick a hosted-model provider (no training on inputs) and add its adapter here.\nexport function createModelsFromEnv(_env: NodeJS.ProcessEnv = process.env): ModelsClient {\n return stubModels;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,36 @@
1
1
  {
2
2
  "name": "@x12i/api-hub-models",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Hosted-model submit and poll adapter for the x12i API hub",
5
+ "type": "module",
6
+ "engines": {
7
+ "node": ">=24"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/x12i/apis-hub.git",
24
+ "directory": "packages/models"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/x12i/apis-hub/issues"
28
+ },
29
+ "homepage": "https://github.com/x12i/apis-hub",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@x12i/api-hub-core": "0.1.1"
35
+ }
36
+ }