@openchambery/web 1.19.13-beta.1 → 1.19.13-beta.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/dist/assets/{AgentAvatar-CiQIIM0d.js → AgentAvatar-mbvX7YlW.js} +1 -1
- package/dist/assets/AssistantView-D5c557Oe.js +2 -0
- package/dist/assets/DiagramView-CjNUrHsU.js +1 -0
- package/dist/assets/FadeInOnReveal-moBbL8Ll.js +1 -0
- package/dist/assets/{Icon-CeKHYyKZ.js → Icon-CkzzhaLr.js} +1 -1
- package/dist/assets/MarkdownRenderer-BHfbAwVO.js +2 -0
- package/dist/assets/MarkdownRendererImpl-CkBoUibc.js +7 -0
- package/dist/assets/{MarkstreamRendererImpl-B-odd2Gz.js → MarkstreamRendererImpl-BrXUOI9g.js} +1 -1
- package/dist/assets/MobileDetailNavigation-aY9-HwBN.js +1 -0
- package/dist/assets/MobileShareBridge-CRt_8DmM.js +4 -0
- package/dist/assets/MobileSurface-CIhtF-CU.js +2 -0
- package/dist/assets/{MultiRunWindow-BT5Dwgzu.js → MultiRunWindow-DDq6C8t7.js} +1 -1
- package/dist/assets/OnboardingScreen-j0iCyH7f.js +1 -0
- package/dist/assets/{OverlayScrollbar-DUghqFGg.js → OverlayScrollbar-CDC5B3RH.js} +1 -1
- package/dist/assets/SettingsView-DFWWMvAj.js +25 -0
- package/dist/assets/{SettingsWindow-DfEGfyr8.js → SettingsWindow-CnYfBplB.js} +1 -1
- package/dist/assets/TerminalView-D5CQFVVm.js +8 -0
- package/dist/assets/ToolOutputDialog-VMI7TQFT.js +10 -0
- package/dist/assets/{appThemeRegistry-CIZ-shMD.js → appThemeRegistry-UIrP0-5h.js} +1 -1
- package/dist/assets/assistant-attachment-cache-C0aRnFkj.js +1 -0
- package/dist/assets/{button-DFPOdZ0g.js → button-xx1UOBA5.js} +1 -1
- package/dist/assets/{clipboard-C05anBwU.js → clipboard-CUasgPiF.js} +1 -1
- package/dist/assets/{collapsible-oGbEvbe1.js → collapsible-CACHBRYZ.js} +1 -1
- package/dist/assets/{context-menu-DGz4_2hs.js → context-menu-CYbm4wBZ.js} +1 -1
- package/dist/assets/desktopHosts-DILvrAok.js +1 -0
- package/dist/assets/desktopRelayRestore-t8asCr3m.js +1 -0
- package/dist/assets/{diagram-Csk5GpDC.js → diagram-u0BC9uuY.js} +1 -1
- package/dist/assets/{dialog-rrqB-H6q.js → dialog-BwQ3rY_x.js} +1 -1
- package/dist/assets/dualLimitLru-D8Sphgua.js +1 -0
- package/dist/assets/{es-FE0sJ9EC.js → es-CRjOwvYM.js} +1 -1
- package/dist/assets/fileQueries-Bgly-E8f.js +1 -0
- package/dist/assets/{fr-DNDCSfGt.js → fr-D9taEN9L.js} +1 -1
- package/dist/assets/{gitApi-_P0FsoXe.js → gitApi-Bu4W3bgD.js} +1 -1
- package/dist/assets/imageSource-BlAJJUEI.js +1 -0
- package/dist/assets/{input-BO3r20op.js → input-CI730njs.js} +1 -1
- package/dist/assets/insertionBoundaries-B1AqxsBD.js +4 -0
- package/dist/assets/{ja-BDwrk0cM.js → ja-CYQ5aoWp.js} +1 -1
- package/dist/assets/{ko-BeeU2o4I.js → ko-BwASV0Q0.js} +1 -1
- package/dist/assets/main-CqcB6tKc.js +169 -0
- package/dist/assets/main-DG-k0j8A.js +2 -0
- package/dist/assets/markdown-worker-CptoICmq.js +2 -0
- package/dist/assets/markdownHeightCache-DEjGKLv8.js +3 -0
- package/dist/assets/markdownTheme-DOfGg-A7.js +1 -0
- package/dist/assets/miniChat-BQtYQrsh.js +2 -0
- package/dist/assets/mobile-8iyqtXkr.js +2 -0
- package/dist/assets/{multirun-CxhTsmRq.js → multirun-CG_FCb_Z.js} +1 -1
- package/dist/assets/persistence-YbQKzHUQ.js +3 -0
- package/dist/assets/{pl-BYuzpcI5.js → pl-DUI1vPil.js} +1 -1
- package/dist/assets/platform-DzsGqlGW.js +2 -0
- package/dist/assets/projectMeta-DMpMDWE1.js +3 -0
- package/dist/assets/{pt-BR-Bn7cwEzv.js → pt-BR-CZQFJllP.js} +1 -1
- package/dist/assets/relay-image-stream-DM7atqJs.js +2 -0
- package/dist/assets/renderElectronMiniChatApp-DBzRdJl8.js +2 -0
- package/dist/assets/renderMobileApp-CIseqCyn.js +28 -0
- package/dist/assets/runtime-auth-D66yq6ie.js +1 -0
- package/dist/assets/runtime-fetch-zIalUnbb.js +1 -0
- package/dist/assets/runtime-switch-LvWDU-SL.js +1 -0
- package/dist/assets/runtimeConfig-D8nJYB-e.js +2 -0
- package/dist/assets/{runtimeEndpointReset-BBLFgl85.js → runtimeEndpointReset-CSub_ecV.js} +6 -6
- package/dist/assets/{sessionChangeSummary-DDVT7O5J.js → sessionChangeSummary-DQDZ5HW-.js} +1 -1
- package/dist/assets/{sessionLookup-C8VZn0gJ.js → sessionLookup-BiBD-4wC.js} +1 -1
- package/dist/assets/sortable-tabs-strip-Dom6Sthu.js +1 -0
- package/dist/assets/src-AA-5nXsF.css +2 -0
- package/dist/assets/{store-n94dSb3M.js → store-CSnH_QXM.js} +2 -2
- package/dist/assets/terminalApi-noa4Cdbh.js +1 -0
- package/dist/assets/{textarea-BjX-KhMN.js → textarea-BfNtUdJE.js} +1 -1
- package/dist/assets/{tooltip-CQGqGAG_.js → tooltip-DPxumadF.js} +1 -1
- package/dist/assets/{uk-CwpbHEa3.js → uk-C1FYZlmA.js} +1 -1
- package/dist/assets/{url-BMjqccCo.js → url-CVhsI9kR.js} +1 -1
- package/dist/assets/{useAppFontEffects-BIFw36SA.js → useAppFontEffects-qHt5uaFN.js} +32 -32
- package/dist/assets/useAssistantUIStore-CLPCNoj_.js +1 -0
- package/dist/assets/useDesktopWindowControlsLayout-BipyIXiK.js +1 -0
- package/dist/assets/{useEffectiveDirectory-DJSYYkVp.js → useEffectiveDirectory-DSNCCbCt.js} +1 -1
- package/dist/assets/useFontPreferences-CB-cGo3I.js +1 -0
- package/dist/assets/useMobileNavigationStore-C1NJGAik.js +413 -0
- package/dist/assets/useSessionAutoCleanup-Dcn6Unvd.js +2 -0
- package/dist/assets/useUIStore-CxOoCv7B.js +1 -0
- package/dist/assets/useWorkerHighlightedLines-CzpT4M7V.js +7 -0
- package/dist/assets/{utils-81nUI_xp.js → utils-BHTVlVov.js} +1 -1
- package/dist/assets/{zh-CN-BpzFZ-LU.js → zh-CN-C96qP1ip.js} +1 -1
- package/dist/assets/{zh-TW-Bopgc5Eo.js → zh-TW-BsRk1Rbb.js} +1 -1
- package/dist/index.html +12 -9
- package/dist/mini-chat.html +12 -9
- package/dist/mobile.html +12 -9
- package/package.json +1 -1
- package/server/lib/assistant-delivery-parts.js +19 -1
- package/server/lib/assistants/DOCUMENTATION.md +39 -7
- package/server/lib/assistants/cards.js +16 -2
- package/server/lib/assistants/cards.test.js +16 -1
- package/server/lib/assistants/contact-attachments.http.test.js +436 -0
- package/server/lib/assistants/contact-attachments.js +814 -0
- package/server/lib/assistants/contact-attachments.p1.test.js +307 -0
- package/server/lib/assistants/contact-attachments.test.js +249 -0
- package/server/lib/assistants/contact-store.js +494 -25
- package/server/lib/assistants/contact-store.test.js +403 -1
- package/server/lib/assistants/contact-tools.js +6 -5
- package/server/lib/assistants/contact-tools.test.js +24 -6
- package/server/lib/assistants/contracts.js +41 -1
- package/server/lib/assistants/harness.test.js +124 -0
- package/server/lib/assistants/routes.js +46 -1
- package/server/lib/assistants/service.js +258 -66
- package/server/lib/assistants/service.test.js +400 -30
- package/server/lib/fs/prompt-attachment-store.js +663 -0
- package/server/lib/fs/prompt-attachment-store.test.js +263 -0
- package/server/lib/fs/routes.js +28 -72
- package/server/lib/fs/routes.test.js +15 -30
- package/server/lib/opencode/core-routes.js +2 -1
- package/dist/assets/AssistantView-YmeyxehG.js +0 -2
- package/dist/assets/DiagramView-BpZb-G9P.js +0 -1
- package/dist/assets/FadeInOnReveal-nf6-ZCrs.js +0 -1
- package/dist/assets/MarkdownRenderer-wpeGdciL.js +0 -2
- package/dist/assets/MarkdownRendererImpl-Peu0GVSQ.js +0 -7
- package/dist/assets/MobileDetailNavigation-CtYke_1C.js +0 -1
- package/dist/assets/MobileShareBridge-DIfOl8EZ.js +0 -4
- package/dist/assets/MobileSurface-_KoX75Ff.js +0 -2
- package/dist/assets/OnboardingScreen-BPusUgFG.js +0 -1
- package/dist/assets/SettingsView-BwQpbIZl.js +0 -25
- package/dist/assets/TerminalView-8402vpcO.js +0 -8
- package/dist/assets/ToolOutputDialog-B5RdkhsG.js +0 -10
- package/dist/assets/desktopHosts-BVJXQq1S.js +0 -1
- package/dist/assets/desktopRelayRestore-RDfuj1M7.js +0 -1
- package/dist/assets/fileQueries-BcdWOPlD.js +0 -1
- package/dist/assets/insertionBoundaries-CWoioTdE.js +0 -4
- package/dist/assets/main-CLj_WS1o.js +0 -2
- package/dist/assets/main-CnSZ89pD.js +0 -169
- package/dist/assets/markdown-worker-CZWE1F1M.js +0 -2
- package/dist/assets/markdownHeightCache-Cdd3hkMl.js +0 -3
- package/dist/assets/markdownTheme-DIB8WVmz.js +0 -2
- package/dist/assets/miniChat-B9c3y2pD.js +0 -2
- package/dist/assets/mobile-DvbXUfNS.js +0 -2
- package/dist/assets/persistence-CVTWFaqe.js +0 -3
- package/dist/assets/platform-BY_Gtsqr.js +0 -2
- package/dist/assets/projectMeta-Dl5iPCB6.js +0 -3
- package/dist/assets/renderElectronMiniChatApp-CkJ51qCh.js +0 -2
- package/dist/assets/renderMobileApp-DcjQPhWw.js +0 -28
- package/dist/assets/runtimeConfig-Bsk2kWsW.js +0 -2
- package/dist/assets/sortable-tabs-strip-s7UUxsa6.js +0 -1
- package/dist/assets/src-BpHDMgud.css +0 -2
- package/dist/assets/terminalApi-C54_rKEK.js +0 -1
- package/dist/assets/useAssistantUIStore-Cg9bsT4b.js +0 -1
- package/dist/assets/useDesktopWindowControlsLayout-DoygEcN4.js +0 -1
- package/dist/assets/useFontPreferences-B5_apbBC.js +0 -1
- package/dist/assets/useMobileNavigationStore-DuTu0Mzh.js +0 -413
- package/dist/assets/useSessionAutoCleanup-C-L9teva.js +0 -1
- package/dist/assets/useUIStore-DwpQTjLO.js +0 -1
- package/dist/assets/useWorkerHighlightedLines-DxTfgdEi.js +0 -7
|
@@ -3,6 +3,8 @@ import os from 'node:os'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { describe, expect, it, vi } from 'vitest'
|
|
5
5
|
import {
|
|
6
|
+
CLEAR_CHAT_HISTORY_CONFIRM_BUBBLE,
|
|
7
|
+
CLEAR_CHAT_HISTORY_TOOL_NAME,
|
|
6
8
|
CREATE_ASSISTANT_TOOL_NAME,
|
|
7
9
|
MESSAGE_ASSISTANT_TOOL_NAME,
|
|
8
10
|
MISSED_FENCE_RETRY_USER_TEXT,
|
|
@@ -875,4 +877,126 @@ describe('runContactTurn', () => {
|
|
|
875
877
|
expect(result.bubbles.join('')).not.toContain('dot.png')
|
|
876
878
|
expect(result.bubbles.join('')).not.toContain('note.txt')
|
|
877
879
|
})
|
|
880
|
+
|
|
881
|
+
it('real Agent loop: clear_chat_history fence runs once, terminates, no follow-up completion', async () => {
|
|
882
|
+
const clearContactMemory = vi.fn(async () => ({ reset: true, memoryCleared: true }))
|
|
883
|
+
const resetContact = vi.fn(async () => ({ reset: true, historyCleared: true }))
|
|
884
|
+
const tools = createContactTools({ clearContactMemory, resetContact })
|
|
885
|
+
let completions = 0
|
|
886
|
+
const createChatCompletion = vi.fn(async () => {
|
|
887
|
+
completions += 1
|
|
888
|
+
if (completions > 4) {
|
|
889
|
+
throw new Error('harness safety stop: clear_chat_history must terminate without follow-up LLM')
|
|
890
|
+
}
|
|
891
|
+
// Looping model would keep emitting wipe fences; terminate must stop after one tool run.
|
|
892
|
+
return {
|
|
893
|
+
completion: {
|
|
894
|
+
choices: [{
|
|
895
|
+
message: {
|
|
896
|
+
content: '```openchamber-tool\n{"name":"clear_chat_history","arguments":{}}\n```',
|
|
897
|
+
},
|
|
898
|
+
}],
|
|
899
|
+
},
|
|
900
|
+
}
|
|
901
|
+
})
|
|
902
|
+
const result = await runContactTurn({
|
|
903
|
+
assistant: { providerID: 'p', modelID: 'm', defaultPrompt: '' },
|
|
904
|
+
history: [
|
|
905
|
+
{ role: 'user', content: 'secret before wipe' },
|
|
906
|
+
{ role: 'assistant', content: 'remembered secret' },
|
|
907
|
+
],
|
|
908
|
+
userText: '清空聊天记录',
|
|
909
|
+
tools,
|
|
910
|
+
createChatCompletion,
|
|
911
|
+
// Default real Agent — only completion is stubbed (same pattern as assign terminate).
|
|
912
|
+
})
|
|
913
|
+
expect(resetContact).toHaveBeenCalledTimes(1)
|
|
914
|
+
expect(clearContactMemory).not.toHaveBeenCalled()
|
|
915
|
+
expect(result.reset).toBe(true)
|
|
916
|
+
expect(result.historyCleared).toBe(true)
|
|
917
|
+
expect(result.bubbles).toEqual([CLEAR_CHAT_HISTORY_CONFIRM_BUBBLE])
|
|
918
|
+
expect(result.bubbles.join('')).not.toMatch(/not cleared|denied|new_conversation instead|secret/i)
|
|
919
|
+
expect(result.cards).toEqual([])
|
|
920
|
+
expect(createChatCompletion).toHaveBeenCalledTimes(1)
|
|
921
|
+
expect(completions).toBe(1)
|
|
922
|
+
})
|
|
923
|
+
|
|
924
|
+
it('real Agent loop: clear_chat_history after cross-turn confirm terminates once with confirm only', async () => {
|
|
925
|
+
const clearContactMemory = vi.fn(async () => ({ reset: true, memoryCleared: true }))
|
|
926
|
+
const resetContact = vi.fn(async () => ({ reset: true, historyCleared: true }))
|
|
927
|
+
const tools = createContactTools({ clearContactMemory, resetContact })
|
|
928
|
+
let completions = 0
|
|
929
|
+
const createChatCompletion = vi.fn(async () => {
|
|
930
|
+
completions += 1
|
|
931
|
+
if (completions > 4) {
|
|
932
|
+
throw new Error('harness safety stop: wipe must terminate once')
|
|
933
|
+
}
|
|
934
|
+
return {
|
|
935
|
+
completion: {
|
|
936
|
+
choices: [{
|
|
937
|
+
message: {
|
|
938
|
+
content: '```openchamber-tool\n{"name":"clear_chat_history","arguments":{}}\n```',
|
|
939
|
+
},
|
|
940
|
+
}],
|
|
941
|
+
},
|
|
942
|
+
}
|
|
943
|
+
})
|
|
944
|
+
const result = await runContactTurn({
|
|
945
|
+
assistant: { providerID: 'p', modelID: 'm', defaultPrompt: '' },
|
|
946
|
+
history: [
|
|
947
|
+
{ role: 'user', content: '能不能帮我清一下聊天?' },
|
|
948
|
+
{ role: 'assistant', content: '是要清空聊天记录吗?' },
|
|
949
|
+
],
|
|
950
|
+
userText: '对,清空聊天记录',
|
|
951
|
+
tools,
|
|
952
|
+
createChatCompletion,
|
|
953
|
+
})
|
|
954
|
+
expect(resetContact).toHaveBeenCalledTimes(1)
|
|
955
|
+
expect(clearContactMemory).not.toHaveBeenCalled()
|
|
956
|
+
expect(result.reset).toBe(true)
|
|
957
|
+
expect(result.historyCleared).toBe(true)
|
|
958
|
+
expect(result.bubbles).toEqual([CLEAR_CHAT_HISTORY_CONFIRM_BUBBLE])
|
|
959
|
+
expect(result.bubbles.join('')).not.toMatch(/not cleared|denied|explicit wipe|new_conversation instead/i)
|
|
960
|
+
expect(createChatCompletion).toHaveBeenCalledTimes(1)
|
|
961
|
+
expect(completions).toBe(1)
|
|
962
|
+
})
|
|
963
|
+
|
|
964
|
+
it('real Agent loop: new_conversation terminates once and does not call resetContact', async () => {
|
|
965
|
+
const clearContactMemory = vi.fn(async () => ({ reset: true, memoryCleared: true }))
|
|
966
|
+
const resetContact = vi.fn(async () => ({ reset: true, historyCleared: true }))
|
|
967
|
+
const tools = createContactTools({ clearContactMemory, resetContact })
|
|
968
|
+
let completions = 0
|
|
969
|
+
const createChatCompletion = vi.fn(async () => {
|
|
970
|
+
completions += 1
|
|
971
|
+
if (completions > 4) {
|
|
972
|
+
throw new Error('harness safety stop: new_conversation must terminate without follow-up LLM')
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
completion: {
|
|
976
|
+
choices: [{
|
|
977
|
+
message: {
|
|
978
|
+
content: '```openchamber-tool\n{"name":"new_conversation","arguments":{}}\n```',
|
|
979
|
+
},
|
|
980
|
+
}],
|
|
981
|
+
},
|
|
982
|
+
}
|
|
983
|
+
})
|
|
984
|
+
const result = await runContactTurn({
|
|
985
|
+
assistant: { providerID: 'p', modelID: 'm', defaultPrompt: '' },
|
|
986
|
+
history: [
|
|
987
|
+
{ role: 'user', content: 'transcript stays' },
|
|
988
|
+
{ role: 'assistant', content: 'yes it does' },
|
|
989
|
+
],
|
|
990
|
+
userText: '清除记忆',
|
|
991
|
+
tools,
|
|
992
|
+
createChatCompletion,
|
|
993
|
+
})
|
|
994
|
+
expect(clearContactMemory).toHaveBeenCalledTimes(1)
|
|
995
|
+
expect(resetContact).not.toHaveBeenCalled()
|
|
996
|
+
expect(result.reset).toBe(true)
|
|
997
|
+
expect(result.historyCleared).toBe(false)
|
|
998
|
+
expect(result.bubbles).toEqual([NEW_CONVERSATION_CONFIRM_BUBBLE])
|
|
999
|
+
expect(createChatCompletion).toHaveBeenCalledTimes(1)
|
|
1000
|
+
expect(completions).toBe(1)
|
|
1001
|
+
})
|
|
878
1002
|
})
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { AssistantError, createAssistantsService } from './service.js';
|
|
2
|
+
import { isSafeInlineImageMime } from './contact-attachments.js';
|
|
2
3
|
import { createChatCompletion } from '../llm/completions.js';
|
|
3
4
|
import { ensureLlmTempDirectory } from '../llm/temp-directory.js';
|
|
4
5
|
import { setAssignedSessionSettleHandler } from '../session-goal/runtime.js';
|
|
5
6
|
import { createOpencodeClient } from '@opencode-ai/sdk/v2';
|
|
6
7
|
|
|
7
|
-
const respond = (res, work, success = 200) => Promise.resolve().then(work).then((body) => res.status(success).json(body)).catch((error) => { const code = error instanceof AssistantError ? error.code : error?.code || 'internal_error'; const status = code === 'not_found' ? 404 : ['revision_conflict', 'idempotency_conflict'].includes(code) ? 409 : code === 'assistant_disabled' ? 403 : code === 'no_provider' ? 400 : code === 'upstream_error' ? 502 : 400; res.status(status).json({ ok: false, error: code, message: typeof error?.message === 'string' && error.message.trim() ? error.message : code }); });
|
|
8
|
+
const respond = (res, work, success = 200) => Promise.resolve().then(work).then((body) => res.status(success).json(body)).catch((error) => { const code = error instanceof AssistantError ? error.code : error?.code || 'internal_error'; const status = code === 'not_found' ? 404 : ['revision_conflict', 'idempotency_conflict', 'contact_generation_conflict'].includes(code) ? 409 : code === 'assistant_disabled' ? 403 : code === 'no_provider' ? 400 : code === 'upstream_error' ? 502 : 400; res.status(status).json({ ok: false, error: code, message: typeof error?.message === 'string' && error.message.trim() ? error.message : code }); });
|
|
8
9
|
const gone = (_req, res) => res.status(410).json({ ok: false, error: 'assistant_topics_retired' });
|
|
9
10
|
|
|
10
11
|
export const registerAssistantRoutes = (app, dependencies) => {
|
|
@@ -49,6 +50,50 @@ export const registerAssistantRoutes = (app, dependencies) => {
|
|
|
49
50
|
app.post('/api/openchamber/assistants/:assistantID/contact/reset', (req, res) => respond(res, () => service.resetContact(req.params.assistantID)));
|
|
50
51
|
app.post('/api/openchamber/assistants/:assistantID/contact/cards', (req, res) => respond(res, () => service.appendContactCard(req.params.assistantID, req.body), 201));
|
|
51
52
|
app.post('/api/openchamber/assistants/:assistantID/contact/dm', (req, res) => respond(res, () => service.deliverPeerMessage(req.params.assistantID, req.body), 201));
|
|
53
|
+
// Contact attachments: raw-byte PUT + opaque GET (content-addressed under dataDir).
|
|
54
|
+
app.put('/api/openchamber/assistants/:assistantID/contact/attachments/:uploadID', (req, res) => {
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
req.once?.('aborted', () => controller.abort());
|
|
57
|
+
respond(res, () => service.putAssistantContactAttachment(req.params.assistantID, req.params.uploadID, {
|
|
58
|
+
stream: req,
|
|
59
|
+
headers: req.headers,
|
|
60
|
+
signal: controller.signal,
|
|
61
|
+
}), 201);
|
|
62
|
+
});
|
|
63
|
+
app.get('/api/openchamber/assistants/:assistantID/contact/attachments/:attachmentID', (req, res) => {
|
|
64
|
+
Promise.resolve()
|
|
65
|
+
.then(() => service.getAssistantContactAttachment(req.params.assistantID, req.params.attachmentID))
|
|
66
|
+
.then((file) => {
|
|
67
|
+
res.status(200);
|
|
68
|
+
res.setHeader('Content-Type', file.mime || 'application/octet-stream');
|
|
69
|
+
res.setHeader('Content-Length', String(file.size));
|
|
70
|
+
res.setHeader('ETag', file.etag || `"${file.sha256}"`);
|
|
71
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
72
|
+
// App owns cache; do not let browsers/CDNs immutable-cache attachment bodies.
|
|
73
|
+
res.setHeader('Cache-Control', 'private, no-store');
|
|
74
|
+
const safeInline = isSafeInlineImageMime(file.mime);
|
|
75
|
+
const name = file.filename ? encodeURIComponent(file.filename) : 'attachment';
|
|
76
|
+
res.setHeader(
|
|
77
|
+
'Content-Disposition',
|
|
78
|
+
`${safeInline ? 'inline' : 'attachment'}; filename*=UTF-8''${name}`,
|
|
79
|
+
);
|
|
80
|
+
res.end(file.buffer);
|
|
81
|
+
})
|
|
82
|
+
.catch((error) => {
|
|
83
|
+
const code = error instanceof AssistantError ? error.code : error?.code || 'internal_error';
|
|
84
|
+
const status = code === 'not_found' ? 404
|
|
85
|
+
: ['revision_conflict', 'idempotency_conflict', 'contact_generation_conflict'].includes(code) ? 409
|
|
86
|
+
: code === 'assistant_disabled' ? 403
|
|
87
|
+
: code === 'PROMPT_ATTACHMENT_TOO_LARGE' ? 413
|
|
88
|
+
: code === 'upstream_error' ? 502
|
|
89
|
+
: 400;
|
|
90
|
+
res.status(status).json({
|
|
91
|
+
ok: false,
|
|
92
|
+
error: code,
|
|
93
|
+
message: typeof error?.message === 'string' && error.message.trim() ? error.message : code,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
52
97
|
app.get('/api/openchamber/assistants/:assistantID/scheduled-tasks', (req, res) => respond(res, () => service.listAssistantScheduledTasks(req.params.assistantID)));
|
|
53
98
|
// Contact send admits the user message at 202; assistant bubbles stream on SSE.
|
|
54
99
|
app.post('/api/openchamber/assistants/:assistantID/messages', (req, res) => respond(res, () => service.send(req.params.assistantID, req.body), 202)); app.post('/api/openchamber/assistants/:assistantID/share', (req, res) => respond(res, () => service.share(req.params.assistantID, req.body), 202)); app.get('/api/openchamber/assistants/share-operations/:operationID', (req, res) => respond(res, () => { const operation = service.shareOperation(req.params.operationID); if (!operation) throw new AssistantError('not_found'); return operation; }));
|