@native-knowledge/knowledge-sdk 0.0.0-stage → 0.1.0-rc.2
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 +202 -0
- package/NOTICE +29 -0
- package/README.md +34 -2
- package/dist/agent-entry.d.ts +291 -0
- package/dist/agent-entry.js +153 -0
- package/dist/agent-guidance-error.d.ts +15 -0
- package/dist/agent-guidance-error.js +46 -0
- package/dist/auth.d.ts +124 -0
- package/dist/auth.js +165 -0
- package/dist/index.d.ts +290 -0
- package/dist/index.js +166 -0
- package/dist/internal/contracts/artifact.d.ts +120 -0
- package/dist/internal/contracts/artifact.js +103 -0
- package/dist/internal/contracts/collection-control.d.ts +160 -0
- package/dist/internal/contracts/collection-control.js +69 -0
- package/dist/internal/contracts/collection.d.ts +263 -0
- package/dist/internal/contracts/collection.js +174 -0
- package/dist/internal/contracts/common.d.ts +17 -0
- package/dist/internal/contracts/common.js +16 -0
- package/dist/internal/contracts/currentness.d.ts +477 -0
- package/dist/internal/contracts/currentness.js +221 -0
- package/dist/internal/contracts/event.d.ts +60 -0
- package/dist/internal/contracts/event.js +35 -0
- package/dist/internal/contracts/evidence.d.ts +202 -0
- package/dist/internal/contracts/evidence.js +171 -0
- package/dist/internal/contracts/index.d.ts +54 -0
- package/dist/internal/contracts/index.js +54 -0
- package/dist/internal/contracts/material-capability.d.ts +88 -0
- package/dist/internal/contracts/material-capability.js +8 -0
- package/dist/internal/contracts/material-carrier.d.ts +104 -0
- package/dist/internal/contracts/material-carrier.js +15 -0
- package/dist/internal/contracts/material-context.d.ts +28 -0
- package/dist/internal/contracts/material-context.js +9 -0
- package/dist/internal/contracts/material-read.d.ts +152 -0
- package/dist/internal/contracts/material-read.js +12 -0
- package/dist/internal/contracts/observation.d.ts +75 -0
- package/dist/internal/contracts/observation.js +39 -0
- package/dist/internal/contracts/persistence.d.ts +370 -0
- package/dist/internal/contracts/persistence.js +239 -0
- package/dist/internal/contracts/project-intent.d.ts +218 -0
- package/dist/internal/contracts/project-intent.js +121 -0
- package/dist/internal/contracts/projection.d.ts +380 -0
- package/dist/internal/contracts/projection.js +194 -0
- package/dist/internal/contracts/public-reference.d.ts +237 -0
- package/dist/internal/contracts/public-reference.js +87 -0
- package/dist/internal/contracts/reference-record.d.ts +124 -0
- package/dist/internal/contracts/reference-record.js +55 -0
- package/dist/internal/contracts/remote-file.d.ts +128 -0
- package/dist/internal/contracts/remote-file.js +159 -0
- package/dist/internal/contracts/resource-scope.d.ts +40 -0
- package/dist/internal/contracts/resource-scope.js +38 -0
- package/dist/internal/contracts/review-packet.d.ts +274 -0
- package/dist/internal/contracts/review-packet.js +61 -0
- package/dist/internal/contracts/review-plane.d.ts +90 -0
- package/dist/internal/contracts/review-plane.js +89 -0
- package/dist/internal/contracts/rich-document.d.ts +641 -0
- package/dist/internal/contracts/rich-document.js +184 -0
- package/dist/internal/contracts/runtime-compatibility.d.ts +22 -0
- package/dist/internal/contracts/runtime-compatibility.js +55 -0
- package/dist/internal/contracts/runtime.d.ts +59 -0
- package/dist/internal/contracts/runtime.js +68 -0
- package/dist/internal/contracts/scheduler.d.ts +348 -0
- package/dist/internal/contracts/scheduler.js +256 -0
- package/dist/internal/contracts/signal.d.ts +113 -0
- package/dist/internal/contracts/signal.js +77 -0
- package/dist/internal/contracts/source-governance.d.ts +375 -0
- package/dist/internal/contracts/source-governance.js +194 -0
- package/dist/internal/contracts/source.d.ts +62 -0
- package/dist/internal/contracts/source.js +32 -0
- package/dist/internal/contracts/tenant-budget.d.ts +59 -0
- package/dist/internal/contracts/tenant-budget.js +34 -0
- package/dist/internal/contracts/tenant-collection-request.d.ts +209 -0
- package/dist/internal/contracts/tenant-collection-request.js +58 -0
- package/dist/internal/contracts/tenant-collection.d.ts +199 -0
- package/dist/internal/contracts/tenant-collection.js +66 -0
- package/dist/internal/contracts/tenant-currentness.d.ts +117 -0
- package/dist/internal/contracts/tenant-currentness.js +26 -0
- package/dist/internal/contracts/tenant-management.d.ts +286 -0
- package/dist/internal/contracts/tenant-management.js +27 -0
- package/dist/internal/contracts/tenant-material-api.d.ts +584 -0
- package/dist/internal/contracts/tenant-material-api.js +46 -0
- package/dist/internal/contracts/tenant-material-json-schema.js +16 -0
- package/dist/internal/contracts/tenant-pdf-text.d.ts +27 -0
- package/dist/internal/contracts/tenant-pdf-text.js +13 -0
- package/dist/internal/contracts/tenant-project-control.d.ts +94 -0
- package/dist/internal/contracts/tenant-project-control.js +19 -0
- package/dist/internal/contracts/tenant-public-reference.d.ts +179 -0
- package/dist/internal/contracts/tenant-public-reference.js +26 -0
- package/dist/internal/contracts/tenant-resource-api.d.ts +738 -0
- package/dist/internal/contracts/tenant-resource-api.js +70 -0
- package/dist/internal/contracts/tenant-resource.d.ts +298 -0
- package/dist/internal/contracts/tenant-resource.js +118 -0
- package/dist/internal/contracts/tenant-rich-document.d.ts +317 -0
- package/dist/internal/contracts/tenant-rich-document.js +17 -0
- package/dist/internal/contracts/tenant-source-binding.d.ts +44 -0
- package/dist/internal/contracts/tenant-source-binding.js +33 -0
- package/dist/internal/contracts/tenant-source-bridge.d.ts +70 -0
- package/dist/internal/contracts/tenant-source-bridge.js +9 -0
- package/dist/internal/contracts/tenant-source-cutover.d.ts +226 -0
- package/dist/internal/contracts/tenant-source-cutover.js +26 -0
- package/dist/internal/contracts/tenant-source-document.d.ts +163 -0
- package/dist/internal/contracts/tenant-source-document.js +38 -0
- package/dist/internal/contracts/tenant-source-lease.d.ts +1 -0
- package/dist/internal/contracts/tenant-source-lease.js +5 -0
- package/dist/internal/contracts/tenant-source.d.ts +223 -0
- package/dist/internal/contracts/tenant-source.js +83 -0
- package/dist/internal/contracts/tenant-web-collection.d.ts +287 -0
- package/dist/internal/contracts/tenant-web-collection.js +118 -0
- package/dist/internal/contracts/tenant-web-content-plan.d.ts +86 -0
- package/dist/internal/contracts/tenant-web-content-plan.js +39 -0
- package/dist/internal/contracts/tenant-web-content-result.d.ts +63 -0
- package/dist/internal/contracts/tenant-web-content-result.js +15 -0
- package/dist/internal/contracts/tenant-web-content.d.ts +383 -0
- package/dist/internal/contracts/tenant-web-content.js +16 -0
- package/dist/internal/contracts/tenant-web-crawl.d.ts +310 -0
- package/dist/internal/contracts/tenant-web-crawl.js +55 -0
- package/dist/internal/contracts/tenant-web-dynamic.d.ts +698 -0
- package/dist/internal/contracts/tenant-web-dynamic.js +68 -0
- package/dist/internal/contracts/tenant-web-results.d.ts +66 -0
- package/dist/internal/contracts/tenant-web-results.js +41 -0
- package/dist/internal/contracts/tenant-web-workflow.d.ts +494 -0
- package/dist/internal/contracts/tenant-web-workflow.js +29 -0
- package/dist/internal/serving-contracts/agent-catalog.d.ts +70 -0
- package/dist/internal/serving-contracts/agent-catalog.js +56 -0
- package/dist/internal/serving-contracts/agent-entry.d.ts +13148 -0
- package/dist/internal/serving-contracts/agent-entry.js +57 -0
- package/dist/internal/serving-contracts/agent-guidance-shapes.d.ts +263 -0
- package/dist/internal/serving-contracts/agent-guidance-shapes.js +12 -0
- package/dist/internal/serving-contracts/agent-input-hints.d.ts +24 -0
- package/dist/internal/serving-contracts/agent-input-hints.js +104 -0
- package/dist/internal/serving-contracts/body-search.d.ts +231 -0
- package/dist/internal/serving-contracts/body-search.js +16 -0
- package/dist/internal/serving-contracts/consumer.d.ts +989 -0
- package/dist/internal/serving-contracts/consumer.js +45 -0
- package/dist/internal/serving-contracts/core.d.ts +333 -0
- package/dist/internal/serving-contracts/core.js +76 -0
- package/dist/internal/serving-contracts/http.d.ts +2874 -0
- package/dist/internal/serving-contracts/http.js +64 -0
- package/dist/internal/serving-contracts/index.d.ts +24 -0
- package/dist/internal/serving-contracts/index.js +25 -0
- package/dist/internal/serving-contracts/management.d.ts +424 -0
- package/dist/internal/serving-contracts/management.js +15 -0
- package/dist/internal/serving-contracts/material-capabilities.d.ts +508 -0
- package/dist/internal/serving-contracts/material-capabilities.js +14 -0
- package/dist/internal/serving-contracts/material-index-status.d.ts +55 -0
- package/dist/internal/serving-contracts/material-index-status.js +5 -0
- package/dist/internal/serving-contracts/mcp.d.ts +2218 -0
- package/dist/internal/serving-contracts/mcp.js +140 -0
- package/dist/internal/serving-contracts/tenant-governance.d.ts +236 -0
- package/dist/internal/serving-contracts/tenant-governance.js +13 -0
- package/dist/internal/serving-contracts/tenant-management.d.ts +6276 -0
- package/dist/internal/serving-contracts/tenant-management.js +89 -0
- package/dist/internal/serving-contracts/tenant-project.d.ts +7550 -0
- package/dist/internal/serving-contracts/tenant-project.js +39 -0
- package/dist/internal/serving-contracts/tenant-runtime.d.ts +9674 -0
- package/dist/internal/serving-contracts/tenant-runtime.js +37 -0
- package/dist/internal/serving-contracts/tenant-session.d.ts +87 -0
- package/dist/internal/serving-contracts/tenant-session.js +8 -0
- package/dist/internal/serving-contracts/tenant-web-content.d.ts +4606 -0
- package/dist/internal/serving-contracts/tenant-web-content.js +20 -0
- package/dist/internal/serving-contracts/tenant-web-crawl.d.ts +2958 -0
- package/dist/internal/serving-contracts/tenant-web-crawl.js +13 -0
- package/dist/internal/serving-contracts/tenant-web-dynamic.d.ts +3902 -0
- package/dist/internal/serving-contracts/tenant-web-dynamic.js +20 -0
- package/dist/internal/serving-contracts/tenant-web-workflow.d.ts +5750 -0
- package/dist/internal/serving-contracts/tenant-web-workflow.js +36 -0
- package/dist/internal/serving-contracts/tenant-web.d.ts +6418 -0
- package/dist/internal/serving-contracts/tenant-web.js +53 -0
- package/dist/internal/serving-contracts/tenant-workspace.d.ts +6451 -0
- package/dist/internal/serving-contracts/tenant-workspace.js +28 -0
- package/dist/internal/serving-contracts/workflow.d.ts +439 -0
- package/dist/internal/serving-contracts/workflow.js +37 -0
- package/dist/material-stream.d.ts +18 -0
- package/dist/material-stream.js +29 -0
- package/dist/oauth.d.ts +268 -0
- package/dist/oauth.js +539 -0
- package/dist/resource.d.ts +88 -0
- package/dist/resource.js +123 -0
- package/dist/session.d.ts +4 -0
- package/dist/session.js +3 -0
- package/dist/tenant-client.d.ts +169 -0
- package/dist/tenant-client.js +58 -0
- package/dist/tenant-management.d.ts +240 -0
- package/dist/tenant-management.js +149 -0
- package/dist/tenant-project.d.ts +57 -0
- package/dist/tenant-project.js +63 -0
- package/dist/tenant-runtime.d.ts +174 -0
- package/dist/tenant-runtime.js +17 -0
- package/dist/tenant-web-content.d.ts +44 -0
- package/dist/tenant-web-content.js +70 -0
- package/dist/tenant-web-crawl.d.ts +42 -0
- package/dist/tenant-web-crawl.js +39 -0
- package/dist/tenant-web-dynamic.d.ts +42 -0
- package/dist/tenant-web-dynamic.js +39 -0
- package/dist/tenant-web-workflow.d.ts +43 -0
- package/dist/tenant-web-workflow.js +63 -0
- package/dist/tenant-web.d.ts +42 -0
- package/dist/tenant-web.js +39 -0
- package/dist/tenant-workspace.d.ts +36 -0
- package/dist/tenant-workspace.js +21 -0
- package/dist/token-provider.d.ts +14 -0
- package/dist/token-provider.js +21 -0
- package/package.json +45 -3
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/** Explicit v26 task entry; existing clients and default protocol versions stay independent. */
|
|
2
|
+
import { KnowledgeAgentGuidanceError, validateGuidanceActions, agentGuidanceFailure } from './agent-guidance-error.js';
|
|
3
|
+
import { agentGuidanceHeader, agentResupplyGuidanceFeature, agentGuidedToolSchemas, agentGuidedDataSchemas, agentGuidedEnvelopeSchema } from './internal/serving-contracts/index.js';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { resourceToolSchemas, tenantWebJobRef, tenantProjectOperationRef } from './internal/contracts/index.js';
|
|
6
|
+
import { agentBodySearchHeader, agentBodySearchFeature, bodySearchToolSchemas, bodyCitationSchema, agentEntryToolSchemas, agentEntryDataSchemas, agentEntryEnvelopeSchema, agentEntryProviderSchema, agentCapabilityEnvelopeSchema, agentCapabilitySchemas, agentCapabilityDataSchemas } from './internal/serving-contracts/index.js';
|
|
7
|
+
import { KnowledgeTenantManagementClient, TenantManagementSdkError } from './tenant-management.js';
|
|
8
|
+
import { KnowledgeResourceClient } from './resource.js';
|
|
9
|
+
const paged = new Set(['knowledge_resource_search_body', 'knowledge_resource_list', 'knowledge_resource_search', 'knowledge_resource_counts', 'knowledge_resource_changes', 'tenant_material_routes', 'tenant_source_list', 'tenant_source_inventory', 'tenant_work_queue', 'tenant_project_list', 'tenant_project_materials', 'tenant_runtime_status', 'tenant_web_collect_results', 'tenant_web_collect_cycles']);
|
|
10
|
+
const object = (v) => v && typeof v === 'object' && !Array.isArray(v) ? v : {};
|
|
11
|
+
function pageBudget(value = 10) { if (!Number.isSafeInteger(value) || value < 1 || value > 100)
|
|
12
|
+
throw new TenantManagementSdkError('AGENT_SDK_PAGE_BUDGET_INVALID', 0); return value; }
|
|
13
|
+
export class KnowledgeAgentClient extends KnowledgeTenantManagementClient {
|
|
14
|
+
failure(raw, status) { return agentGuidanceFailure(raw, status, this.options.expectedTenantId); }
|
|
15
|
+
async entry(tool, raw, signal, guided = false) {
|
|
16
|
+
const input = (guided ? agentGuidedToolSchemas : agentEntryToolSchemas)[tool].parse(raw), body = JSON.stringify({ tool, arguments: input });
|
|
17
|
+
if (Buffer.byteLength(body) > 131072)
|
|
18
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_REQUEST_TOO_LARGE', 0);
|
|
19
|
+
const response = await this.request(this.url('call', 26), { method: 'POST', headers: { 'content-type': 'application/json', [agentBodySearchHeader]: agentBodySearchFeature, ...(guided ? { [agentGuidanceHeader]: agentResupplyGuidanceFeature } : {}) }, body, ...(signal ? { signal } : {}) }), v = (guided ? agentGuidedEnvelopeSchema : agentEntryEnvelopeSchema)(tool).safeParse(await this.json(response));
|
|
20
|
+
if (!v.success)
|
|
21
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_RESPONSE_INVALID', response.status);
|
|
22
|
+
if (v.data.tenantId !== this.options.expectedTenantId)
|
|
23
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', response.status);
|
|
24
|
+
return v.data;
|
|
25
|
+
}
|
|
26
|
+
async agentProvider() { const r = await this.request(this.url('provider', 26)), v = agentEntryProviderSchema.safeParse(await this.json(r)); if (!v.success)
|
|
27
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_RESPONSE_INVALID', r.status); return v.data; }
|
|
28
|
+
async discover(input = {}, options = {}) {
|
|
29
|
+
const result = await this.entry('knowledge_discover', input, options.signal), data = agentEntryDataSchemas.knowledge_discover.parse(result.data);
|
|
30
|
+
if (input.tool && data.detail?.name !== input.tool || !input.tool && data.detail !== null)
|
|
31
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
32
|
+
return { ...result, tool: 'knowledge_discover', data };
|
|
33
|
+
}
|
|
34
|
+
async execute(tool, input, options = {}) { return await this.executeChecked(tool, input, options, false); }
|
|
35
|
+
async guidedDiscover(input = {}, options = {}) {
|
|
36
|
+
const result = await this.entry('knowledge_discover', input, options.signal, true), data = agentGuidedDataSchemas.knowledge_discover.parse(result.data);
|
|
37
|
+
if (input.tool && data.detail?.name !== input.tool || !input.tool && data.detail !== null || Boolean(input.task) !== (data.mode === 'TASK') || Boolean(data.taskPlan) !== (data.mode === 'TASK'))
|
|
38
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
39
|
+
validateGuidanceActions(data.nextActions);
|
|
40
|
+
for (const step of data.taskPlan?.steps ?? [])
|
|
41
|
+
if (step.action)
|
|
42
|
+
validateGuidanceActions([step.action]);
|
|
43
|
+
return { ...result, tool: 'knowledge_discover', data };
|
|
44
|
+
}
|
|
45
|
+
planTask(task, options = {}) { return this.guidedDiscover({ task }, options); }
|
|
46
|
+
inputGuide(tool, input = {}, options = {}) { return this.guidedDiscover({ tool, arguments: JSON.parse(JSON.stringify(input)), ...(options.resupplyFields ? { resupplyFields: options.resupplyFields } : {}) }, options); }
|
|
47
|
+
async guidedExecute(tool, input, options = {}) {
|
|
48
|
+
try {
|
|
49
|
+
return await this.executeChecked(tool, input, options, true);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error instanceof KnowledgeAgentGuidanceError)
|
|
53
|
+
throw error;
|
|
54
|
+
if (error instanceof TenantManagementSdkError)
|
|
55
|
+
throw new KnowledgeAgentGuidanceError(error.code, error.status, { reasonCode: 'OUTCOME_UNKNOWN', summary: '先检查当前授权及准确目标状态,不自动重试写入。', inputHints: [], nextActions: [] }, { ...(error.completedPhase ? { completedPhase: error.completedPhase } : {}), ...(error.ref ? { ref: error.ref } : {}) });
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async executeChecked(tool, input, options, guided) {
|
|
60
|
+
const parsedInput = Object.hasOwn(agentCapabilitySchemas, tool) ? agentCapabilitySchemas[tool].safeParse(input) : null, wireInput = parsedInput?.success ? JSON.parse(JSON.stringify(parsedInput.data)) : input;
|
|
61
|
+
const response = await this.entry('knowledge_execute', { tool, arguments: wireInput }, options.signal, guided), data = (guided ? agentGuidedDataSchemas : agentEntryDataSchemas).knowledge_execute.parse(response.data);
|
|
62
|
+
if (data.capability !== tool)
|
|
63
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
64
|
+
if (data.status === 'COMPLETED') {
|
|
65
|
+
const parsed = agentCapabilityEnvelopeSchema(tool).safeParse(data.result);
|
|
66
|
+
if (!parsed.success)
|
|
67
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_RESPONSE_INVALID', 200);
|
|
68
|
+
const inner = object(parsed.data), context = object(inner.context), raw = object(wireInput), d = object(inner.data);
|
|
69
|
+
if ((Object.hasOwn(resourceToolSchemas, tool) || Object.hasOwn(bodySearchToolSchemas, tool)) ? context.tenantId !== this.options.expectedTenantId || (raw.projectRef ? context.type !== 'PROJECT' || context.projectRef !== raw.projectRef || inner.policy !== 'TENANT_PROJECT_SCOPE' : context.type !== 'TENANT' || inner.policy !== 'TENANT_NATIVE') : inner.tenantId !== this.options.expectedTenantId || inner.tool !== tool)
|
|
70
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
71
|
+
if (tool.startsWith('tenant_project_')) {
|
|
72
|
+
const receipt = object(d.receipt), expected = typeof raw.requestId === 'string' ? tenantProjectOperationRef(this.options.expectedTenantId, raw.requestId) : null;
|
|
73
|
+
if (raw.projectKey && d.projectKey !== raw.projectKey || expected && (d.operationRef && d.operationRef !== expected || receipt.operationRef && receipt.operationRef !== expected))
|
|
74
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
75
|
+
}
|
|
76
|
+
if (tool === 'tenant_web_collect_control') {
|
|
77
|
+
const operation = object(object(d.receipt).operation);
|
|
78
|
+
if (operation.requestId !== raw.requestId || operation.action !== raw.action || operation.expectedEpoch !== raw.expectedEpoch || operation.jobRef !== object(d.job).rootJobRef)
|
|
79
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
80
|
+
}
|
|
81
|
+
const source = tool === 'tenant_source_get' ? d : object(d.head), expectedSource = raw.sourceId ?? object(raw.definition).sourceId, request = object(d.request);
|
|
82
|
+
if (d.tenantId && d.tenantId !== this.options.expectedTenantId || source.tenantId && source.tenantId !== this.options.expectedTenantId || request.tenantId && request.tenantId !== this.options.expectedTenantId || expectedSource && source.sourceId && source.sourceId !== expectedSource || ['tenant_collection_request', 'tenant_collection_status'].includes(tool) && request.requestId && request.requestId !== raw.requestId || tool === 'tenant_workflow_advance' && (d.previousRef !== raw.ref || d.action !== raw.action))
|
|
83
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
84
|
+
const expectedJob = tool === 'tenant_web_collect_submit' || tool === 'tenant_web_collect_refresh' ? tenantWebJobRef(this.options.expectedTenantId, String(raw.requestId)) : raw.jobRef;
|
|
85
|
+
if (expectedJob && (d.jobRef ?? object(d.job).jobRef) !== expectedJob || raw.ref && object(d.item).ref && object(d.item).ref !== raw.ref || ['tenant_workflow_plan', 'tenant_knowledge_read'].includes(tool) && d.ref !== raw.ref || tool === 'tenant_snapshot_status' && d.subjectRef !== raw.ref || tool === 'knowledge_resource_media' && (d.ref && d.ref !== raw.ref || raw.documentRef && d.state === 'READY' && d.documentRef !== raw.documentRef))
|
|
86
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
87
|
+
if (tool === 'knowledge_resource_search_body')
|
|
88
|
+
for (const item of d.items) {
|
|
89
|
+
const citation = bodyCitationSchema.parse(item.citation);
|
|
90
|
+
if (citation.ref !== item.item.ref || citation.contentHash !== item.item.identityHash)
|
|
91
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
92
|
+
}
|
|
93
|
+
if (tool === 'knowledge_resource_read_passage' && JSON.stringify(d.citation) !== JSON.stringify(bodyCitationSchema.parse(raw.citation)))
|
|
94
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTEXT_MISMATCH', 200);
|
|
95
|
+
const read = tool === 'tenant_web_collect_read' ? object(d.result) : d;
|
|
96
|
+
if (read.delivery === 'INLINE_BASE64') {
|
|
97
|
+
if (typeof read.contentBase64 !== 'string' || !object(read.item).ref)
|
|
98
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_RESPONSE_INVALID', 200);
|
|
99
|
+
const text = String(read.contentBase64), bytes = Buffer.from(text, 'base64'), descriptor = object(read.descriptor), item = object(read.item);
|
|
100
|
+
if (bytes.toString('base64') !== text || bytes.length !== descriptor.byteLength || read.byteLength !== null && bytes.length !== read.byteLength || 'sha256:' + createHash('sha256').update(bytes).digest('hex') !== descriptor.contentHash || item.identityKind === 'CAS_BYTES' && descriptor.contentHash !== item.identityHash)
|
|
101
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_CONTENT_IDENTITY_MISMATCH', 200);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (data.result !== null || !data.missingFields.length)
|
|
105
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_RESPONSE_INVALID', 200);
|
|
106
|
+
if (guided)
|
|
107
|
+
validateGuidanceActions(data.nextActions);
|
|
108
|
+
return { ...response, tool: 'knowledge_execute', data };
|
|
109
|
+
}
|
|
110
|
+
openMaterialStream(ref, options = {}) { return new KnowledgeResourceClient({ ...this.options, authentication: 'OAUTH' }).stream(ref, options); }
|
|
111
|
+
async *discoverPages(input, options = {}) {
|
|
112
|
+
const max = pageBudget(options.maxPages), seen = new Set(input.after ? [input.after] : []);
|
|
113
|
+
let request = { ...input };
|
|
114
|
+
for (let i = 0; i < max; i++) {
|
|
115
|
+
const page = await this.discover(request, options), after = page.data.nextAfter;
|
|
116
|
+
if (after && seen.has(after))
|
|
117
|
+
throw new TenantManagementSdkError('AGENT_SDK_CURSOR_REPEATED', 200);
|
|
118
|
+
if (after)
|
|
119
|
+
seen.add(after);
|
|
120
|
+
yield page;
|
|
121
|
+
if (!after)
|
|
122
|
+
return;
|
|
123
|
+
request = { ...request, after };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async *pages(tool, input, options = {}) {
|
|
127
|
+
const media = tool === 'knowledge_resource_media', project = tool === 'tenant_project_get', continuation = options.continuation;
|
|
128
|
+
if (!paged.has(tool) && !media && !project)
|
|
129
|
+
throw new TenantManagementSdkError('AGENT_SDK_OPERATION_NOT_PAGEABLE', 0);
|
|
130
|
+
if (project ? !['purpose', 'selection'].includes(continuation ?? '') : continuation !== undefined)
|
|
131
|
+
throw new TenantManagementSdkError('AGENT_SDK_CONTINUATION_INVALID', 0);
|
|
132
|
+
const max = pageBudget(options.maxPages), cursor = media ? 'afterOrdinal' : project ? continuation + 'After' : 'after';
|
|
133
|
+
let request = { ...object(input) };
|
|
134
|
+
const initial = request[cursor], seen = new Set(typeof initial === 'string' || typeof initial === 'number' ? [initial] : []);
|
|
135
|
+
for (let i = 0; i < max; i++) {
|
|
136
|
+
const page = await this.execute(tool, request, options), completed = page.data.status === 'COMPLETED', data = object(page.data.result?.data), after = media ? data.nextAfterOrdinal : project ? object(data[continuation]).nextAfter : data.nextAfter;
|
|
137
|
+
const more = media ? typeof after === 'number' : typeof after === 'string' && after.length > 0;
|
|
138
|
+
if (more) {
|
|
139
|
+
if (seen.has(after) || media && typeof request[cursor] === 'number' && after <= request[cursor])
|
|
140
|
+
throw new TenantManagementSdkError('AGENT_SDK_CURSOR_REPEATED', 200);
|
|
141
|
+
if (media && (data.state !== 'READY' || typeof data.documentRef !== 'string'))
|
|
142
|
+
throw new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_RESPONSE_INVALID', 200);
|
|
143
|
+
seen.add(after);
|
|
144
|
+
}
|
|
145
|
+
// Capture the exact continuation before yielding: consumers may retain or edit the returned page.
|
|
146
|
+
const next = { ...request, [cursor]: after, ...(media && more ? { documentRef: data.documentRef } : {}) };
|
|
147
|
+
yield page;
|
|
148
|
+
if (!completed || !more)
|
|
149
|
+
return;
|
|
150
|
+
request = next;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type AgentRecovery } from './internal/serving-contracts/index.js';
|
|
2
|
+
import { TenantManagementSdkError } from './tenant-management.js';
|
|
3
|
+
export declare function validateGuidanceActions(actions: AgentRecovery['nextActions'], options?: {
|
|
4
|
+
status?: number;
|
|
5
|
+
recoveryOnly?: boolean;
|
|
6
|
+
}): void;
|
|
7
|
+
export declare class KnowledgeAgentGuidanceError extends TenantManagementSdkError<string> {
|
|
8
|
+
readonly guidance: AgentRecovery;
|
|
9
|
+
constructor(code: string, status: number, guidance: AgentRecovery, details?: {
|
|
10
|
+
completedPhase?: string;
|
|
11
|
+
ref?: string;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/** Invalid optional guidance cannot erase a validated durable failure receipt. */
|
|
15
|
+
export declare function agentGuidanceFailure(raw: unknown, status: number, tenantId: string): TenantManagementSdkError<string> | null;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { agentGuidedToolSchemas, agentEntryToolSchemas, agentCapabilitySchemas, agentCatalog, agentRecoverySchema, tenantWebWorkflowErrorSchema, tenantWebSafeErrorRef } from './internal/serving-contracts/index.js';
|
|
2
|
+
import { TenantManagementSdkError } from './tenant-management.js';
|
|
3
|
+
export function validateGuidanceActions(actions, options = {}) {
|
|
4
|
+
const invalid = () => new TenantManagementSdkError('TENANT_MANAGEMENT_SDK_RESPONSE_INVALID', options.status ?? 200);
|
|
5
|
+
for (const action of actions) {
|
|
6
|
+
if (action.canExecute && action.missingFields.length)
|
|
7
|
+
throw invalid();
|
|
8
|
+
const entry = agentGuidedToolSchemas[action.tool].safeParse(action.arguments);
|
|
9
|
+
if (!entry.success)
|
|
10
|
+
throw invalid();
|
|
11
|
+
if (action.tool === 'knowledge_execute') {
|
|
12
|
+
const input = agentEntryToolSchemas.knowledge_execute.parse(action.arguments);
|
|
13
|
+
if (options.recoveryOnly && !agentCatalog[input.tool].readOnly || action.canExecute && !agentCapabilitySchemas[input.tool].safeParse(input.arguments).success)
|
|
14
|
+
throw invalid();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class KnowledgeAgentGuidanceError extends TenantManagementSdkError {
|
|
19
|
+
guidance;
|
|
20
|
+
constructor(code, status, guidance, details) {
|
|
21
|
+
super(code, status, details);
|
|
22
|
+
this.guidance = guidance;
|
|
23
|
+
this.name = 'KnowledgeAgentGuidanceError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Invalid optional guidance cannot erase a validated durable failure receipt. */
|
|
27
|
+
export function agentGuidanceFailure(raw, status, tenantId) {
|
|
28
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
29
|
+
return null;
|
|
30
|
+
const { guidance, ...base } = raw, parsed = tenantWebWorkflowErrorSchema.safeParse(base);
|
|
31
|
+
if (!parsed.success)
|
|
32
|
+
return null;
|
|
33
|
+
const failure = parsed.data, ref = tenantWebSafeErrorRef(failure.ref, tenantId), details = { ...(failure.completedPhase ? { completedPhase: failure.completedPhase } : {}), ...(ref ? { ref } : {}) };
|
|
34
|
+
if (!Object.hasOwn(raw, 'guidance'))
|
|
35
|
+
return new TenantManagementSdkError(failure.errorCode, status, details);
|
|
36
|
+
try {
|
|
37
|
+
const checked = agentRecoverySchema.parse(guidance);
|
|
38
|
+
validateGuidanceActions(checked.nextActions, { status, recoveryOnly: true });
|
|
39
|
+
if (/AUTHORITY_CHANGED|FORBIDDEN/.test(failure.errorCode) && checked.nextActions.length)
|
|
40
|
+
throw new Error('invalid revoked actions');
|
|
41
|
+
return new KnowledgeAgentGuidanceError(failure.errorCode, status, checked, details);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return new KnowledgeAgentGuidanceError('TENANT_MANAGEMENT_SDK_RESPONSE_INVALID', status, { reasonCode: /AUTHORITY_CHANGED|FORBIDDEN/.test(failure.errorCode) ? 'AUTHORITY_REQUIRED' : 'OUTCOME_UNKNOWN', summary: '错误引导无效;保留已知阶段和安全引用,检查当前授权及准确结果,不重放写入。', inputHints: [], nextActions: [] }, details);
|
|
45
|
+
}
|
|
46
|
+
}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { type TenantClientProvider, type TenantSession } from './internal/serving-contracts/index.js';
|
|
3
|
+
export { type TenantSession, type TenantClientProvider };
|
|
4
|
+
export interface OAuthTokens {
|
|
5
|
+
accessToken: string;
|
|
6
|
+
refreshToken: string;
|
|
7
|
+
expiresAt: string;
|
|
8
|
+
scopes: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface AuthorizationTransaction {
|
|
11
|
+
issuer: string;
|
|
12
|
+
resource: string;
|
|
13
|
+
clientId: string;
|
|
14
|
+
redirectUri: string;
|
|
15
|
+
state: string;
|
|
16
|
+
verifier: string;
|
|
17
|
+
authorizationUrl: string;
|
|
18
|
+
expiresAt: number;
|
|
19
|
+
scopes: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface AuthClientOptions {
|
|
22
|
+
baseUrl: string;
|
|
23
|
+
expectedIssuer?: string;
|
|
24
|
+
expectedResource?: string;
|
|
25
|
+
allowInsecureHttp?: boolean;
|
|
26
|
+
fetchImpl?: typeof fetch;
|
|
27
|
+
}
|
|
28
|
+
export declare class AuthError extends Error {
|
|
29
|
+
readonly code: string;
|
|
30
|
+
readonly status: number;
|
|
31
|
+
constructor(code: string, status?: number);
|
|
32
|
+
}
|
|
33
|
+
export declare function authUrl(raw: string, insecure?: boolean): URL;
|
|
34
|
+
export declare class KnowledgeAuthClient {
|
|
35
|
+
private options;
|
|
36
|
+
private root;
|
|
37
|
+
private fetchImpl;
|
|
38
|
+
private cached;
|
|
39
|
+
constructor(options: AuthClientOptions);
|
|
40
|
+
private request;
|
|
41
|
+
discover(): Promise<{
|
|
42
|
+
schemaVersion: 26;
|
|
43
|
+
provider: "knowledge-v2";
|
|
44
|
+
revision: string;
|
|
45
|
+
issuer: string;
|
|
46
|
+
resource: string;
|
|
47
|
+
authorizationServerMetadataUrl: string;
|
|
48
|
+
tenantSessionEndpoint: string;
|
|
49
|
+
tenantOperationStatusEndpoint: string;
|
|
50
|
+
resourceCallEndpoint: string;
|
|
51
|
+
resourceFileEndpoint: string;
|
|
52
|
+
registrationModes: ("PRE_REGISTERED_NATIVE" | "CIMD_READ_ONLY")[];
|
|
53
|
+
callbackModes: "NATIVE_LOOPBACK_PORT_V1"[];
|
|
54
|
+
providers: {
|
|
55
|
+
version: number;
|
|
56
|
+
endpoint: string;
|
|
57
|
+
}[];
|
|
58
|
+
tools: {
|
|
59
|
+
name: string;
|
|
60
|
+
endpoint: string;
|
|
61
|
+
method: "GET" | "POST";
|
|
62
|
+
inputSchema: import("zod").JSONType;
|
|
63
|
+
outputSchema: import("zod").JSONType;
|
|
64
|
+
scopeAlternatives: string[][];
|
|
65
|
+
}[];
|
|
66
|
+
limits: {
|
|
67
|
+
requestMaxBytes: 131072;
|
|
68
|
+
resultMaxBytes: 524288;
|
|
69
|
+
};
|
|
70
|
+
}>;
|
|
71
|
+
metadata(): Promise<{
|
|
72
|
+
issuer: string;
|
|
73
|
+
authorizationEndpoint: string;
|
|
74
|
+
tokenEndpoint: string;
|
|
75
|
+
revocationEndpoint: string;
|
|
76
|
+
}>;
|
|
77
|
+
begin(input: {
|
|
78
|
+
clientId: string;
|
|
79
|
+
redirectUri: string;
|
|
80
|
+
scopes: string[];
|
|
81
|
+
authorityVersion?: 1 | 2;
|
|
82
|
+
expectedTenantRef?: string;
|
|
83
|
+
}): Promise<{
|
|
84
|
+
issuer: string;
|
|
85
|
+
resource: string;
|
|
86
|
+
clientId: string;
|
|
87
|
+
redirectUri: string;
|
|
88
|
+
state: string;
|
|
89
|
+
verifier: string;
|
|
90
|
+
authorizationUrl: string;
|
|
91
|
+
expiresAt: number;
|
|
92
|
+
scopes: string[];
|
|
93
|
+
}>;
|
|
94
|
+
exchange(t: AuthorizationTransaction, callback: URL): Promise<OAuthTokens>;
|
|
95
|
+
private tokens;
|
|
96
|
+
refresh(clientId: string, refreshToken: string, scopes?: string[]): Promise<OAuthTokens>;
|
|
97
|
+
session(token: string, expected?: Partial<Pick<TenantSession, 'issuer' | 'resource' | 'clientId' | 'userId' | 'tenantId' | 'authorityVersion'>>): Promise<{
|
|
98
|
+
schemaVersion: 1;
|
|
99
|
+
issuer: string;
|
|
100
|
+
resource: string;
|
|
101
|
+
clientId: string;
|
|
102
|
+
userId: string;
|
|
103
|
+
tenantId: string;
|
|
104
|
+
tenantRef: string;
|
|
105
|
+
contextType: "TENANT";
|
|
106
|
+
authorityVersion: 2 | 1;
|
|
107
|
+
tokenScopes: string[];
|
|
108
|
+
effectiveScopes: string[];
|
|
109
|
+
expiresAt: string;
|
|
110
|
+
managementConfigured: boolean;
|
|
111
|
+
blockers: string[];
|
|
112
|
+
}>;
|
|
113
|
+
operationStatus(token: string, raw: unknown, expectedTenantId?: string): Promise<{
|
|
114
|
+
schemaVersion: 1;
|
|
115
|
+
tenantId: string;
|
|
116
|
+
requestId: string;
|
|
117
|
+
state: "NOT_FOUND" | "INTENT_PERSISTED";
|
|
118
|
+
operationRef: string | null;
|
|
119
|
+
action: string | null;
|
|
120
|
+
receivedAt: string | null;
|
|
121
|
+
completion: "NOT_PROVEN";
|
|
122
|
+
}>;
|
|
123
|
+
revoke(clientId: string, token: string): Promise<void>;
|
|
124
|
+
}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
2
|
+
import { tenantClientProviderSchema, tenantSessionSchema, tenantOperationStatusInputSchema, tenantOperationStatusSchema } from './internal/serving-contracts/index.js';
|
|
3
|
+
export {};
|
|
4
|
+
export class AuthError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
status;
|
|
7
|
+
constructor(code, status = 0) {
|
|
8
|
+
super(code);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.name = 'AuthError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function authUrl(raw, insecure = false) { let u; try {
|
|
15
|
+
u = new URL(raw);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new AuthError('AUTH_URL_INVALID');
|
|
19
|
+
} if (u.username || u.password || u.search || u.hash || u.protocol !== 'https:' && !(insecure && u.protocol === 'http:' && ['127.0.0.1', '[::1]', 'localhost'].includes(u.hostname)))
|
|
20
|
+
throw new AuthError('AUTH_URL_INVALID'); return u; }
|
|
21
|
+
export class KnowledgeAuthClient {
|
|
22
|
+
options;
|
|
23
|
+
root;
|
|
24
|
+
fetchImpl;
|
|
25
|
+
cached;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
this.options = options;
|
|
28
|
+
this.root = authUrl(options.baseUrl, options.allowInsecureHttp);
|
|
29
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
30
|
+
}
|
|
31
|
+
async request(url, init = {}) {
|
|
32
|
+
let response;
|
|
33
|
+
try {
|
|
34
|
+
response = await this.fetchImpl(url, { ...init, redirect: 'error', signal: AbortSignal.timeout(15000) });
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new AuthError('AUTH_TRANSPORT_FAILURE');
|
|
38
|
+
}
|
|
39
|
+
if (!response.body)
|
|
40
|
+
throw new AuthError('AUTH_RESPONSE_INVALID', response.status);
|
|
41
|
+
const reader = response.body.getReader(), chunks = [];
|
|
42
|
+
let n = 0;
|
|
43
|
+
try {
|
|
44
|
+
while (true) {
|
|
45
|
+
const p = await reader.read();
|
|
46
|
+
if (p.done)
|
|
47
|
+
break;
|
|
48
|
+
n += p.value.length;
|
|
49
|
+
if (n > 524288) {
|
|
50
|
+
await reader.cancel();
|
|
51
|
+
throw new AuthError('AUTH_RESPONSE_BUDGET');
|
|
52
|
+
}
|
|
53
|
+
chunks.push(p.value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
reader.releaseLock();
|
|
58
|
+
}
|
|
59
|
+
let data;
|
|
60
|
+
try {
|
|
61
|
+
data = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new AuthError('AUTH_RESPONSE_INVALID', response.status);
|
|
65
|
+
}
|
|
66
|
+
if (!response.ok)
|
|
67
|
+
throw new AuthError(response.status === 401 ? 'AUTH_REQUIRED' : response.status === 403 ? 'AUTH_FORBIDDEN' : response.status === 404 ? 'SERVER_CAPABILITY_UNAVAILABLE' : 'AUTH_REQUEST_FAILED', response.status);
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
async discover() {
|
|
71
|
+
if (this.cached)
|
|
72
|
+
return this.cached;
|
|
73
|
+
let p;
|
|
74
|
+
try {
|
|
75
|
+
p = tenantClientProviderSchema.parse(await this.request(new URL(this.root.pathname.replace(/\/$/, '') + '/v1/tenant/client-provider', this.root.origin)));
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
if (e instanceof AuthError)
|
|
79
|
+
throw e;
|
|
80
|
+
throw new AuthError('AUTH_DISCOVERY_INVALID');
|
|
81
|
+
}
|
|
82
|
+
if (this.options.expectedIssuer && p.issuer !== this.options.expectedIssuer || this.options.expectedResource && p.resource !== this.options.expectedResource)
|
|
83
|
+
throw new AuthError('AUTH_ISSUER_MISMATCH');
|
|
84
|
+
authUrl(p.issuer, this.options.allowInsecureHttp);
|
|
85
|
+
authUrl(p.resource, this.options.allowInsecureHttp);
|
|
86
|
+
for (const raw of [p.tenantSessionEndpoint, p.tenantOperationStatusEndpoint, p.resourceCallEndpoint, p.resourceFileEndpoint, ...p.tools.map(t => t.endpoint), ...p.providers.map(t => t.endpoint)])
|
|
87
|
+
if (authUrl(raw, this.options.allowInsecureHttp).origin !== this.root.origin)
|
|
88
|
+
throw new AuthError('AUTH_ENDPOINT_MISMATCH');
|
|
89
|
+
const issuer = authUrl(p.issuer, this.options.allowInsecureHttp);
|
|
90
|
+
if (authUrl(p.authorizationServerMetadataUrl, this.options.allowInsecureHttp).origin !== issuer.origin)
|
|
91
|
+
throw new AuthError('AUTH_ISSUER_MISMATCH');
|
|
92
|
+
this.cached = p;
|
|
93
|
+
return p;
|
|
94
|
+
}
|
|
95
|
+
async metadata() { const p = await this.discover(), m = await this.request(p.authorizationServerMetadataUrl); if (m.issuer !== p.issuer || !Array.isArray(m.code_challenge_methods_supported) || !m.code_challenge_methods_supported.includes('S256') || !Array.isArray(m.grant_types_supported) || !m.grant_types_supported.includes('authorization_code'))
|
|
96
|
+
throw new AuthError('AUTH_DISCOVERY_INVALID'); for (const key of ['authorization_endpoint', 'token_endpoint', 'revocation_endpoint'])
|
|
97
|
+
if (typeof m[key] !== 'string' || (key !== 'authorization_endpoint' && authUrl(m[key], this.options.allowInsecureHttp).origin !== new URL(p.issuer).origin))
|
|
98
|
+
throw new AuthError('AUTH_ENDPOINT_MISMATCH'); authUrl(m.authorization_endpoint, this.options.allowInsecureHttp); return { issuer: p.issuer, authorizationEndpoint: m.authorization_endpoint, tokenEndpoint: m.token_endpoint, revocationEndpoint: m.revocation_endpoint }; }
|
|
99
|
+
async begin(input) {
|
|
100
|
+
const p = await this.discover();
|
|
101
|
+
if (!p.registrationModes.includes('PRE_REGISTERED_NATIVE'))
|
|
102
|
+
throw new AuthError('SERVER_CAPABILITY_UNAVAILABLE');
|
|
103
|
+
const m = await this.metadata(), state = randomBytes(32).toString('base64url'), verifier = randomBytes(48).toString('base64url'), u = new URL(m.authorizationEndpoint);
|
|
104
|
+
const redirect = new URL(input.redirectUri);
|
|
105
|
+
if (redirect.protocol !== 'http:' || !['127.0.0.1', '[::1]'].includes(redirect.hostname) || redirect.search || redirect.hash || redirect.username || redirect.password || !input.scopes.length || input.scopes.length > 32)
|
|
106
|
+
throw new AuthError('AUTH_INPUT_INVALID');
|
|
107
|
+
for (const [k, v] of Object.entries({ response_type: 'code', client_id: input.clientId, redirect_uri: redirect.href, resource: p.resource, scope: input.scopes.join(' '), authority_version: String(input.authorityVersion ?? 1), state, code_challenge: createHash('sha256').update(verifier).digest('base64url'), code_challenge_method: 'S256', ...(input.expectedTenantRef ? { tenant_ref: input.expectedTenantRef } : {}) }))
|
|
108
|
+
u.searchParams.set(k, v);
|
|
109
|
+
return { issuer: p.issuer, resource: p.resource, clientId: input.clientId, redirectUri: redirect.href, state, verifier, authorizationUrl: u.href, expiresAt: Date.now() + 300000, scopes: input.scopes };
|
|
110
|
+
}
|
|
111
|
+
async exchange(t, callback) {
|
|
112
|
+
if (Date.now() >= t.expiresAt || callback.origin !== new URL(t.redirectUri).origin || callback.pathname !== new URL(t.redirectUri).pathname || callback.hash || callback.username || callback.password)
|
|
113
|
+
throw new AuthError('AUTH_CALLBACK_INVALID');
|
|
114
|
+
for (const k of callback.searchParams.keys())
|
|
115
|
+
if (!['state', 'iss', 'code', 'error', 'error_description', 'error_uri'].includes(k) || callback.searchParams.getAll(k).length !== 1)
|
|
116
|
+
throw new AuthError('AUTH_CALLBACK_INVALID');
|
|
117
|
+
if (callback.searchParams.get('state') !== t.state || callback.searchParams.get('iss') !== t.issuer)
|
|
118
|
+
throw new AuthError('AUTH_CALLBACK_INVALID');
|
|
119
|
+
if (callback.searchParams.has('error'))
|
|
120
|
+
throw new AuthError('AUTH_CONSENT_DENIED');
|
|
121
|
+
const code = callback.searchParams.get('code');
|
|
122
|
+
if (!code)
|
|
123
|
+
throw new AuthError('AUTH_CALLBACK_INVALID');
|
|
124
|
+
const p = await this.discover();
|
|
125
|
+
if (p.issuer !== t.issuer || p.resource !== t.resource)
|
|
126
|
+
throw new AuthError('AUTH_ISSUER_MISMATCH');
|
|
127
|
+
return this.tokens(new URLSearchParams({ grant_type: 'authorization_code', client_id: t.clientId, resource: t.resource, redirect_uri: t.redirectUri, code, code_verifier: t.verifier }), t.scopes);
|
|
128
|
+
}
|
|
129
|
+
async tokens(body, allowed) {
|
|
130
|
+
const m = await this.metadata();
|
|
131
|
+
let raw;
|
|
132
|
+
try {
|
|
133
|
+
raw = await this.request(m.tokenEndpoint, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body });
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
throw new AuthError(e instanceof AuthError && e.status === 400 ? 'AUTH_REAUTH_REQUIRED' : 'AUTH_TOKEN_RESULT_UNKNOWN');
|
|
137
|
+
}
|
|
138
|
+
const r = raw;
|
|
139
|
+
if (r.token_type !== 'Bearer' || typeof r.access_token !== 'string' || typeof r.refresh_token !== 'string' || !/^[-\w]{43}$/.test(r.access_token) || !/^[-\w]{43}$/.test(r.refresh_token) || typeof r.expires_in !== 'number' || !Number.isSafeInteger(r.expires_in) || r.expires_in < 1 || r.expires_in > 86400 || typeof r.scope !== 'string')
|
|
140
|
+
throw new AuthError('AUTH_TOKEN_RESULT_UNKNOWN');
|
|
141
|
+
const scopes = r.scope.split(' ');
|
|
142
|
+
if (allowed && scopes.some(s => !allowed.includes(s)))
|
|
143
|
+
throw new AuthError('AUTH_SCOPE_MISMATCH');
|
|
144
|
+
return { accessToken: r.access_token, refreshToken: r.refresh_token, expiresAt: new Date(Date.now() + r.expires_in * 1000).toISOString(), scopes };
|
|
145
|
+
}
|
|
146
|
+
async refresh(clientId, refreshToken, scopes) { const p = await this.discover(); return this.tokens(new URLSearchParams({ grant_type: 'refresh_token', client_id: clientId, resource: p.resource, refresh_token: refreshToken }), scopes); }
|
|
147
|
+
async session(token, expected) {
|
|
148
|
+
const p = await this.discover();
|
|
149
|
+
let s;
|
|
150
|
+
try {
|
|
151
|
+
s = tenantSessionSchema.parse(await this.request(p.tenantSessionEndpoint, { headers: { authorization: 'Bearer ' + token } }));
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
if (e instanceof AuthError)
|
|
155
|
+
throw e;
|
|
156
|
+
throw new AuthError('AUTH_SESSION_INVALID');
|
|
157
|
+
}
|
|
158
|
+
if (s.issuer !== p.issuer || s.resource !== p.resource || expected && Object.entries(expected).some(([k, v]) => s[k] !== v))
|
|
159
|
+
throw new AuthError('AUTH_CONTEXT_MISMATCH');
|
|
160
|
+
return s;
|
|
161
|
+
}
|
|
162
|
+
async operationStatus(token, raw, expectedTenantId) { const input = tenantOperationStatusInputSchema.parse(raw), p = await this.discover(), url = new URL(p.tenantOperationStatusEndpoint); url.searchParams.set('requestId', input.requestId); const result = tenantOperationStatusSchema.parse(await this.request(url, { headers: { authorization: 'Bearer ' + token } })); if (expectedTenantId && result.tenantId !== expectedTenantId)
|
|
163
|
+
throw new AuthError('AUTH_CONTEXT_MISMATCH'); return result; }
|
|
164
|
+
async revoke(clientId, token) { const m = await this.metadata(); await this.request(m.revocationEndpoint, { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ client_id: clientId, token }) }); }
|
|
165
|
+
}
|