feat(p7): /v1/ai REST + kernel-client AI methods + @insignia/iios-ai-web SDK

Task 7.5: AiController exposes POST /v1/ai/jobs, GET /v1/ai/artifacts(+/:id),
POST accept/reject (session-auth). kernel-client RestClient gains runAiJob/
listArtifacts/getArtifact/acceptArtifact/rejectArtifact + AiArtifact/AiClaim/
AiEvidenceLink/AiModelRun types. New @insignia/iios-ai-web (AiProvider, useRunJob,
useAiProposals, useArtifact). Boundary allowlist: ai-web -> contracts +
kernel-client (fails on ai-web->service).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 16:42:07 +05:30
parent 68d922c248
commit 881b37afd0
12 changed files with 294 additions and 1 deletions
+27
View File
@@ -0,0 +1,27 @@
{
"name": "@insignia/iios-ai-web",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "dist/index.js",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"exports": { ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" } },
"files": ["dist"],
"scripts": {
"build": "tsup",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@insignia/iios-kernel-client": "workspace:*"
},
"peerDependencies": {
"react": ">=18"
},
"devDependencies": {
"@types/react": "^19.0.0",
"react": "^19.0.0",
"tsup": "^8.3.5",
"typescript": "^5.7.3"
}
}
+2
View File
@@ -0,0 +1,2 @@
export { AiProvider, useRunJob, useAiProposals, useArtifact } from './react';
export type { AiArtifact, AiClaim, AiEvidenceLink, AiModelRun, AiJobResult } from '@insignia/iios-kernel-client';
+83
View File
@@ -0,0 +1,83 @@
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { RestClient, type AiArtifact, type AiJobResult } from '@insignia/iios-kernel-client';
const AiContext = createContext<RestClient | null>(null);
export function AiProvider({
serviceUrl,
token,
children,
}: {
serviceUrl: string;
token: string;
children: React.ReactNode;
}): React.ReactElement {
const client = useMemo(() => new RestClient({ serviceUrl, token }), [serviceUrl, token]);
return <AiContext.Provider value={client}>{children}</AiContext.Provider>;
}
function useClient(): RestClient {
const c = useContext(AiContext);
if (!c) throw new Error('AI hooks must be used within <AiProvider>');
return c;
}
/** Run an AI worker (proposal only). Returns the job + proposed artifact (or null on abstain/fail). */
export function useRunJob(): (interactionId: string, jobType: 'CLASSIFY' | 'SUMMARIZE' | 'EXTRACT') => Promise<AiJobResult> {
const client = useClient();
return (interactionId, jobType) => client.runAiJob({ interactionId, jobType });
}
/** List AI proposals + human accept/reject feedback. */
export function useAiProposals(opts?: { interactionId?: string; status?: string; pollMs?: number }): {
artifacts: AiArtifact[];
refresh: () => Promise<void>;
accept: (id: string) => Promise<void>;
reject: (id: string) => Promise<void>;
} {
const client = useClient();
const [artifacts, setArtifacts] = useState<AiArtifact[]>([]);
const refresh = useCallback(async () => {
try {
setArtifacts(await client.listArtifacts({ interactionId: opts?.interactionId, status: opts?.status }));
} catch {
/* transient */
}
}, [client, opts?.interactionId, opts?.status]);
useEffect(() => {
void refresh();
if (!opts?.pollMs) return;
const t = setInterval(() => void refresh(), opts.pollMs);
return () => clearInterval(t);
}, [refresh, opts?.pollMs]);
const accept = async (id: string) => {
await client.acceptArtifact(id);
await refresh();
};
const reject = async (id: string) => {
await client.rejectArtifact(id);
await refresh();
};
return { artifacts, refresh, accept, reject };
}
/** Full artifact detail (claims + evidence + model run) for explainability. */
export function useArtifact(id: string | null): { artifact: AiArtifact | null; refresh: () => Promise<void> } {
const client = useClient();
const [artifact, setArtifact] = useState<AiArtifact | null>(null);
const refresh = useCallback(async () => {
if (!id) {
setArtifact(null);
return;
}
try {
setArtifact(await client.getArtifact(id));
} catch {
/* transient */
}
}, [client, id]);
useEffect(() => {
void refresh();
}, [refresh]);
return { artifact, refresh };
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ES2022", "DOM"],
"jsx": "react-jsx",
"types": ["react"]
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["src/**/*.test.ts", "src/**/*.test.tsx"]
}
+9
View File
@@ -0,0 +1,9 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
clean: true,
external: ['react', 'react-dom', '@insignia/iios-kernel-client'],
});