@arnilo/prism 0.0.96 → 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/CHANGELOG.md +290 -2
- package/README.md +17 -3
- package/dist/agent-definitions.js +2 -3
- package/dist/agent-event-source.d.ts +11 -0
- package/dist/agent-event-source.js +512 -0
- package/dist/agent-loops.d.ts +5 -0
- package/dist/agent-loops.js +99 -14
- package/dist/agent-run-lifecycle.d.ts +5 -2
- package/dist/agent-run-lifecycle.js +18 -2
- package/dist/agent-run-state.d.ts +27 -1
- package/dist/agent-run-state.js +113 -7
- package/dist/agents.d.ts +3 -1
- package/dist/agents.js +1255 -129
- package/dist/artifacts.d.ts +132 -0
- package/dist/artifacts.js +44 -0
- package/dist/cache-helpers.js +18 -9
- package/dist/checkpoints.d.ts +4 -0
- package/dist/checkpoints.js +17 -9
- package/dist/cli-init.js +3 -7
- package/dist/cli-runner.d.ts +2 -6
- package/dist/cli-runner.js +71 -33
- package/dist/compaction.js +5 -4
- package/dist/config.js +7 -4
- package/dist/content.js +26 -24
- package/dist/context-budget.d.ts +67 -0
- package/dist/context-budget.js +288 -0
- package/dist/contracts.d.ts +590 -8
- package/dist/contracts.js +142 -1
- package/dist/contribution-parsing.js +6 -2
- package/dist/contributions.d.ts +2 -0
- package/dist/contributions.js +3 -0
- package/dist/conversations.d.ts +50 -0
- package/dist/conversations.js +98 -0
- package/dist/credentials.d.ts +22 -2
- package/dist/credentials.js +18 -3
- package/dist/devices.d.ts +94 -0
- package/dist/devices.js +138 -0
- package/dist/event-multiplexer.js +18 -4
- package/dist/extensions.d.ts +18 -1
- package/dist/extensions.js +79 -6
- package/dist/feedback.js +12 -10
- package/dist/guardrails.d.ts +1 -1
- package/dist/guardrails.js +26 -17
- package/dist/identity.d.ts +92 -0
- package/dist/identity.js +265 -0
- package/dist/index.d.ts +94 -72
- package/dist/index.js +48 -36
- package/dist/input.d.ts +10 -1
- package/dist/input.js +152 -52
- package/dist/instruction-injection.d.ts +1 -1
- package/dist/middleware.js +9 -1
- package/dist/models.d.ts +2 -0
- package/dist/models.js +3 -0
- package/dist/node/agent-definitions.js +16 -8
- package/dist/node/contribution-discovery.d.ts +1 -2
- package/dist/node/contribution-discovery.js +3 -3
- package/dist/node/session-store-jsonl.js +13 -7
- package/dist/node/settings.d.ts +1 -1
- package/dist/node/settings.js +1 -1
- package/dist/node/system-project-prompts.js +2 -4
- package/dist/node/trust.js +1 -1
- package/dist/persistence-lifecycle.d.ts +103 -0
- package/dist/persistence-lifecycle.js +202 -0
- package/dist/provider-events.d.ts +1 -0
- package/dist/provider-events.js +6 -1
- package/dist/provider-request-policy.js +3 -4
- package/dist/providers/media.d.ts +1 -1
- package/dist/providers/openai-compatible.d.ts +46 -1
- package/dist/providers/openai-compatible.js +123 -53
- package/dist/providers/openai-primitives.js +10 -7
- package/dist/providers/transport.d.ts +6 -0
- package/dist/providers/transport.js +21 -0
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +3 -0
- package/dist/redaction.d.ts +1 -0
- package/dist/redaction.js +26 -9
- package/dist/resources.d.ts +2 -2
- package/dist/resources.js +2 -2
- package/dist/retry.d.ts +5 -0
- package/dist/retry.js +8 -1
- package/dist/rpc.js +55 -11
- package/dist/run-ledger.d.ts +6 -0
- package/dist/run-ledger.js +16 -13
- package/dist/run-limits.js +49 -10
- package/dist/secure-agent.js +8 -2
- package/dist/security.js +7 -2
- package/dist/session-stores.d.ts +7 -2
- package/dist/session-stores.js +195 -21
- package/dist/skill-disclosure.d.ts +35 -0
- package/dist/skill-disclosure.js +101 -0
- package/dist/skill-load.d.ts +25 -0
- package/dist/skill-load.js +112 -0
- package/dist/structured-output.d.ts +5 -1
- package/dist/structured-output.js +20 -2
- package/dist/system-prompts.js +7 -2
- package/dist/testing/agent-event-source-conformance.d.ts +4 -0
- package/dist/testing/agent-event-source-conformance.js +54 -0
- package/dist/testing/compaction-conformance.js +5 -1
- package/dist/testing/extension-conformance.js +15 -3
- package/dist/testing/feedback.d.ts +1 -3
- package/dist/testing/feedback.js +1 -1
- package/dist/testing/persistence-schema.d.ts +2 -2
- package/dist/testing/persistence-schema.js +280 -35
- package/dist/testing/provider-conformance.js +3 -3
- package/dist/testing/run-ledger-conformance.js +1 -1
- package/dist/testing/session-store-conformance.d.ts +6 -0
- package/dist/testing/session-store-conformance.js +37 -2
- package/dist/testing/tool-conformance.js +30 -5
- package/dist/testing/tool-effect-store-conformance.d.ts +9 -0
- package/dist/testing/tool-effect-store-conformance.js +85 -0
- package/dist/thinking.js +4 -1
- package/dist/tool-effects.d.ts +15 -0
- package/dist/tool-effects.js +352 -0
- package/dist/tool-result-fold.d.ts +40 -0
- package/dist/tool-result-fold.js +176 -0
- package/dist/tools.d.ts +8 -3
- package/dist/tools.js +248 -13
- package/docs/0.1.0-readiness.md +215 -0
- package/docs/a2a.md +33 -2
- package/docs/acp.md +152 -0
- package/docs/ag-ui-adoption.md +77 -0
- package/docs/ag-ui.md +225 -0
- package/docs/agent-events.md +34 -3
- package/docs/agent-identity.md +144 -0
- package/docs/agent-loops.md +17 -2
- package/docs/agent-session-runtime.md +21 -4
- package/docs/browser-automation.md +5 -0
- package/docs/caveman.md +129 -0
- package/docs/cli-rpc.md +3 -6
- package/docs/coding-agent-tools.md +229 -25
- package/docs/coding-security.md +77 -11
- package/docs/compaction-and-retry.md +5 -2
- package/docs/compaction-llm.md +20 -1
- package/docs/compaction-observational-memory.md +52 -8
- package/docs/context-and-skills.md +94 -7
- package/docs/contribution-registries.md +1 -0
- package/docs/conversations.md +135 -0
- package/docs/credential-storage.md +34 -1
- package/docs/credentials-and-redaction.md +11 -1
- package/docs/database-persistence.md +27 -7
- package/docs/device-adapters.md +97 -0
- package/docs/enterprise-postgres-state.md +178 -0
- package/docs/evaluations.md +14 -1
- package/docs/extensions.md +4 -1
- package/docs/forge-integration.md +113 -0
- package/docs/guardrails.md +16 -2
- package/docs/host-security.md +35 -4
- package/docs/index.md +69 -37
- package/docs/input-and-prompt-assembly.md +8 -7
- package/docs/language-intelligence.md +162 -0
- package/docs/mcp-tools.md +62 -5
- package/docs/middleware-hooks.md +2 -2
- package/docs/migration.md +427 -2
- package/docs/model-routing.md +111 -0
- package/docs/multimodal-content.md +8 -5
- package/docs/node-jsonl-session-store.md +1 -1
- package/docs/observability.md +2 -0
- package/docs/openapi-tools.md +56 -0
- package/docs/performance.md +282 -0
- package/docs/policy-and-audit.md +171 -0
- package/docs/ponytail.md +127 -0
- package/docs/postgres-persistence.md +8 -4
- package/docs/process-sessions.md +147 -0
- package/docs/provider-caching.md +13 -1
- package/docs/provider-conformance.md +29 -5
- package/docs/provider-packages.md +43 -2
- package/docs/provider-request-policies.md +2 -0
- package/docs/providers/ai-sdk.md +24 -7
- package/docs/providers/alibaba.md +179 -0
- package/docs/providers/anthropic.md +93 -0
- package/docs/providers/azure.md +74 -0
- package/docs/providers/bedrock.md +72 -0
- package/docs/providers/google.md +89 -0
- package/docs/providers/ollama.md +166 -0
- package/docs/providers/openai-compatible.md +31 -2
- package/docs/providers/openai.md +24 -5
- package/docs/providers/openrouter.md +2 -0
- package/docs/providers/vertex.md +71 -0
- package/docs/public-contracts.md +68 -4
- package/docs/rag.md +41 -12
- package/docs/release-and-install.md +362 -208
- package/docs/resource-loading.md +3 -0
- package/docs/runs-and-usage.md +3 -0
- package/docs/server.md +44 -6
- package/docs/session-store-conformance.md +2 -0
- package/docs/session-stores.md +41 -2
- package/docs/sqlite-persistence.md +11 -3
- package/docs/structured-output.md +7 -1
- package/docs/supervisors.md +8 -0
- package/docs/tool-effects.md +95 -0
- package/docs/tools.md +5 -0
- package/docs/work-artifacts-and-review.md +102 -0
- package/docs/work-connectors.md +32 -0
- package/docs/work-tools.md +137 -0
- package/docs/workflows.md +6 -0
- package/docs/working-and-semantic-memory.md +40 -7
- package/package.json +30 -7
- package/templates/init/providers.json +22 -0
- package/docs/review-coverage-2026-07-14.md +0 -260
- package/docs/review-coverage-2026-07-15.md +0 -193
- package/docs/review-coverage-2026-07-17-provider-validation.md +0 -192
- package/docs/review-coverage-2026-07-19-phase-3.md +0 -174
- package/docs/review-coverage-2026-07-20-phase-4.md +0 -175
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { OwnershipScope, PersistencePage, PersistenceQuery, RetentionPolicy } from "./contracts.js";
|
|
2
|
+
/** Resource classes covered by holds, quotas, and retention sweeps. */
|
|
3
|
+
export type PersistenceResourceKind = "session" | "entry" | "run" | "event" | "tool_call" | "usage" | "checkpoint" | "feedback" | "audit" | "work_artifact" | "connector_operation";
|
|
4
|
+
export declare const DEFAULT_LIFECYCLE_PAGE_SIZE = 100;
|
|
5
|
+
export declare const HARD_LIFECYCLE_PAGE_SIZE = 500;
|
|
6
|
+
export declare const DEFAULT_MAX_HOLD_REASON_BYTES = 1024;
|
|
7
|
+
export declare const HARD_MAX_HOLD_REASON_BYTES: number;
|
|
8
|
+
export declare class PersistenceLifecycleError extends Error {
|
|
9
|
+
readonly code: string;
|
|
10
|
+
constructor(message: string, code?: string);
|
|
11
|
+
}
|
|
12
|
+
export interface LegalHoldRecord extends OwnershipScope {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
15
|
+
readonly resourceId: string;
|
|
16
|
+
readonly reason: string;
|
|
17
|
+
readonly createdAt: string;
|
|
18
|
+
readonly createdBy?: string;
|
|
19
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
20
|
+
}
|
|
21
|
+
export interface PutLegalHoldInput extends OwnershipScope {
|
|
22
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
23
|
+
readonly resourceId: string;
|
|
24
|
+
readonly reason: string;
|
|
25
|
+
readonly id?: string;
|
|
26
|
+
readonly createdBy?: string;
|
|
27
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
28
|
+
readonly signal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
export interface ReleaseLegalHoldInput extends OwnershipScope {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly signal?: AbortSignal;
|
|
33
|
+
}
|
|
34
|
+
export interface LegalHoldQuery extends PersistenceQuery, OwnershipScope {
|
|
35
|
+
readonly resourceKind?: PersistenceResourceKind;
|
|
36
|
+
readonly resourceId?: string;
|
|
37
|
+
readonly holdId?: string;
|
|
38
|
+
readonly signal?: AbortSignal;
|
|
39
|
+
}
|
|
40
|
+
export interface ApplyRetentionInput extends OwnershipScope {
|
|
41
|
+
readonly policy: RetentionPolicy;
|
|
42
|
+
/** When omitted, adapters discover expired/over-limit session ids for this ownership page. */
|
|
43
|
+
readonly candidates?: readonly string[];
|
|
44
|
+
readonly cursor?: string;
|
|
45
|
+
readonly limit?: number;
|
|
46
|
+
readonly signal?: AbortSignal;
|
|
47
|
+
}
|
|
48
|
+
export interface ApplyRetentionResult {
|
|
49
|
+
readonly deleted: readonly string[];
|
|
50
|
+
readonly skippedHeld: readonly string[];
|
|
51
|
+
readonly nextCursor?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface LegalHoldExportItem {
|
|
54
|
+
readonly holdId: string;
|
|
55
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
56
|
+
readonly resourceId: string;
|
|
57
|
+
readonly reason: string;
|
|
58
|
+
readonly createdAt: string;
|
|
59
|
+
readonly redacted: true;
|
|
60
|
+
}
|
|
61
|
+
export interface ExportUnderHoldInput extends OwnershipScope {
|
|
62
|
+
readonly holdId?: string;
|
|
63
|
+
readonly resourceKind?: PersistenceResourceKind;
|
|
64
|
+
readonly cursor?: string;
|
|
65
|
+
readonly limit?: number;
|
|
66
|
+
readonly signal?: AbortSignal;
|
|
67
|
+
}
|
|
68
|
+
export interface TenantQuota extends OwnershipScope {
|
|
69
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
70
|
+
readonly limit: number;
|
|
71
|
+
readonly used: number;
|
|
72
|
+
readonly updatedAt: string;
|
|
73
|
+
}
|
|
74
|
+
export interface SetTenantQuotaInput extends OwnershipScope {
|
|
75
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
76
|
+
readonly limit: number;
|
|
77
|
+
readonly signal?: AbortSignal;
|
|
78
|
+
}
|
|
79
|
+
export interface ConsumeTenantQuotaInput extends OwnershipScope {
|
|
80
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
81
|
+
/** Units to consume. Default 1. */
|
|
82
|
+
readonly delta?: number;
|
|
83
|
+
readonly signal?: AbortSignal;
|
|
84
|
+
}
|
|
85
|
+
/** Optional persistence lifecycle capability (retention / hold / export / quota). */
|
|
86
|
+
export interface PersistenceLifecycleStore {
|
|
87
|
+
putLegalHold(input: PutLegalHoldInput): Promise<LegalHoldRecord>;
|
|
88
|
+
releaseLegalHold(input: ReleaseLegalHoldInput): Promise<boolean>;
|
|
89
|
+
listLegalHolds(query: LegalHoldQuery): Promise<PersistencePage<LegalHoldRecord>>;
|
|
90
|
+
/** Deletes candidates not under legal hold. Hold always wins over retention. */
|
|
91
|
+
applyRetention(input: ApplyRetentionInput): Promise<ApplyRetentionResult>;
|
|
92
|
+
exportUnderHold(input: ExportUnderHoldInput): Promise<PersistencePage<LegalHoldExportItem>>;
|
|
93
|
+
setTenantQuota(input: SetTenantQuotaInput): Promise<TenantQuota>;
|
|
94
|
+
getTenantQuota(input: OwnershipScope & {
|
|
95
|
+
readonly resourceKind: PersistenceResourceKind;
|
|
96
|
+
readonly signal?: AbortSignal;
|
|
97
|
+
}): Promise<TenantQuota | null>;
|
|
98
|
+
/** Fails closed when used + delta would exceed limit. */
|
|
99
|
+
consumeTenantQuota(input: ConsumeTenantQuotaInput): Promise<TenantQuota>;
|
|
100
|
+
}
|
|
101
|
+
export declare function createMemoryPersistenceLifecycle(): PersistenceLifecycleStore;
|
|
102
|
+
/** True when a session/resource id is under an active legal hold in the page of holds. */
|
|
103
|
+
export declare function isResourceHeld(holds: readonly Pick<LegalHoldRecord, "resourceKind" | "resourceId">[], resourceKind: PersistenceResourceKind, resourceId: string): boolean;
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
export const DEFAULT_LIFECYCLE_PAGE_SIZE = 100;
|
|
2
|
+
export const HARD_LIFECYCLE_PAGE_SIZE = 500;
|
|
3
|
+
export const DEFAULT_MAX_HOLD_REASON_BYTES = 1024;
|
|
4
|
+
export const HARD_MAX_HOLD_REASON_BYTES = 8 * 1024;
|
|
5
|
+
export class PersistenceLifecycleError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(message, code = "ERR_PRISM_PERSISTENCE_LIFECYCLE") {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.name = "PersistenceLifecycleError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function createMemoryPersistenceLifecycle() {
|
|
14
|
+
const holds = new Map();
|
|
15
|
+
const quotas = new Map();
|
|
16
|
+
const deleted = new Set();
|
|
17
|
+
return {
|
|
18
|
+
async putLegalHold(input) {
|
|
19
|
+
throwIfAborted(input.signal);
|
|
20
|
+
assertOwnership(input);
|
|
21
|
+
const reason = assertReason(input.reason);
|
|
22
|
+
if (!input.resourceId || !input.resourceKind) {
|
|
23
|
+
throw new PersistenceLifecycleError("resourceKind and resourceId are required", "ERR_PRISM_LIFECYCLE_HOLD");
|
|
24
|
+
}
|
|
25
|
+
const id = input.id ?? crypto.randomUUID();
|
|
26
|
+
const record = {
|
|
27
|
+
id,
|
|
28
|
+
resourceKind: input.resourceKind,
|
|
29
|
+
resourceId: input.resourceId,
|
|
30
|
+
reason,
|
|
31
|
+
createdAt: new Date().toISOString(),
|
|
32
|
+
...(input.createdBy === undefined ? {} : { createdBy: input.createdBy }),
|
|
33
|
+
...(input.metadata === undefined ? {} : { metadata: input.metadata }),
|
|
34
|
+
...ownership(input),
|
|
35
|
+
};
|
|
36
|
+
holds.set(id, record);
|
|
37
|
+
return record;
|
|
38
|
+
},
|
|
39
|
+
async releaseLegalHold(input) {
|
|
40
|
+
throwIfAborted(input.signal);
|
|
41
|
+
assertOwnership(input);
|
|
42
|
+
const current = holds.get(input.id);
|
|
43
|
+
if (!current)
|
|
44
|
+
return false;
|
|
45
|
+
assertSameOwnership(input, current);
|
|
46
|
+
holds.delete(input.id);
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
async listLegalHolds(query) {
|
|
50
|
+
throwIfAborted(query.signal);
|
|
51
|
+
assertOwnership(query);
|
|
52
|
+
const limit = pageLimit(query.limit);
|
|
53
|
+
const items = [...holds.values()]
|
|
54
|
+
.filter((hold) => sameOwnership(query, hold))
|
|
55
|
+
.filter((hold) => query.holdId === undefined || hold.id === query.holdId)
|
|
56
|
+
.filter((hold) => query.resourceKind === undefined || hold.resourceKind === query.resourceKind)
|
|
57
|
+
.filter((hold) => query.resourceId === undefined || hold.resourceId === query.resourceId)
|
|
58
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
59
|
+
const start = query.cursor ? items.findIndex((item) => item.id === query.cursor) + 1 : 0;
|
|
60
|
+
const slice = items.slice(Math.max(0, start), Math.max(0, start) + limit);
|
|
61
|
+
const next = start + limit < items.length ? slice.at(-1)?.id : undefined;
|
|
62
|
+
return { items: slice, ...(next === undefined ? {} : { nextCursor: next }) };
|
|
63
|
+
},
|
|
64
|
+
async applyRetention(input) {
|
|
65
|
+
throwIfAborted(input.signal);
|
|
66
|
+
assertOwnership(input);
|
|
67
|
+
const held = new Set([...holds.values()].filter((hold) => sameOwnership(input, hold) && hold.resourceKind === "session").map((hold) => hold.resourceId));
|
|
68
|
+
const candidates = input.candidates ?? [];
|
|
69
|
+
const deletedIds = [];
|
|
70
|
+
const skippedHeld = [];
|
|
71
|
+
for (const id of candidates) {
|
|
72
|
+
if (held.has(id)) {
|
|
73
|
+
skippedHeld.push(id);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
deleted.add(id);
|
|
77
|
+
deletedIds.push(id);
|
|
78
|
+
}
|
|
79
|
+
return { deleted: deletedIds, skippedHeld };
|
|
80
|
+
},
|
|
81
|
+
async exportUnderHold(input) {
|
|
82
|
+
throwIfAborted(input.signal);
|
|
83
|
+
assertOwnership(input);
|
|
84
|
+
const page = await this.listLegalHolds({
|
|
85
|
+
...ownership(input),
|
|
86
|
+
holdId: input.holdId,
|
|
87
|
+
resourceKind: input.resourceKind,
|
|
88
|
+
cursor: input.cursor,
|
|
89
|
+
limit: input.limit,
|
|
90
|
+
signal: input.signal,
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
items: page.items.map((hold) => ({
|
|
94
|
+
holdId: hold.id,
|
|
95
|
+
resourceKind: hold.resourceKind,
|
|
96
|
+
resourceId: hold.resourceId,
|
|
97
|
+
reason: hold.reason,
|
|
98
|
+
createdAt: hold.createdAt,
|
|
99
|
+
redacted: true,
|
|
100
|
+
})),
|
|
101
|
+
...(page.nextCursor === undefined ? {} : { nextCursor: page.nextCursor }),
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
async setTenantQuota(input) {
|
|
105
|
+
throwIfAborted(input.signal);
|
|
106
|
+
assertOwnership(input);
|
|
107
|
+
if (!Number.isSafeInteger(input.limit) || input.limit < 0) {
|
|
108
|
+
throw new PersistenceLifecycleError("limit must be a non-negative safe integer", "ERR_PRISM_LIFECYCLE_QUOTA");
|
|
109
|
+
}
|
|
110
|
+
const key = quotaKey(input, input.resourceKind);
|
|
111
|
+
const previous = quotas.get(key);
|
|
112
|
+
const record = {
|
|
113
|
+
...ownership(input),
|
|
114
|
+
resourceKind: input.resourceKind,
|
|
115
|
+
limit: input.limit,
|
|
116
|
+
used: previous?.used ?? 0,
|
|
117
|
+
updatedAt: new Date().toISOString(),
|
|
118
|
+
};
|
|
119
|
+
if (record.used > record.limit) {
|
|
120
|
+
throw new PersistenceLifecycleError("quota already exceeded", "ERR_PRISM_LIFECYCLE_QUOTA");
|
|
121
|
+
}
|
|
122
|
+
quotas.set(key, record);
|
|
123
|
+
return record;
|
|
124
|
+
},
|
|
125
|
+
async getTenantQuota(input) {
|
|
126
|
+
throwIfAborted(input.signal);
|
|
127
|
+
assertOwnership(input);
|
|
128
|
+
return quotas.get(quotaKey(input, input.resourceKind)) ?? null;
|
|
129
|
+
},
|
|
130
|
+
async consumeTenantQuota(input) {
|
|
131
|
+
throwIfAborted(input.signal);
|
|
132
|
+
assertOwnership(input);
|
|
133
|
+
const delta = input.delta ?? 1;
|
|
134
|
+
if (!Number.isSafeInteger(delta) || delta < 1) {
|
|
135
|
+
throw new PersistenceLifecycleError("delta must be a positive safe integer", "ERR_PRISM_LIFECYCLE_QUOTA");
|
|
136
|
+
}
|
|
137
|
+
const key = quotaKey(input, input.resourceKind);
|
|
138
|
+
const current = quotas.get(key);
|
|
139
|
+
if (!current) {
|
|
140
|
+
throw new PersistenceLifecycleError("tenant quota not configured", "ERR_PRISM_LIFECYCLE_QUOTA");
|
|
141
|
+
}
|
|
142
|
+
if (current.used + delta > current.limit) {
|
|
143
|
+
throw new PersistenceLifecycleError(`tenant quota exhausted for ${input.resourceKind}`, "ERR_PRISM_LIFECYCLE_QUOTA_EXHAUSTED");
|
|
144
|
+
}
|
|
145
|
+
const next = {
|
|
146
|
+
...current,
|
|
147
|
+
used: current.used + delta,
|
|
148
|
+
updatedAt: new Date().toISOString(),
|
|
149
|
+
};
|
|
150
|
+
quotas.set(key, next);
|
|
151
|
+
return next;
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** True when a session/resource id is under an active legal hold in the page of holds. */
|
|
156
|
+
export function isResourceHeld(holds, resourceKind, resourceId) {
|
|
157
|
+
return holds.some((hold) => hold.resourceKind === resourceKind && hold.resourceId === resourceId);
|
|
158
|
+
}
|
|
159
|
+
function ownership(input) {
|
|
160
|
+
return {
|
|
161
|
+
...(input.tenantId === undefined ? {} : { tenantId: input.tenantId }),
|
|
162
|
+
...(input.accountId === undefined ? {} : { accountId: input.accountId }),
|
|
163
|
+
...(input.userId === undefined ? {} : { userId: input.userId }),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
function sameOwnership(a, b) {
|
|
167
|
+
return a.tenantId === b.tenantId && a.accountId === b.accountId && a.userId === b.userId;
|
|
168
|
+
}
|
|
169
|
+
function assertSameOwnership(expected, actual) {
|
|
170
|
+
if (!sameOwnership(expected, actual)) {
|
|
171
|
+
throw new PersistenceLifecycleError("ownership mismatch", "ERR_PRISM_LIFECYCLE_OWNERSHIP");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function assertOwnership(input) {
|
|
175
|
+
if (![input.tenantId, input.accountId, input.userId].some((value) => typeof value === "string" && value.length > 0)) {
|
|
176
|
+
throw new PersistenceLifecycleError("ownership required", "ERR_PRISM_LIFECYCLE_OWNERSHIP");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function assertReason(reason) {
|
|
180
|
+
if (typeof reason !== "string" || !reason.trim()) {
|
|
181
|
+
throw new PersistenceLifecycleError("reason is required", "ERR_PRISM_LIFECYCLE_HOLD");
|
|
182
|
+
}
|
|
183
|
+
if (Buffer.byteLength(reason, "utf8") > HARD_MAX_HOLD_REASON_BYTES) {
|
|
184
|
+
throw new PersistenceLifecycleError("reason exceeds limit", "ERR_PRISM_LIFECYCLE_HOLD");
|
|
185
|
+
}
|
|
186
|
+
return reason;
|
|
187
|
+
}
|
|
188
|
+
function pageLimit(limit) {
|
|
189
|
+
const resolved = limit ?? DEFAULT_LIFECYCLE_PAGE_SIZE;
|
|
190
|
+
if (!Number.isSafeInteger(resolved) || resolved < 1 || resolved > HARD_LIFECYCLE_PAGE_SIZE) {
|
|
191
|
+
throw new PersistenceLifecycleError(`limit must be 1..${HARD_LIFECYCLE_PAGE_SIZE}`, "ERR_PRISM_LIFECYCLE_LIMITS");
|
|
192
|
+
}
|
|
193
|
+
return resolved;
|
|
194
|
+
}
|
|
195
|
+
function quotaKey(ownership, kind) {
|
|
196
|
+
return `${ownership.tenantId ?? ""}\0${ownership.accountId ?? ""}\0${ownership.userId ?? ""}\0${kind}`;
|
|
197
|
+
}
|
|
198
|
+
function throwIfAborted(signal) {
|
|
199
|
+
if (signal?.aborted)
|
|
200
|
+
throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=persistence-lifecycle.js.map
|
|
@@ -10,6 +10,7 @@ export declare function providerToolCallDelta(delta: {
|
|
|
10
10
|
readonly argumentsText?: string;
|
|
11
11
|
}): ProviderEvent;
|
|
12
12
|
export declare function providerToolCallDeltaContent(delta: Omit<ToolCallDeltaContent, "type">): ToolCallDeltaContent;
|
|
13
|
+
export declare function providerContinuationRequired(cursor: string, reason?: string): ProviderEvent;
|
|
13
14
|
export declare function reconstructToolCallDeltas(events: readonly ProviderEvent[]): readonly ToolCallContent[];
|
|
14
15
|
export declare function providerUsage(usage: Usage): ProviderEvent;
|
|
15
16
|
export declare function providerDone(usage?: Usage): ProviderEvent;
|
package/dist/provider-events.js
CHANGED
|
@@ -18,6 +18,9 @@ export function providerToolCallDelta(delta) {
|
|
|
18
18
|
export function providerToolCallDeltaContent(delta) {
|
|
19
19
|
return { type: "tool_call_delta", ...delta };
|
|
20
20
|
}
|
|
21
|
+
export function providerContinuationRequired(cursor, reason) {
|
|
22
|
+
return { type: "continuation_required", cursor, reason };
|
|
23
|
+
}
|
|
21
24
|
export function reconstructToolCallDeltas(events) {
|
|
22
25
|
const partials = new Map();
|
|
23
26
|
for (const event of events) {
|
|
@@ -32,7 +35,9 @@ export function reconstructToolCallDeltas(events) {
|
|
|
32
35
|
partial.argumentsText += event.argumentsText;
|
|
33
36
|
partials.set(event.index, partial);
|
|
34
37
|
}
|
|
35
|
-
return [...partials.entries()]
|
|
38
|
+
return [...partials.entries()]
|
|
39
|
+
.sort(([a], [b]) => a - b)
|
|
40
|
+
.map(([index, partial]) => {
|
|
36
41
|
if (!partial.id || !partial.name) {
|
|
37
42
|
throw new ProviderTransportError("incomplete_delta", `Incomplete tool call delta at index ${index}`);
|
|
38
43
|
}
|
|
@@ -50,10 +50,9 @@ export function mergeProviderRequestOptions(base, patch) {
|
|
|
50
50
|
cache: {
|
|
51
51
|
...base.cache,
|
|
52
52
|
...patch.cache,
|
|
53
|
-
breakpoints: base.cache?.breakpoints || patch.cache?.breakpoints
|
|
54
|
-
...(base.cache?.breakpoints ?? []),
|
|
55
|
-
|
|
56
|
-
] : undefined,
|
|
53
|
+
breakpoints: base.cache?.breakpoints || patch.cache?.breakpoints
|
|
54
|
+
? [...(base.cache?.breakpoints ?? []), ...(patch.cache?.breakpoints ?? [])]
|
|
55
|
+
: undefined,
|
|
57
56
|
},
|
|
58
57
|
};
|
|
59
58
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { type MediaContentBlock, type ModelInputCapability, type ResolvedMediaContent, type ResolveMediaContentOptions } from "../content.js";
|
|
1
2
|
import type { AudioContent, ContentBlock, DocumentContent, FileContent, JsonObject, Message, ModelCapabilities, ModelConfig } from "../contracts.js";
|
|
2
|
-
import { type MediaContentBlock, type ModelInputCapability, type ResolveMediaContentOptions, type ResolvedMediaContent } from "../content.js";
|
|
3
3
|
/** Default upload-cache entry cap per provider media session. */
|
|
4
4
|
export declare const DEFAULT_PROVIDER_UPLOAD_CACHE_ENTRIES = 32;
|
|
5
5
|
/** Inline OpenAI file_data ceiling before preferring Files API upload. */
|
|
@@ -1,9 +1,54 @@
|
|
|
1
|
-
import type { AIProvider } from "../contracts.js";
|
|
1
|
+
import type { AIProvider, JsonObject, Message, ProviderEvent, ProviderRequest, Usage } from "../contracts.js";
|
|
2
2
|
import { type CredentialValueSource } from "../credentials.js";
|
|
3
3
|
export interface OpenAICompatibleProviderOptions {
|
|
4
4
|
readonly id?: string;
|
|
5
5
|
readonly baseUrl: string;
|
|
6
6
|
readonly apiKey?: CredentialValueSource;
|
|
7
7
|
readonly fetch?: typeof fetch;
|
|
8
|
+
/** Override chat-completions URL (default `${baseUrl}/chat/completions`). */
|
|
9
|
+
readonly chatCompletionsUrl?: string | ((request: ProviderRequest) => string);
|
|
10
|
+
/** Default `bearer`. Azure resource keys use `api-key`; host-signed fetches may use `none`. */
|
|
11
|
+
readonly authStyle?: "bearer" | "api-key" | "none";
|
|
12
|
+
/** Extra provider-specific body fields (thinking/reasoning/cache); merged over the base body. */
|
|
13
|
+
readonly buildBodyExtra?: (request: ProviderRequest) => JsonObject | undefined;
|
|
14
|
+
/** Transform messages before serialization (e.g. cache-control markers). Defaults to `request.messages`. */
|
|
15
|
+
readonly mapMessages?: (request: ProviderRequest) => readonly Message[];
|
|
16
|
+
/** Custom message serializer (e.g. Z.AI `reasoning_content` replay). Defaults to assert + `serializeOpenAIChatMessage`. */
|
|
17
|
+
readonly serializeMessage?: (message: Message, request: ProviderRequest) => JsonObject;
|
|
18
|
+
/** Custom usage mapping (e.g. OpenRouter cost fields). Defaults to `mapOpenAIChatUsage`. */
|
|
19
|
+
readonly mapUsage?: (usage: unknown) => Usage | undefined;
|
|
20
|
+
/** Extra request headers (merged over caller headers; provider auth/content-type still win). */
|
|
21
|
+
readonly extraHeaders?: (request: ProviderRequest) => Record<string, string>;
|
|
22
|
+
/** Final body transform applied last (token limits, compat stripping). Wins over everything. */
|
|
23
|
+
readonly transformBody?: (body: JsonObject, request: ProviderRequest) => JsonObject;
|
|
24
|
+
/** Require `[DONE]` and a `finish_reason` before emitting `done`; truncated streams yield an error. `done` then carries the final usage. */
|
|
25
|
+
readonly strictCompletion?: boolean;
|
|
26
|
+
/** Emit the final stream usage on the `done` event (without strict completion checks). */
|
|
27
|
+
readonly doneUsage?: boolean;
|
|
28
|
+
/** Prefix for HTTP error messages (default `OpenAI-compatible request failed`). */
|
|
29
|
+
readonly requestFailedPrefix?: string;
|
|
30
|
+
/** Custom HTTP error mapping (e.g. NeuralWatt retry classification). Receives the response and redacted body text. */
|
|
31
|
+
readonly mapHttpError?: (response: Response, bodyText: string, secrets: readonly (string | undefined)[]) => Error;
|
|
32
|
+
/** Handle SSE comment lines in the stream (e.g. NeuralWatt energy/cost telemetry). */
|
|
33
|
+
readonly onComment?: (text: string) => ProviderEvent | undefined;
|
|
8
34
|
}
|
|
35
|
+
export interface OpenAIChatEventsOptions {
|
|
36
|
+
readonly signal?: AbortSignal;
|
|
37
|
+
/** Require `[DONE]` and a `finish_reason`; `done` then carries the final usage. */
|
|
38
|
+
readonly strictCompletion?: boolean;
|
|
39
|
+
/** Emit the final stream usage on the `done` event. */
|
|
40
|
+
readonly doneUsage?: boolean;
|
|
41
|
+
readonly mapUsage?: (usage: unknown) => Usage | undefined;
|
|
42
|
+
/** Handle an SSE comment line (text after `:`), e.g. NeuralWatt `: energy` / `: cost` telemetry. Returned events are yielded in stream order before the data of the same SSE event. */
|
|
43
|
+
readonly onComment?: (text: string) => ProviderEvent | undefined;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Shared OpenAI Chat Completions SSE stream loop: maps `data:` frames to Prism
|
|
47
|
+
* `ProviderEvent` values (text/thinking deltas, tool-call fragments, usage, done/error).
|
|
48
|
+
*/
|
|
49
|
+
export declare function openAIChatEvents(body: ReadableStream<Uint8Array>, options?: OpenAIChatEventsOptions): AsyncIterable<ProviderEvent>;
|
|
9
50
|
export declare function createOpenAICompatibleProvider(options: OpenAICompatibleProviderOptions): AIProvider;
|
|
51
|
+
/** Subset of factory options that shape the request body. */
|
|
52
|
+
export type OpenAIChatBodyOptions = Pick<OpenAICompatibleProviderOptions, "mapMessages" | "serializeMessage" | "buildBodyExtra" | "transformBody">;
|
|
53
|
+
/** Base Chat Completions request body builder, exported for provider packages keeping public body helpers. */
|
|
54
|
+
export declare function buildOpenAIChatBody(request: ProviderRequest, options?: OpenAIChatBodyOptions): JsonObject;
|
|
@@ -1,78 +1,141 @@
|
|
|
1
1
|
import { resolveCredentialValue } from "../credentials.js";
|
|
2
2
|
import { providerDone, providerError, providerTextDelta, providerThinkingDelta, providerToolCall, providerToolCallDelta, providerUsage, toolCallFromArgumentsText, } from "../provider-events.js";
|
|
3
|
-
import { assertOpenAIChatMessage, applyOpenAIChatStructuredOutput, mapOpenAIChatUsage, serializeOpenAIChatMessage, serializeOpenAITool, } from "./openai-primitives.js";
|
|
4
|
-
import { ProviderTransportError, readBoundedResponseText, readSseData, } from "./transport.js";
|
|
5
3
|
import { assertStructuredOutputRequestSupported } from "../structured-output.js";
|
|
4
|
+
import { applyOpenAIChatStructuredOutput, assertOpenAIChatMessage, mapOpenAIChatUsage, serializeOpenAIChatMessage, serializeOpenAITool, } from "./openai-primitives.js";
|
|
5
|
+
import { httpStatusError, ProviderTransportError, readBoundedResponseText, readSseEvents } from "./transport.js";
|
|
6
|
+
/**
|
|
7
|
+
* Shared OpenAI Chat Completions SSE stream loop: maps `data:` frames to Prism
|
|
8
|
+
* `ProviderEvent` values (text/thinking deltas, tool-call fragments, usage, done/error).
|
|
9
|
+
*/
|
|
10
|
+
export async function* openAIChatEvents(body, options = {}) {
|
|
11
|
+
const tools = new Map();
|
|
12
|
+
let usage;
|
|
13
|
+
let sawDoneMarker = false;
|
|
14
|
+
let sawFinishReason = false;
|
|
15
|
+
for await (const sseEvent of readSseEvents(body, { signal: options.signal })) {
|
|
16
|
+
if (options.onComment && sseEvent.comments?.length) {
|
|
17
|
+
for (const text of sseEvent.comments) {
|
|
18
|
+
const commentEvent = options.onComment(text);
|
|
19
|
+
if (commentEvent)
|
|
20
|
+
yield commentEvent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const data = sseEvent.data.trim();
|
|
24
|
+
if (!data)
|
|
25
|
+
continue;
|
|
26
|
+
if (data === "[DONE]") {
|
|
27
|
+
sawDoneMarker = true;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(data);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// Malformed chunks are terminal: yield the error instead of crashing the generator.
|
|
36
|
+
yield providerError(error, []);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const mapped = (options.mapUsage ?? mapOpenAIChatUsage)(parsed.usage);
|
|
40
|
+
if (mapped) {
|
|
41
|
+
usage = mapped;
|
|
42
|
+
yield providerUsage(mapped);
|
|
43
|
+
}
|
|
44
|
+
for (const choice of parsed.choices ?? []) {
|
|
45
|
+
if (choice.finish_reason)
|
|
46
|
+
sawFinishReason = true;
|
|
47
|
+
const delta = choice.delta ?? {};
|
|
48
|
+
if (typeof delta.content === "string" && delta.content)
|
|
49
|
+
yield providerTextDelta(delta.content);
|
|
50
|
+
const thinking = delta.reasoning ?? delta.reasoning_content;
|
|
51
|
+
if (typeof thinking === "string" && thinking) {
|
|
52
|
+
yield providerThinkingDelta(thinking);
|
|
53
|
+
}
|
|
54
|
+
for (const tool of delta.tool_calls ?? []) {
|
|
55
|
+
const index = tool.index ?? 0;
|
|
56
|
+
const current = tools.get(index) ?? { argumentsText: "" };
|
|
57
|
+
current.id = tool.id ?? current.id;
|
|
58
|
+
current.name = tool.function?.name ?? current.name;
|
|
59
|
+
current.argumentsText += tool.function?.arguments ?? "";
|
|
60
|
+
tools.set(index, current);
|
|
61
|
+
yield providerToolCallDelta({
|
|
62
|
+
index,
|
|
63
|
+
id: tool.id,
|
|
64
|
+
name: tool.function?.name,
|
|
65
|
+
argumentsText: tool.function?.arguments,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const incomplete = [...tools.entries()].find(([, call]) => !call.id || !call.name);
|
|
71
|
+
if (incomplete) {
|
|
72
|
+
yield providerError(new ProviderTransportError("incomplete_delta", `Incomplete tool call delta at index ${incomplete[0]}`));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (options.strictCompletion && (!sawDoneMarker || !sawFinishReason)) {
|
|
76
|
+
// Truncated streams must fail loudly — emitting done would mark partial output as succeeded.
|
|
77
|
+
yield providerError(new Error(`Chat stream ended without completion evidence ` +
|
|
78
|
+
`([DONE]: ${sawDoneMarker ? "received" : "missing"}, ` +
|
|
79
|
+
`finish_reason: ${sawFinishReason ? "received" : "missing"})`));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
for (const call of tools.values()) {
|
|
83
|
+
yield providerToolCall(toolCallFromArgumentsText(call.id, call.name, call.argumentsText));
|
|
84
|
+
}
|
|
85
|
+
yield providerDone(options.strictCompletion || options.doneUsage ? usage : undefined);
|
|
86
|
+
}
|
|
6
87
|
export function createOpenAICompatibleProvider(options) {
|
|
7
88
|
const providerId = options.id ?? "openai-compatible";
|
|
8
89
|
return {
|
|
9
90
|
id: providerId,
|
|
10
91
|
async *generate(request) {
|
|
92
|
+
if (request.signal?.aborted)
|
|
93
|
+
throw request.signal.reason ?? new Error("aborted");
|
|
11
94
|
const apiKey = await resolveCredentialValue(options.apiKey, {
|
|
12
95
|
name: "apiKey",
|
|
13
96
|
provider: providerId,
|
|
14
97
|
});
|
|
15
98
|
const fetchImpl = options.fetch ?? fetch;
|
|
16
99
|
const secrets = [apiKey];
|
|
17
|
-
const tools = new Map();
|
|
18
100
|
try {
|
|
19
|
-
const
|
|
101
|
+
const url = typeof options.chatCompletionsUrl === "function"
|
|
102
|
+
? options.chatCompletionsUrl(request)
|
|
103
|
+
: (options.chatCompletionsUrl ?? `${options.baseUrl.replace(/\/+$/, "")}/chat/completions`);
|
|
104
|
+
const authStyle = options.authStyle ?? "bearer";
|
|
105
|
+
const headers = {
|
|
106
|
+
...Object.fromEntries(Object.entries(request.options?.headers ?? {}).filter((entry) => typeof entry[1] === "string")),
|
|
107
|
+
...options.extraHeaders?.(request),
|
|
108
|
+
"content-type": "application/json",
|
|
109
|
+
};
|
|
110
|
+
if (apiKey && authStyle === "api-key")
|
|
111
|
+
headers["api-key"] = apiKey;
|
|
112
|
+
if (apiKey && authStyle === "bearer")
|
|
113
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
114
|
+
const response = await fetchImpl(url, {
|
|
20
115
|
method: "POST",
|
|
21
|
-
headers
|
|
22
|
-
|
|
23
|
-
"content-type": "application/json",
|
|
24
|
-
...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),
|
|
25
|
-
},
|
|
26
|
-
body: JSON.stringify(toOpenAIRequest(request)),
|
|
116
|
+
headers,
|
|
117
|
+
body: JSON.stringify(toOpenAIRequest(request, options)),
|
|
27
118
|
signal: request.signal,
|
|
28
119
|
});
|
|
29
120
|
if (!response.ok) {
|
|
30
|
-
|
|
121
|
+
const bodyText = await readBoundedResponseText(response, { secrets });
|
|
122
|
+
const error = options.mapHttpError
|
|
123
|
+
? options.mapHttpError(response, bodyText, secrets)
|
|
124
|
+
: httpStatusError(options.requestFailedPrefix ?? "OpenAI-compatible request failed", response, bodyText);
|
|
125
|
+
yield providerError(error, secrets);
|
|
31
126
|
return;
|
|
32
127
|
}
|
|
33
128
|
if (!response.body) {
|
|
34
129
|
yield providerError(new Error("OpenAI-compatible response had no body"), secrets);
|
|
35
130
|
return;
|
|
36
131
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for (const choice of parsed.choices ?? []) {
|
|
45
|
-
const delta = choice.delta ?? {};
|
|
46
|
-
if (typeof delta.content === "string" && delta.content)
|
|
47
|
-
yield providerTextDelta(delta.content);
|
|
48
|
-
if (typeof delta.reasoning_content === "string" && delta.reasoning_content) {
|
|
49
|
-
yield providerThinkingDelta(delta.reasoning_content);
|
|
50
|
-
}
|
|
51
|
-
for (const tool of delta.tool_calls ?? []) {
|
|
52
|
-
const index = tool.index ?? 0;
|
|
53
|
-
const current = tools.get(index) ?? { argumentsText: "" };
|
|
54
|
-
current.id = tool.id ?? current.id;
|
|
55
|
-
current.name = tool.function?.name ?? current.name;
|
|
56
|
-
current.argumentsText += tool.function?.arguments ?? "";
|
|
57
|
-
tools.set(index, current);
|
|
58
|
-
yield providerToolCallDelta({
|
|
59
|
-
index,
|
|
60
|
-
id: tool.id,
|
|
61
|
-
name: tool.function?.name,
|
|
62
|
-
argumentsText: tool.function?.arguments,
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const incomplete = [...tools.entries()].find(([, call]) => !call.id || !call.name);
|
|
68
|
-
if (incomplete) {
|
|
69
|
-
yield providerError(new ProviderTransportError("incomplete_delta", `Incomplete tool call delta at index ${incomplete[0]}`), secrets);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
for (const call of tools.values()) {
|
|
73
|
-
yield providerToolCall(toolCallFromArgumentsText(call.id, call.name, call.argumentsText));
|
|
74
|
-
}
|
|
75
|
-
yield providerDone();
|
|
132
|
+
yield* openAIChatEvents(response.body, {
|
|
133
|
+
signal: request.signal,
|
|
134
|
+
strictCompletion: options.strictCompletion,
|
|
135
|
+
doneUsage: options.doneUsage,
|
|
136
|
+
mapUsage: options.mapUsage,
|
|
137
|
+
onComment: options.onComment,
|
|
138
|
+
});
|
|
76
139
|
}
|
|
77
140
|
catch (error) {
|
|
78
141
|
yield providerError(error, secrets);
|
|
@@ -80,11 +143,13 @@ export function createOpenAICompatibleProvider(options) {
|
|
|
80
143
|
},
|
|
81
144
|
};
|
|
82
145
|
}
|
|
83
|
-
function toOpenAIRequest(request) {
|
|
146
|
+
function toOpenAIRequest(request, options) {
|
|
84
147
|
assertStructuredOutputRequestSupported(request.model, request.options);
|
|
85
148
|
const body = {
|
|
86
149
|
model: request.model.model,
|
|
87
|
-
messages: request.messages.map((message, index) => {
|
|
150
|
+
messages: (options.mapMessages?.(request) ?? request.messages).map((message, index) => {
|
|
151
|
+
if (options.serializeMessage)
|
|
152
|
+
return options.serializeMessage(message, request);
|
|
88
153
|
assertOpenAIChatMessage(message, `messages[${index}]`);
|
|
89
154
|
return serializeOpenAIChatMessage(message, request.model.capabilities ?? {});
|
|
90
155
|
}),
|
|
@@ -94,6 +159,11 @@ function toOpenAIRequest(request) {
|
|
|
94
159
|
...request.model.parameters,
|
|
95
160
|
};
|
|
96
161
|
applyOpenAIChatStructuredOutput(body, request.options?.structuredOutput);
|
|
97
|
-
|
|
162
|
+
const merged = { ...body, ...options.buildBodyExtra?.(request) };
|
|
163
|
+
return options.transformBody ? options.transformBody(merged, request) : merged;
|
|
164
|
+
}
|
|
165
|
+
/** Base Chat Completions request body builder, exported for provider packages keeping public body helpers. */
|
|
166
|
+
export function buildOpenAIChatBody(request, options = {}) {
|
|
167
|
+
return toOpenAIRequest(request, options);
|
|
98
168
|
}
|
|
99
169
|
//# sourceMappingURL=openai-compatible.js.map
|